@@ -33,12 +33,12 @@ const SCOPED = /^(?::where\(html\.dark\) )?(?:\[data-platform-ui\]|:where\(\[dat
3333const GLOBAL_TARGET = / (?: ^ | [ \s > + ~ ( , ] ) (?: : r o o t | : h o s t | h t m l | b o d y ) (? ! [ \w - ] ) /
3434const ENTRY_AT_RULES = new Set ( [ 'media' , 'container' , 'supports' , 'font-face' ] )
3535const MODULE_AT_RULES = new Set ( [ 'media' , 'container' , 'supports' , 'keyframes' ] )
36- const KEYFRAMES = / k e y f r a m e s $ /
3736const ORGANIZATION_ROUTES = path . join ( 'app' , 'o' , '[organizationId]' ) + path . sep
3837const NEXT_ENTRY =
3938 / ^ (?: p a g e | l a y o u t | t e m p l a t e | l o a d i n g | e r r o r | g l o b a l - e r r o r | n o t - f o u n d | d e f a u l t | r o u t e ) \. [ j t ] s x ? $ /
4039const ROOT_ENTRIES = [ 'proxy.ts' , 'instrumentation.ts' , 'instrumentation-node.ts' , 'bootstrap.ts' ]
41- const SOURCE = / \. (?: [ j t ] s x ? | c s s ) $ /
40+ const CODE_FILE = / \. [ j t ] s x ? $ /
41+ const CSS_MODULE = / \. m o d u l e \. c s s $ /
4242const TEST_FILE = / \. (?: t e s t | s p e c ) \. [ j t ] s x ? $ | \. i n t e g r a t i o n \. t s $ /
4343const SKIPPED_FOLDERS = new Set ( [ 'node_modules' , '.next' , 'dist' , 'public' ] )
4444const RESOLVE_SUFFIXES = [ '' , '.ts' , '.tsx' , '.js' , '.jsx' , '.cjs' , '/index.ts' , '/index.tsx' ]
@@ -48,11 +48,6 @@ export interface Finding {
4848 detail : string
4949}
5050
51- function isInsideKeyframes ( node : { parent ?: unknown } ) : boolean {
52- const parent = node . parent as AtRule | undefined
53- return parent ?. type === 'atrule' && KEYFRAMES . test ( parent . name )
54- }
55-
5651/** Checks compiled entry CSS. `hostGlobals` is the host stylesheet a new font face must not alter. */
5752export function findEntryEscapes ( css : string , hostGlobals : string ) : Finding [ ] {
5853 const findings : Finding [ ] = [ ]
@@ -93,8 +88,8 @@ export function findModuleEscapes(css: string, file: string): Finding[] {
9388 }
9489 } )
9590 root . walkRules ( ( rule ) => {
96- if ( isInsideKeyframes ( rule ) ) return
97- if ( rule . parent ?. type === 'rule' ) return
91+ const { parent } = rule
92+ if ( parent ?. type === 'rule' || ( parent as AtRule | undefined ) ?. name === 'keyframes ') return
9893 for ( const selector of rule . selectors ) {
9994 if ( selector . includes ( ':global' ) || ! / \. - ? [ _ a - z A - Z ] / . test ( selector ) ) {
10095 findings . push ( { rule : 'module-escape' , detail : `${ file } : ${ selector } ` } )
@@ -104,12 +99,12 @@ export function findModuleEscapes(css: string, file: string): Finding[] {
10499 return findings
105100}
106101
107- function walk ( directory : string , files : string [ ] = [ ] ) : string [ ] {
108- for ( const name of readdirSync ( directory ) ) {
109- if ( SKIPPED_FOLDERS . has ( name ) ) continue
110- const full = path . join ( directory , name )
111- if ( statSync ( full ) . isDirectory ( ) ) walk ( full , files )
112- else if ( SOURCE . test ( name ) && ! TEST_FILE . test ( name ) ) files . push ( full )
102+ function walk ( directory : string , pattern : RegExp , files : string [ ] = [ ] ) : string [ ] {
103+ for ( const entry of readdirSync ( directory , { withFileTypes : true } ) ) {
104+ if ( SKIPPED_FOLDERS . has ( entry . name ) ) continue
105+ const full = path . join ( directory , entry . name )
106+ if ( entry . isDirectory ( ) ) walk ( full , pattern , files )
107+ else if ( pattern . test ( entry . name ) && ! TEST_FILE . test ( entry . name ) ) files . push ( full )
113108 }
114109 return files
115110}
@@ -123,35 +118,33 @@ function resolveSpecifier(specifier: string, from: string): string | null {
123118 if ( ! base ) return null
124119 for ( const suffix of RESOLVE_SUFFIXES ) {
125120 const candidate = `${ base } ${ suffix } `
126- try {
127- if ( statSync ( candidate ) . isFile ( ) ) return candidate
128- } catch { }
121+ if ( statSync ( candidate , { throwIfNoEntry : false } ) ?. isFile ( ) ) return candidate
129122 }
130123 return null
131124}
132125
133- function readImports ( file : string ) : string [ ] {
134- if ( file . endsWith ( '.css' ) ) return [ ]
135- return new Bun . Transpiler ( { loader : file . endsWith ( 'x' ) ? 'tsx' : 'ts' } )
136- . scan ( readFileSync ( file , 'utf8' ) )
137- . imports . flatMap ( ( { path : specifier } ) => resolveSpecifier ( specifier , file ) ?? [ ] )
138- }
139-
140126/** Walks importers back from `platform/`, so each module is visited once however the graph cycles. */
141127function findRouteReach ( ) : Finding [ ] {
142128 const insidePlatform = ( file : string ) => file . startsWith ( `${ PLATFORM } ${ path . sep } ` )
143- const files = walk ( APP ) . filter ( ( file ) => ! insidePlatform ( file ) )
129+ const files = walk ( APP , CODE_FILE ) . filter ( ( file ) => ! insidePlatform ( file ) )
130+ const tsTranspiler = new Bun . Transpiler ( { loader : 'ts' } )
131+ const tsxTranspiler = new Bun . Transpiler ( { loader : 'tsx' } )
144132 const importers = new Map < string , string [ ] > ( )
145133 const queue : string [ ] = [ ]
146134 /** Maps a module that reaches `platform/` to its next step on the way there. */
147135 const nextStep = new Map < string , string > ( )
148136 for ( const file of files ) {
149- for ( const target of readImports ( file ) ) {
137+ const transpiler = file . endsWith ( 'x' ) ? tsxTranspiler : tsTranspiler
138+ for ( const { path : specifier } of transpiler . scan ( readFileSync ( file , 'utf8' ) ) . imports ) {
139+ const target = resolveSpecifier ( specifier , file )
140+ if ( ! target ) continue
150141 if ( insidePlatform ( target ) ) {
151142 if ( ! nextStep . has ( file ) ) queue . push ( file )
152143 nextStep . set ( file , target )
144+ } else if ( importers . has ( target ) ) {
145+ importers . get ( target ) ?. push ( file )
153146 } else {
154- importers . set ( target , [ ... ( importers . get ( target ) ?? [ ] ) , file ] )
147+ importers . set ( target , [ file ] )
155148 }
156149 }
157150 }
@@ -200,7 +193,7 @@ if (import.meta.main) {
200193 } )
201194 const findings = findEntryEscapes ( entryCss , readFileSync ( HOST_GLOBALS , 'utf8' ) )
202195
203- const modules = walk ( PLATFORM ) . filter ( ( file ) => file . endsWith ( '.module.css' ) )
196+ const modules = walk ( PLATFORM , CSS_MODULE )
204197 for ( const file of modules ) {
205198 findings . push ( ...findModuleEscapes ( await compile ( file ) , path . relative ( APP , file ) ) )
206199 }
0 commit comments