@@ -18,6 +18,8 @@ const BABEL_CONFIGS = [
1818 ".babelrc.cjs" ,
1919] ;
2020
21+ export const GLOBALS = [ "window" , "globalThis" ] ;
22+
2123/*
22243) Figures out which browsers user is targeting
2325
@@ -38,9 +40,7 @@ export interface GuardedScope {
3840 * scope that the guard applies to and after which index it applies.
3941 * Should be called with either a bare Identifier or a MemberExpression.
4042 */
41- export function determineGuardedScope (
42- node : ( ESTree . Identifier | ESTree . MemberExpression ) & Rule . NodeParentExtension
43- ) : GuardedScope | null {
43+ export function determineGuardedScope ( node : Rule . Node ) : GuardedScope | null {
4444 const result = getIfStatementAndGuardType ( node ) ;
4545 if ( ! result ) return null ;
4646
@@ -71,15 +71,12 @@ export function isBlockOrProgram(
7171}
7272
7373function getIfStatementAndGuardType (
74- node : ( ESTree . Identifier | ESTree . MemberExpression ) & Rule . NodeParentExtension
74+ node : Rule . Node
7575) : [ ESTree . IfStatement & Rule . NodeParentExtension , boolean ] | null {
7676 let positiveGuard = true ;
7777 let expression : Rule . Node = node ;
7878
79- if (
80- node . parent ?. type === "UnaryExpression" &&
81- node . parent . operator === "typeof"
82- ) {
79+ if ( isUnaryExpression ( node . parent , "typeof" ) ) {
8380 // unused typeof check
8481 if ( node . parent . parent ?. type !== "BinaryExpression" ) return null ;
8582
@@ -105,16 +102,21 @@ function getIfStatementAndGuardType(
105102 // typepf foo === "undefined"
106103 positiveGuard = false ;
107104 }
105+ } else if ( isBinaryExpression ( expression . parent , "in" ) ) {
106+ expression = expression . parent ;
107+ } else if ( isBinaryExpression ( expression . parent ) ) {
108+ if (
109+ expression . parent . right . type === "Identifier" &&
110+ expression . parent . right . name === "undefined"
111+ ) {
112+ }
108113 }
109114
110115 // !window.fetch
111116 // !!window.fetch
112117 // !!!!!!window.fetch
113118 // !(typeof fetch === "undefined")
114- while (
115- expression . parent ?. type === "UnaryExpression" &&
116- expression . parent . operator === "!"
117- ) {
119+ while ( isUnaryExpression ( expression . parent , "!" ) ) {
118120 expression = expression . parent ;
119121 positiveGuard = ! positiveGuard ;
120122 }
@@ -147,20 +149,37 @@ function ifStatementHasEarlyReturn(
147149 ) ;
148150}
149151
150- export function isInsideTypeofCheck (
151- node : ESTree . Identifier & Rule . NodeParentExtension
152- ) {
152+ export function isInsideTypeofCheck ( node : Rule . Node ) {
153153 return (
154154 node . parent . type === "UnaryExpression" && node . parent . operator === "typeof"
155155 ) ;
156156}
157157
158158function isStringLiteral (
159- node : ESTree . Node
160- ) : node is ESTree . SimpleLiteral & { value : string } {
159+ node : Rule . Node
160+ ) : node is ESTree . SimpleLiteral & { value : string } & Rule . NodeParentExtension {
161161 return node . type === "Literal" && typeof node . value === "string" ;
162162}
163163
164+ function isBinaryExpression (
165+ node : Rule . Node ,
166+ operator ?: string
167+ ) : node is ESTree . BinaryExpression & Rule . NodeParentExtension {
168+ return (
169+ node . type === "BinaryExpression" &&
170+ ( ! operator || node . operator === operator )
171+ ) ;
172+ }
173+
174+ function isUnaryExpression (
175+ node : Rule . Node ,
176+ operator ?: string
177+ ) : node is ESTree . UnaryExpression & Rule . NodeParentExtension {
178+ return (
179+ node . type === "UnaryExpression" && ( ! operator || node . operator === operator )
180+ ) ;
181+ }
182+
164183export function reverseTargetMappings < K extends string , V extends string > (
165184 targetMappings : Record < K , V >
166185) : Record < V , K > {
@@ -170,6 +189,90 @@ export function reverseTargetMappings<K extends string, V extends string>(
170189 return Object . fromEntries ( reversedEntries ) ;
171190}
172191
192+ export function topmostIdentifierOrMemberExpression (
193+ node : Rule . Node
194+ ) :
195+ | ( ( ESTree . Identifier | ESTree . MemberExpression ) & Rule . NodeParentExtension )
196+ | null {
197+ switch ( node . type ) {
198+ case "Identifier" :
199+ case "MemberExpression" : {
200+ if ( node . parent . type !== "MemberExpression" ) return node ;
201+
202+ let expression = node . parent ;
203+ while ( expression . parent . type === "MemberExpression" ) {
204+ expression = expression . parent ;
205+ }
206+
207+ return expression ;
208+ }
209+
210+ default :
211+ return null ;
212+ }
213+ }
214+
215+ interface IdentifierProtoChain {
216+ protoChain : string [ ] ;
217+ expression : Rule . Node ;
218+ }
219+
220+ export function identifierProtoChain (
221+ node : ESTree . Identifier & Rule . NodeParentExtension
222+ ) : null | IdentifierProtoChain {
223+ const result = identifierProtoChainHelper ( node ) ;
224+ if ( ! result ) return null ;
225+
226+ const { expression, protoChain } = result ;
227+
228+ if (
229+ isBinaryExpression ( expression , "in" ) &&
230+ isStringLiteral ( expression . left )
231+ ) {
232+ // e.g. `if ("fetch" in window) {}`
233+ protoChain . push ( expression . left . value ) ;
234+ }
235+
236+ return { expression, protoChain } ;
237+ }
238+
239+ /**
240+ * Returns an array of property names from the given identifier, without any leading
241+ * window or globalThis.
242+ */
243+ function identifierProtoChainHelper (
244+ node : ESTree . Identifier & Rule . NodeParentExtension
245+ ) : null | IdentifierProtoChain {
246+ const expression = topmostIdentifierOrMemberExpression ( node ) ;
247+ if ( ! expression ) return null ;
248+
249+ const protoChain : string [ ] = [ ] ;
250+
251+ const protoChainFromMemberExpressionObject = ( obj : ESTree . Node ) => {
252+ if ( obj . type === "Identifier" ) {
253+ protoChain . push ( obj . name ) ;
254+ return true ;
255+ }
256+
257+ if ( obj . type === "MemberExpression" ) {
258+ if ( obj . property . type !== "Identifier" ) return false ;
259+ if ( ! protoChainFromMemberExpressionObject ( obj . object ) ) return false ;
260+ protoChain . push ( obj . property . name ) ;
261+ return true ;
262+ }
263+
264+ return false ;
265+ } ;
266+
267+ if ( ! protoChainFromMemberExpressionObject ( expression ) ) return null ;
268+
269+ if ( GLOBALS . includes ( protoChain [ 0 ] ) ) {
270+ protoChain . shift ( ) ;
271+ }
272+
273+ return { expression, protoChain } ;
274+ }
275+
173276/**
174277 * Determine the settings to run this plugin with, including the browserslist targets and
175278 * whether to lint all ES APIs.
0 commit comments