@@ -24,6 +24,7 @@ const TS_NODE_TYPES = [
24
24
* @typedef {import("estree").ReturnStatement } ReturnStatement
25
25
* @typedef {import("estree").ContinueStatement } ContinueStatement
26
26
* @typedef {import("estree").BreakStatement } BreakStatement
27
+ * @typedef {import("estree").IfStatement } IfStatement
27
28
* @typedef {import("estree").Node } Node
28
29
* @typedef {import("eslint").SourceCode } SourceCode
29
30
* @typedef {import("eslint").Rule.RuleContext } RuleContext
@@ -50,7 +51,7 @@ module.exports = { createPropertyGuardsContext }
50
51
/**
51
52
* @typedef {object } GuardChecker
52
53
* @property {(node: MemberExpression|Property)=>boolean } test
53
- * @property {"instanceof"|"definedValue"|"definedType"|"hasValue"|"unknown" } kind
54
+ * @property {"instanceof"|"definedValue"|"definedType"|"hasValue"|"optional"|" unknown" } kind
54
55
*/
55
56
/**
56
57
* @typedef {object } MaybeGuard
@@ -251,6 +252,15 @@ function createPropertyGuardsContext(options) {
251
252
}
252
253
return null
253
254
}
255
+ if (
256
+ ( ( parent . type === "CallExpression" && parent . callee === node ) ||
257
+ ( parent . type === "MemberExpression" &&
258
+ parent . object === node ) ) &&
259
+ parent . optional
260
+ ) {
261
+ // e.g. x.property?.()
262
+ return { test : ( n ) => n === node , kind : "optional" }
263
+ }
254
264
255
265
if (
256
266
propertyTypes . every (
@@ -289,36 +299,54 @@ function createPropertyGuardsContext(options) {
289
299
return getGuardCheckerForExpression ( parent , { not : ! not } )
290
300
}
291
301
if ( parent . type === "IfStatement" && parent . test === node ) {
292
- if ( ! not ) {
293
- const block = parent . consequent
294
- return ( n ) =>
295
- block . range [ 0 ] <= n . range [ 0 ] && n . range [ 1 ] <= block . range [ 1 ]
296
- }
297
- // e.g. if (typeof x.property === 'undefined')
298
- if ( parent . alternate ) {
299
- const block = parent . alternate
300
- return ( n ) =>
301
- block . range [ 0 ] <= n . range [ 0 ] && n . range [ 1 ] <= block . range [ 1 ]
302
- }
303
- if ( ! hasJumpStatementInAllPath ( parent . consequent ) ) {
304
- return null
305
- }
306
- /** @type {Node|null } */
307
- const pp = getParent ( parent )
308
- if (
309
- ! pp ||
310
- ( pp . type !== "BlockStatement" && pp . type !== "Program" )
311
- ) {
312
- return null
313
- }
314
- const start = parent . range [ 1 ]
315
- const end = pp . range [ 1 ]
316
-
317
- return ( n ) => start <= n . range [ 0 ] && n . range [ 1 ] <= end
302
+ return getGuardCheckerForIfStatement ( parent , { not } )
303
+ }
304
+ if (
305
+ ! not &&
306
+ parent . type === "LogicalExpression" &&
307
+ parent . operator === "&&" &&
308
+ parent . left === node
309
+ ) {
310
+ // e.g. typeof x.property !== 'undefined' && x.property
311
+ const block = parent . right
312
+ return ( n ) =>
313
+ block . range [ 0 ] <= n . range [ 0 ] && n . range [ 1 ] <= block . range [ 1 ]
318
314
}
319
315
return null
320
316
}
321
317
318
+ /**
319
+ * @param {IfStatement } node
320
+ * @returns {((node: MemberExpression|Property)=>boolean)|null } The guard tester.
321
+ */
322
+ function getGuardCheckerForIfStatement ( node , { not = false } = { } ) {
323
+ if ( ! not ) {
324
+ const block = node . consequent
325
+ return ( n ) =>
326
+ block . range [ 0 ] <= n . range [ 0 ] && n . range [ 1 ] <= block . range [ 1 ]
327
+ }
328
+ if ( node . alternate ) {
329
+ const block = node . alternate
330
+ return ( n ) =>
331
+ block . range [ 0 ] <= n . range [ 0 ] && n . range [ 1 ] <= block . range [ 1 ]
332
+ }
333
+ if ( ! hasJumpStatementInAllPath ( node . consequent ) ) {
334
+ return null
335
+ }
336
+ /** @type {Node|null } */
337
+ const parent = getParent ( node )
338
+ if (
339
+ ! parent ||
340
+ ( parent . type !== "BlockStatement" && parent . type !== "Program" )
341
+ ) {
342
+ return null
343
+ }
344
+ const start = node . range [ 1 ]
345
+ const end = parent . range [ 1 ]
346
+
347
+ return ( n ) => start <= n . range [ 0 ] && n . range [ 1 ] <= end
348
+ }
349
+
322
350
/**
323
351
* @param {MemberExpression|Property } node
324
352
* @returns {GuardChecker|null } The guard checker.
@@ -421,7 +449,10 @@ function createPropertyGuardsContext(options) {
421
449
const guard = {
422
450
...params ,
423
451
prototypeGuard : isPrototypePropertyAccess ( params . node ) ,
424
- used : false ,
452
+ used :
453
+ // A optional chain allows the property access expression of its own.
454
+ // but we mark expressions that are candidates for guarding as used here because we won't check them later.
455
+ checker . kind === "optional" ,
425
456
isAvailableLocation : checker . test ,
426
457
}
427
458
let classGuards = maybeGuards . get ( params . className )
0 commit comments