11/**
22 * ESLint Rule: require-platform-declaration
33 *
4- * Ensures that all non-test source files export __supportedPlatforms
4+ * Ensures that all non-test source files export __supportedPlatforms with valid platform values
55 *
66 * Valid:
7- * export const __supportedPlatforms = ['browser'] as const ;
8- * export const __supportedPlatforms = ['__universal__'] as const ;
9- * export const __supportedPlatforms: Platform[] = ['browser', 'node'];
7+ * export const __supportedPlatforms = ['browser'];
8+ * export const __supportedPlatforms = ['__universal__'];
9+ * export const __supportedPlatforms = ['browser', 'node'];
1010 *
1111 * Invalid:
1212 * // Missing __supportedPlatforms export
13+ * // Invalid platform values (must match Platform type definition in platform_support.ts)
14+ * // Not exported as const array
1315 */
1416
17+ const fs = require ( 'fs' ) ;
18+ const path = require ( 'path' ) ;
19+ const ts = require ( 'typescript' ) ;
20+
21+ // Cache for valid platforms
22+ let validPlatformsCache = null ;
23+
24+ function getValidPlatforms ( context ) {
25+ if ( validPlatformsCache ) {
26+ return validPlatformsCache ;
27+ }
28+
29+ try {
30+ const filename = context . getFilename ( ) ;
31+ const workspaceRoot = filename . split ( '/lib/' ) [ 0 ] ;
32+ const platformSupportPath = path . join ( workspaceRoot , 'lib' , 'platform_support.ts' ) ;
33+
34+ if ( fs . existsSync ( platformSupportPath ) ) {
35+ const content = fs . readFileSync ( platformSupportPath , 'utf8' ) ;
36+ const sourceFile = ts . createSourceFile (
37+ platformSupportPath ,
38+ content ,
39+ ts . ScriptTarget . Latest ,
40+ true
41+ ) ;
42+
43+ const platforms = [ ] ;
44+
45+ // Visit all nodes in the AST
46+ function visit ( node ) {
47+ // Look for: export type Platform = 'browser' | 'node' | ...
48+ if ( ts . isTypeAliasDeclaration ( node ) &&
49+ node . name . text === 'Platform' &&
50+ node . modifiers ?. some ( m => m . kind === ts . SyntaxKind . ExportKeyword ) ) {
51+
52+ // Parse the union type
53+ if ( ts . isUnionTypeNode ( node . type ) ) {
54+ for ( const type of node . type . types ) {
55+ if ( ts . isLiteralTypeNode ( type ) && ts . isStringLiteral ( type . literal ) ) {
56+ platforms . push ( type . literal . text ) ;
57+ }
58+ }
59+ }
60+ }
61+
62+ ts . forEachChild ( node , visit ) ;
63+ }
64+
65+ visit ( sourceFile ) ;
66+
67+ if ( platforms . length > 0 ) {
68+ validPlatformsCache = platforms ;
69+ return validPlatformsCache ;
70+ }
71+ }
72+ } catch ( error ) {
73+ // Fallback to hardcoded values if parsing fails
74+ console . warn ( 'Could not parse platform_support.ts, using fallback values:' , error . message ) ;
75+ }
76+
77+ // Fallback to default platforms
78+ validPlatformsCache = [ 'browser' , 'node' , 'react_native' , '__universal__' ] ;
79+ return validPlatformsCache ;
80+ }
81+
1582module . exports = {
1683 meta : {
1784 type : 'problem' ,
1885 docs : {
19- description : 'Require __supportedPlatforms export in all source files' ,
86+ description : 'Require __supportedPlatforms export with valid platform values in all source files' ,
2087 category : 'Best Practices' ,
2188 recommended : true ,
2289 } ,
2390 messages : {
24- missingPlatformDeclaration : 'File must export __supportedPlatforms to declare which platforms it supports. Example: export const __supportedPlatforms = [\'__universal__\'] as const;' ,
25- invalidPlatformDeclaration : '__supportedPlatforms must be exported as a const array. Example: export const __supportedPlatforms = [\'browser\', \'node\'] as const;' ,
91+ missingPlatformDeclaration : 'File must export __supportedPlatforms to declare which platforms it supports. Example: export const __supportedPlatforms = [\'__universal__\'];' ,
92+ invalidPlatformDeclaration : '__supportedPlatforms must be exported as a const array. Example: export const __supportedPlatforms = [\'browser\', \'node\'];' ,
93+ invalidPlatformValue : '__supportedPlatforms contains invalid platform value "{{value}}". Valid platforms are: {{validPlatforms}}' ,
94+ emptyPlatformArray : '__supportedPlatforms array cannot be empty. Specify at least one platform or use [\'__universal__\']' ,
2695 } ,
2796 schema : [ ] ,
2897 } ,
@@ -49,8 +118,8 @@ module.exports = {
49118 return { } ;
50119 }
51120
121+ const VALID_PLATFORMS = getValidPlatforms ( context ) ;
52122 let hasPlatformExport = false ;
53- let isValidExport = false ;
54123
55124 return {
56125 ExportNamedDeclaration ( node ) {
@@ -73,7 +142,7 @@ module.exports = {
73142 return ;
74143 }
75144
76- // Validate it's an array
145+ // Validate it's an array expression
77146 let init = declarator . init ;
78147
79148 // Handle TSAsExpression: [...] as const
@@ -86,13 +155,43 @@ module.exports = {
86155 init = init . expression ;
87156 }
88157
89- if ( init && init . type === 'ArrayExpression' ) {
90- isValidExport = true ;
91- } else {
158+ if ( ! init || init . type !== 'ArrayExpression' ) {
92159 context . report ( {
93160 node : declarator ,
94161 messageId : 'invalidPlatformDeclaration' ,
95162 } ) ;
163+ return ;
164+ }
165+
166+ // Check if array is empty
167+ if ( init . elements . length === 0 ) {
168+ context . report ( {
169+ node : init ,
170+ messageId : 'emptyPlatformArray' ,
171+ } ) ;
172+ return ;
173+ }
174+
175+ // Validate each array element is a valid platform string
176+ for ( const element of init . elements ) {
177+ if ( element && element . type === 'Literal' && typeof element . value === 'string' ) {
178+ if ( ! VALID_PLATFORMS . includes ( element . value ) ) {
179+ context . report ( {
180+ node : element ,
181+ messageId : 'invalidPlatformValue' ,
182+ data : {
183+ value : element . value ,
184+ validPlatforms : VALID_PLATFORMS . map ( p => `'${ p } '` ) . join ( ', ' )
185+ }
186+ } ) ;
187+ }
188+ } else {
189+ // Not a string literal
190+ context . report ( {
191+ node : element || init ,
192+ messageId : 'invalidPlatformDeclaration' ,
193+ } ) ;
194+ }
96195 }
97196 }
98197 }
0 commit comments