Skip to content

Commit 70aa9a7

Browse files
committed
refactor validator
1 parent 1a9203f commit 70aa9a7

File tree

4 files changed

+170
-243
lines changed

4 files changed

+170
-243
lines changed

eslint-local-rules/require-platform-declaration.js

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,8 @@
1717
* // Not exported as const array
1818
*/
1919

20-
const path = require('path');
2120
const { getValidPlatforms } = require('../scripts/platform-utils');
2221

23-
// Cache for valid platforms per workspace
24-
const validPlatformsCache = new Map();
25-
26-
function getValidPlatformsForContext(context) {
27-
const filename = context.getFilename();
28-
const workspaceRoot = filename.split('/lib/')[0];
29-
30-
if (validPlatformsCache.has(workspaceRoot)) {
31-
return validPlatformsCache.get(workspaceRoot);
32-
}
33-
34-
const platforms = getValidPlatforms(workspaceRoot);
35-
validPlatformsCache.set(workspaceRoot, platforms);
36-
return platforms;
37-
}
38-
3922
module.exports = {
4023
meta: {
4124
type: 'problem',
@@ -55,7 +38,7 @@ module.exports = {
5538
},
5639

5740
create(context) {
58-
const VALID_PLATFORMS = getValidPlatformsForContext(context);
41+
const VALID_PLATFORMS = getValidPlatforms();
5942
let hasPlatformExport = false;
6043

6144
return {

scripts/add-platform-exports.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ function processFile(filePath) {
285285
let content = fs.readFileSync(filePath, 'utf-8');
286286

287287
// Get valid platforms for validation
288-
const validPlatforms = getValidPlatforms(WORKSPACE_ROOT);
288+
const validPlatforms = getValidPlatforms();
289289

290290
// Use TypeScript parser to check for existing __platforms
291291
const result = extractPlatformsFromAST(

scripts/platform-utils.js

Lines changed: 70 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,21 @@ const ts = require('typescript');
3131
// Cache for valid platforms
3232
let 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

262270
module.exports = {

0 commit comments

Comments
 (0)