@@ -26,105 +26,51 @@ export default createRule('no-top-level-browser-globals', {
2626 }
2727 const blowerGlobals = getBrowserGlobals ( ) ;
2828
29- const functions : TSESTree . FunctionLike [ ] = [ ] ;
29+ const referenceTracker = new ReferenceTracker ( sourceCode . scopeManager . globalScope ! , {
30+ // Specifies the global variables that are allowed to prevent `window.window` from being iterated over.
31+ globalObjectNames : [ 'globalThis' ]
32+ } ) ;
3033
31- function isTopLevelLocation ( node : TSESTree . Node ) {
32- for ( const func of functions ) {
33- if ( func . range [ 0 ] <= node . range [ 0 ] && node . range [ 1 ] <= func . range [ 1 ] ) {
34- return false ;
35- }
36- }
37- return true ;
38- }
34+ type MaybeGuard = {
35+ reference ?: { node : TSESTree . Node ; name : string } ;
36+ isAvailableLocation : ( node : TSESTree . Node ) => boolean ;
37+ // The guard that checks whether the browser environment is set to true.
38+ browserEnvironment : boolean ;
39+ used ?: boolean ;
40+ } ;
41+ const maybeGuards : MaybeGuard [ ] = [ ] ;
42+
43+ const functions : TSESTree . FunctionLike [ ] = [ ] ;
3944
4045 function enterFunction ( node : TSESTree . FunctionLike ) {
4146 if ( isTopLevelLocation ( node ) ) {
4247 functions . push ( node ) ;
4348 }
4449 }
4550
46- function verifyGlobalReferences ( ) {
47- const referenceTracker = new ReferenceTracker ( sourceCode . scopeManager . globalScope ! , {
48- // Specifies the global variables that are allowed to prevent `window.window` from being iterated over.
49- globalObjectNames : [ 'globalThis' ]
50- } ) ;
51-
52- type MaybeGuard = {
53- reference ?: { node : TSESTree . Node ; name : string } ;
54- isAvailableLocation : ( node : TSESTree . Node ) => boolean ;
55- // The guard that checks whether the browser environment is set to true.
56- browserEnvironment : boolean ;
57- used ?: boolean ;
58- } ;
59- const maybeGuards : MaybeGuard [ ] = [ ] ;
60-
61- /**
62- * Checks whether the node is in a location where the expression is available or not.
63- * @returns `true` if the expression is available.
64- */
65- function isAvailableLocation ( ref : { node : TSESTree . Node ; name : string } ) {
66- for ( const guard of maybeGuards . reverse ( ) ) {
67- if ( guard . isAvailableLocation ( ref . node ) ) {
68- if ( guard . browserEnvironment || guard . reference ?. name === ref . name ) {
69- guard . used = true ;
70- return true ;
71- }
51+ function enterMetaProperty ( node : TSESTree . MetaProperty ) {
52+ if ( node . meta . name !== 'import' || node . property . name !== 'meta' ) return ;
53+ for ( const ref of referenceTracker . iteratePropertyReferences ( node , {
54+ env : {
55+ // See https://vite.dev/guide/ssr#conditional-logic
56+ SSR : {
57+ [ ReferenceTracker . READ ] : true
7258 }
7359 }
74- return false ;
75- }
76-
77- /**
78- * Iterate over the references of modules that can check the browser environment.
79- */
80- function * iterateBrowserCheckerModuleReferences ( ) : Iterable < TSESTree . Expression > {
81- for ( const ref of referenceTracker . iterateEsmReferences ( {
82- 'esm-env' : {
83- [ ReferenceTracker . ESM ] : true ,
84- // See https://www.npmjs.com/package/esm-env
85- BROWSER : {
86- [ ReferenceTracker . READ ] : true
87- }
88- } ,
89- '$app/environment' : {
90- [ ReferenceTracker . ESM ] : true ,
91- // See https://svelte.dev/docs/kit/$app-environment#browser
92- browser : {
93- [ ReferenceTracker . READ ] : true
94- }
95- }
96- } ) ) {
97- if ( ref . node . type === 'Identifier' || ref . node . type === 'MemberExpression' ) {
98- yield ref . node ;
99- } else if ( ref . node . type === 'ImportSpecifier' ) {
100- const variable = findVariable ( context , ref . node . local ) ;
101- if ( variable ) {
102- for ( const reference of variable . references ) {
103- if ( reference . isRead ( ) && reference . identifier . type === 'Identifier' ) {
104- yield reference . identifier ;
105- }
106- }
107- }
60+ } ) ) {
61+ if ( ref . node . type === 'Identifier' || ref . node . type === 'MemberExpression' ) {
62+ const guardChecker = getGuardChecker ( { node : ref . node , not : true } ) ;
63+ if ( guardChecker ) {
64+ maybeGuards . push ( {
65+ isAvailableLocation : guardChecker ,
66+ browserEnvironment : true
67+ } ) ;
10868 }
10969 }
11070 }
71+ }
11172
112- /**
113- * Iterate over the used references of global variables.
114- */
115- function * iterateBrowserGlobalReferences ( ) : Iterable < TrackedReferences < unknown > > {
116- yield * referenceTracker . iterateGlobalReferences (
117- Object . fromEntries (
118- blowerGlobals . map ( ( name ) => [
119- name ,
120- {
121- [ ReferenceTracker . READ ] : true
122- }
123- ] )
124- )
125- ) ;
126- }
127-
73+ function verifyGlobalReferences ( ) {
12874 // Collects guarded location checkers by checking module references
12975 // that can check the browser environment.
13076 for ( const referenceNode of iterateBrowserCheckerModuleReferences ( ) ) {
@@ -171,9 +117,90 @@ export default createRule('no-top-level-browser-globals', {
171117
172118 return {
173119 ':function' : enterFunction ,
120+ MetaProperty : enterMetaProperty ,
174121 'Program:exit' : verifyGlobalReferences
175122 } ;
176123
124+ /**
125+ * Checks whether the node is in a location where the expression is available or not.
126+ * @returns `true` if the expression is available.
127+ */
128+ function isAvailableLocation ( ref : { node : TSESTree . Node ; name : string } ) {
129+ for ( const guard of maybeGuards . reverse ( ) ) {
130+ if ( guard . isAvailableLocation ( ref . node ) ) {
131+ if ( guard . browserEnvironment || guard . reference ?. name === ref . name ) {
132+ guard . used = true ;
133+ return true ;
134+ }
135+ }
136+ }
137+ return false ;
138+ }
139+
140+ /**
141+ * Checks whether the node is in a top-level location.
142+ * @returns `true` if the node is in a top-level location.
143+ */
144+ function isTopLevelLocation ( node : TSESTree . Node ) {
145+ for ( const func of functions ) {
146+ if ( func . range [ 0 ] <= node . range [ 0 ] && node . range [ 1 ] <= func . range [ 1 ] ) {
147+ return false ;
148+ }
149+ }
150+ return true ;
151+ }
152+
153+ /**
154+ * Iterate over the references of modules that can check the browser environment.
155+ */
156+ function * iterateBrowserCheckerModuleReferences ( ) : Iterable < TSESTree . Expression > {
157+ for ( const ref of referenceTracker . iterateEsmReferences ( {
158+ 'esm-env' : {
159+ [ ReferenceTracker . ESM ] : true ,
160+ // See https://www.npmjs.com/package/esm-env
161+ BROWSER : {
162+ [ ReferenceTracker . READ ] : true
163+ }
164+ } ,
165+ '$app/environment' : {
166+ [ ReferenceTracker . ESM ] : true ,
167+ // See https://svelte.dev/docs/kit/$app-environment#browser
168+ browser : {
169+ [ ReferenceTracker . READ ] : true
170+ }
171+ }
172+ } ) ) {
173+ if ( ref . node . type === 'Identifier' || ref . node . type === 'MemberExpression' ) {
174+ yield ref . node ;
175+ } else if ( ref . node . type === 'ImportSpecifier' ) {
176+ const variable = findVariable ( context , ref . node . local ) ;
177+ if ( variable ) {
178+ for ( const reference of variable . references ) {
179+ if ( reference . isRead ( ) && reference . identifier . type === 'Identifier' ) {
180+ yield reference . identifier ;
181+ }
182+ }
183+ }
184+ }
185+ }
186+ }
187+
188+ /**
189+ * Iterate over the used references of global variables.
190+ */
191+ function * iterateBrowserGlobalReferences ( ) : Iterable < TrackedReferences < unknown > > {
192+ yield * referenceTracker . iterateGlobalReferences (
193+ Object . fromEntries (
194+ blowerGlobals . map ( ( name ) => [
195+ name ,
196+ {
197+ [ ReferenceTracker . READ ] : true
198+ }
199+ ] )
200+ )
201+ ) ;
202+ }
203+
177204 /**
178205 * If the node is a reference used in a guard clause that checks if the node is in a browser environment,
179206 * it returns information about the expression that checks if the browser variable is available.
0 commit comments