1+ // Shared TypeScript AST and source-file helpers for guard scripts.
12import { existsSync , promises as fs } from "node:fs" ;
23import { createRequire } from "node:module" ;
34import path from "node:path" ;
@@ -13,6 +14,9 @@ function getTypeScript() {
1314
1415const baseTestSuffixes = [ ".test.ts" , ".test-utils.ts" , ".test-harness.ts" , ".e2e-harness.ts" ] ;
1516
17+ /**
18+ * Resolves the repository root by walking upward from the caller module.
19+ */
1620export function resolveRepoRoot ( importMetaUrl ) {
1721 // Walk up from the caller's directory until we find the repo root (.git).
1822 // This handles callers at any depth (scripts/*.mjs, scripts/lib/*.mjs, etc.)
@@ -29,6 +33,9 @@ export function resolveRepoRoot(importMetaUrl) {
2933 return path . resolve ( path . dirname ( fileURLToPath ( importMetaUrl ) ) , ".." , ".." ) ;
3034}
3135
36+ /**
37+ * Converts repo-relative source roots into absolute paths.
38+ */
3239export function resolveSourceRoots ( repoRoot , relativeRoots ) {
3340 return relativeRoots . map ( ( root ) => path . join ( repoRoot , ...root . split ( "/" ) . filter ( Boolean ) ) ) ;
3441}
@@ -38,6 +45,9 @@ function isTestLikeTypeScriptFile(filePath, options = {}) {
3845 return [ ...baseTestSuffixes , ...extraTestSuffixes ] . some ( ( suffix ) => filePath . endsWith ( suffix ) ) ;
3946}
4047
48+ /**
49+ * Recursively collects TypeScript files under a file or directory target.
50+ */
4151export async function collectTypeScriptFiles ( targetPath , options = { } ) {
4252 const includeTests = options . includeTests ?? false ;
4353 const extraTestSuffixes = options . extraTestSuffixes ?? [ ] ;
@@ -92,6 +102,9 @@ export async function collectTypeScriptFiles(targetPath, options = {}) {
92102 return out ;
93103}
94104
105+ /**
106+ * Collects TypeScript files from multiple roots, ignoring missing roots by default.
107+ */
95108export async function collectTypeScriptFilesFromRoots ( sourceRoots , options = { } ) {
96109 return (
97110 await Promise . all (
@@ -106,6 +119,9 @@ export async function collectTypeScriptFilesFromRoots(sourceRoots, options = {})
106119 ) . flat ( ) ;
107120}
108121
122+ /**
123+ * Runs a guard's violation scanner across collected TypeScript source files.
124+ */
109125export async function collectFileViolations ( params ) {
110126 const files = await collectTypeScriptFilesFromRoots ( params . sourceRoots , {
111127 extraTestSuffixes : params . extraTestSuffixes ,
@@ -128,10 +144,16 @@ export async function collectFileViolations(params) {
128144 return violations ;
129145}
130146
147+ /**
148+ * Returns the one-based source line for a TypeScript AST node.
149+ */
131150export function toLine ( sourceFile , node ) {
132151 return sourceFile . getLineAndCharacterOfPosition ( node . getStart ( sourceFile ) ) . line + 1 ;
133152}
134153
154+ /**
155+ * Extracts text from identifier, string, or numeric property names.
156+ */
135157export function getPropertyNameText ( name ) {
136158 const ts = getTypeScript ( ) ;
137159 if ( ts . isIdentifier ( name ) || ts . isStringLiteral ( name ) || ts . isNumericLiteral ( name ) ) {
@@ -140,6 +162,9 @@ export function getPropertyNameText(name) {
140162 return null ;
141163}
142164
165+ /**
166+ * Removes harmless expression wrappers before AST shape checks.
167+ */
143168export function unwrapExpression ( expression ) {
144169 const ts = getTypeScript ( ) ;
145170 let current = expression ;
@@ -160,6 +185,9 @@ export function unwrapExpression(expression) {
160185 }
161186}
162187
188+ /**
189+ * Collects one-based line numbers for call expressions selected by a callback.
190+ */
163191export function collectCallExpressionLines ( ts , sourceFile , resolveLineNode ) {
164192 const lines = [ ] ;
165193 const visit = ( node ) => {
@@ -183,6 +211,9 @@ function isDirectExecution(importMetaUrl) {
183211 return path . resolve ( entry ) === fileURLToPath ( importMetaUrl ) ;
184212}
185213
214+ /**
215+ * Runs a script main function only when the module is the direct entrypoint.
216+ */
186217export function runAsScript ( importMetaUrl , main ) {
187218 if ( ! isDirectExecution ( importMetaUrl ) ) {
188219 return ;
0 commit comments