@@ -31,18 +31,21 @@ const ts = require('typescript');
3131// Cache for valid platforms
3232let validPlatformsCache = null ;
3333
34+ // Cache for file platforms
35+ const filePlatformCache = new Map ( ) ;
36+
3437/**
3538 * Extract valid platform values from Platform type definition in platform_support.ts
3639 * Parses: type Platform = 'browser' | 'node' | 'react_native' | '__universal__';
3740 *
38- * @param {string } workspaceRoot - The root directory of the workspace
3941 * @returns {string[] } Array of valid platform identifiers
4042 */
41- function getValidPlatforms ( workspaceRoot ) {
43+ function getValidPlatforms ( ) {
4244 if ( validPlatformsCache ) {
4345 return validPlatformsCache ;
4446 }
4547
48+ const workspaceRoot = path . join ( __dirname , '..' ) ;
4649 const platformSupportPath = path . join ( workspaceRoot , 'lib' , 'platform_support.ts' ) ;
4750
4851 if ( ! fs . existsSync ( platformSupportPath ) ) {
@@ -59,8 +62,8 @@ function getValidPlatforms(workspaceRoot) {
5962
6063 const platforms = [ ] ;
6164
62- // Visit all nodes in the AST
63- function visit ( node ) {
65+ // Visit only top-level statements since Platform type must be exported at top level
66+ for ( const node of sourceFile . statements ) {
6467 // Look for: export type Platform = 'browser' | 'node' | ...
6568 if ( ts . isTypeAliasDeclaration ( node ) &&
6669 node . name . text === 'Platform' &&
@@ -77,13 +80,11 @@ function getValidPlatforms(workspaceRoot) {
7780 // Handle single literal type: type Platform = 'browser';
7881 platforms . push ( node . type . literal . text ) ;
7982 }
83+
84+ break ; // Found it, stop searching
8085 }
81-
82- ts . forEachChild ( node , visit ) ;
8386 }
8487
85- visit ( sourceFile ) ;
86-
8788 if ( platforms . length === 0 ) {
8889 throw new Error ( `Could not extract Platform type from ${ platformSupportPath } ` ) ;
8990 }
@@ -113,67 +114,66 @@ function extractPlatformsFromAST(sourceFile, validPlatforms) {
113114 let platforms = [ ] ;
114115 let hasNonStringLiteral = false ;
115116
116- function visit ( node ) {
117+ // Visit only top-level children since __platforms must be exported at top level
118+ for ( const node of sourceFile . statements ) {
117119 // Look for: export const __platforms = [...]
118- if ( ts . isVariableStatement ( node ) ) {
119- // Check if it has export modifier
120- const hasExport = node . modifiers ?. some (
121- mod => mod . kind === ts . SyntaxKind . ExportKeyword
122- ) ;
120+ if ( ! ts . isVariableStatement ( node ) ) continue ;
121+
122+ // Check if it has export modifier
123+ const hasExport = node . modifiers ?. some (
124+ mod => mod . kind === ts . SyntaxKind . ExportKeyword
125+ ) ;
126+ if ( ! hasExport ) continue ;
123127
124- if ( hasExport ) {
125- for ( const declaration of node . declarationList . declarations ) {
126- if ( ts . isVariableDeclaration ( declaration ) &&
127- ts . isIdentifier ( declaration . name ) &&
128- declaration . name . text === '__platforms' ) {
129-
130- found = true ;
131-
132- let initializer = declaration . initializer ;
133-
134- // Handle "as const" assertion: [...] as const
135- if ( initializer && ts . isAsExpression ( initializer ) ) {
136- initializer = initializer . expression ;
137- }
138-
139- // Handle type assertion: <const>[...]
140- if ( initializer && ts . isTypeAssertionExpression ( initializer ) ) {
141- initializer = initializer . expression ;
142- }
143-
144- // Check if it's an array
145- if ( initializer && ts . isArrayLiteralExpression ( initializer ) ) {
146- isArray = true ;
147-
148- // Extract array elements
149- for ( const element of initializer . elements ) {
150- if ( ts . isStringLiteral ( element ) ) {
151- platforms . push ( element . text ) ;
152- } else {
153- // Non-string literal found (variable, computed value, etc.)
154- hasNonStringLiteral = true ;
155- }
156- }
157- }
158-
159- return ; // Found it, stop visiting
128+ for ( const declaration of node . declarationList . declarations ) {
129+ if ( ! ts . isVariableDeclaration ( declaration ) ||
130+ ! ts . isIdentifier ( declaration . name ) ||
131+ declaration . name . text !== '__platforms' ) {
132+ continue ;
133+ }
134+
135+ found = true ;
136+
137+ let initializer = declaration . initializer ;
138+
139+ // Handle "as const" assertion: [...] as const
140+ if ( initializer && ts . isAsExpression ( initializer ) ) {
141+ initializer = initializer . expression ;
142+ }
143+
144+ // Handle type assertion: <const>[...]
145+ if ( initializer && ts . isTypeAssertionExpression ( initializer ) ) {
146+ initializer = initializer . expression ;
147+ }
148+
149+ // Check if it's an array
150+ if ( initializer && ts . isArrayLiteralExpression ( initializer ) ) {
151+ isArray = true ;
152+
153+ // Extract array elements
154+ for ( const element of initializer . elements ) {
155+ if ( ts . isStringLiteral ( element ) ) {
156+ platforms . push ( element . text ) ;
157+ } else {
158+ // Non-string literal found (variable, computed value, etc.)
159+ hasNonStringLiteral = true ;
160160 }
161161 }
162162 }
163+
164+ break ; // Found it, stop searching
163165 }
164-
165- ts . forEachChild ( node , visit ) ;
166+
167+ if ( found ) break ;
166168 }
167-
168- visit ( sourceFile ) ;
169169
170170 // Detailed error reporting
171171 if ( ! found ) {
172172 return {
173173 success : false ,
174174 error : {
175175 type : 'MISSING' ,
176- message : `File does not export '__platforms' constant `
176+ message : `File does not export '__platforms' array `
177177 }
178178 } ;
179179 }
@@ -231,32 +231,40 @@ function extractPlatformsFromAST(sourceFile, validPlatforms) {
231231
232232/**
233233 * Extract platforms from a file path with detailed error reporting
234+ * Uses caching to avoid re-parsing the same file multiple times.
234235 *
235- * @param {string } filePath - Relative path to the file (from workspaceRoot)
236- * @param {string } workspaceRoot - Workspace root directory
236+ * @param {string } absolutePath - Absolute path to the file
237237 * @returns {Object } Result object with success, platforms, and error information
238238 */
239- function extractPlatformsFromFile ( filePath , workspaceRoot ) {
239+ function extractPlatformsFromFile ( absolutePath ) {
240+ // Check cache first
241+ if ( filePlatformCache . has ( absolutePath ) ) {
242+ return filePlatformCache . get ( absolutePath ) ;
243+ }
244+
245+ let result ;
240246 try {
241- const validPlatforms = workspaceRoot ? getValidPlatforms ( workspaceRoot ) : null ;
242- const absolutePath = path . resolve ( workspaceRoot , filePath ) ;
247+ const validPlatforms = getValidPlatforms ( ) ;
243248 const content = fs . readFileSync ( absolutePath , 'utf-8' ) ;
244249 const sourceFile = ts . createSourceFile (
245250 absolutePath ,
246251 content ,
247252 ts . ScriptTarget . Latest ,
248253 true
249254 ) ;
250- return extractPlatformsFromAST ( sourceFile , validPlatforms ) ;
255+ result = extractPlatformsFromAST ( sourceFile , validPlatforms ) ;
251256 } catch ( error ) {
252- return {
257+ result = {
253258 success : false ,
254259 error : {
255260 type : 'READ_ERROR' ,
256261 message : `Failed to read or parse file: ${ error . message } `
257262 }
258263 } ;
259264 }
265+
266+ filePlatformCache . set ( absolutePath , result ) ;
267+ return result ;
260268}
261269
262270module . exports = {
0 commit comments