@@ -24,6 +24,7 @@ const TS_NODE_TYPES = [
2424 * @typedef {import("estree").ReturnStatement } ReturnStatement
2525 * @typedef {import("estree").ContinueStatement } ContinueStatement
2626 * @typedef {import("estree").BreakStatement } BreakStatement
27+ * @typedef {import("estree").IfStatement } IfStatement
2728 * @typedef {import("estree").Node } Node
2829 * @typedef {import("eslint").SourceCode } SourceCode
2930 * @typedef {import("eslint").Rule.RuleContext } RuleContext
@@ -50,7 +51,7 @@ module.exports = { createPropertyGuardsContext }
5051/**
5152 * @typedef {object } GuardChecker
5253 * @property {(node: MemberExpression|Property)=>boolean } test
53- * @property {"instanceof"|"definedValue"|"definedType"|"hasValue"|"unknown" } kind
54+ * @property {"instanceof"|"definedValue"|"definedType"|"hasValue"|"optional"|" unknown" } kind
5455 */
5556/**
5657 * @typedef {object } MaybeGuard
@@ -251,6 +252,15 @@ function createPropertyGuardsContext(options) {
251252 }
252253 return null
253254 }
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+ }
254264
255265 if (
256266 propertyTypes . every (
@@ -289,36 +299,54 @@ function createPropertyGuardsContext(options) {
289299 return getGuardCheckerForExpression ( parent , { not : ! not } )
290300 }
291301 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 ]
318314 }
319315 return null
320316 }
321317
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+
322350 /**
323351 * @param {MemberExpression|Property } node
324352 * @returns {GuardChecker|null } The guard checker.
@@ -421,7 +449,10 @@ function createPropertyGuardsContext(options) {
421449 const guard = {
422450 ...params ,
423451 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" ,
425456 isAvailableLocation : checker . test ,
426457 }
427458 let classGuards = maybeGuards . get ( params . className )
0 commit comments