@@ -26,105 +26,51 @@ export default createRule('no-top-level-browser-globals', {
26
26
}
27
27
const blowerGlobals = getBrowserGlobals ( ) ;
28
28
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
+ } ) ;
30
33
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 [ ] = [ ] ;
39
44
40
45
function enterFunction ( node : TSESTree . FunctionLike ) {
41
46
if ( isTopLevelLocation ( node ) ) {
42
47
functions . push ( node ) ;
43
48
}
44
49
}
45
50
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
72
58
}
73
59
}
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
+ } ) ;
108
68
}
109
69
}
110
70
}
71
+ }
111
72
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 ( ) {
128
74
// Collects guarded location checkers by checking module references
129
75
// that can check the browser environment.
130
76
for ( const referenceNode of iterateBrowserCheckerModuleReferences ( ) ) {
@@ -171,9 +117,90 @@ export default createRule('no-top-level-browser-globals', {
171
117
172
118
return {
173
119
':function' : enterFunction ,
120
+ MetaProperty : enterMetaProperty ,
174
121
'Program:exit' : verifyGlobalReferences
175
122
} ;
176
123
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
+
177
204
/**
178
205
* If the node is a reference used in a guard clause that checks if the node is in a browser environment,
179
206
* it returns information about the expression that checks if the browser variable is available.
0 commit comments