@@ -617,6 +617,42 @@ func (t *TypeInfo) HasNoNullableTypes() bool {
617617 return ! t .hasNull && ! t .hasUndefined && ! t .hasAny && ! t .hasUnknown
618618}
619619
620+ func (t * TypeInfo ) CanBeUndefinedLike () bool {
621+ return len (t .parts ) == 0 || t .hasUndefined || t .hasVoid || t .hasAny || t .hasUnknown
622+ }
623+
624+ func (t * TypeInfo ) CanBeNullishLike () bool {
625+ return len (t .parts ) == 0 || t .hasNull || t .hasUndefined || t .hasVoid || t .hasAny || t .hasUnknown
626+ }
627+
628+ func (t * TypeInfo ) IsAlwaysUndefinedLike () bool {
629+ if len (t .parts ) == 0 || t .IsAnyOrUnknown () {
630+ return false
631+ }
632+
633+ for _ , part := range t .parts {
634+ if ! utils .IsTypeUndefinedType (part ) && ! utils .IsTypeVoidType (part ) {
635+ return false
636+ }
637+ }
638+
639+ return true
640+ }
641+
642+ func (t * TypeInfo ) IsAlwaysNullishLike () bool {
643+ if len (t .parts ) == 0 || t .IsAnyOrUnknown () {
644+ return false
645+ }
646+
647+ for _ , part := range t .parts {
648+ if ! utils .IsTypeNullType (part ) && ! utils .IsTypeUndefinedType (part ) && ! utils .IsTypeVoidType (part ) {
649+ return false
650+ }
651+ }
652+
653+ return true
654+ }
655+
620656type chainProcessor struct {
621657 ctx rule.RuleContext
622658 opts PreferOptionalChainOptions
@@ -2302,63 +2338,58 @@ func (processor *chainProcessor) isUnsafeTrailingComparison(chain []Operand, las
23022338 if ast .IsBinaryExpression (unwrappedNode ) {
23032339 binExpr := unwrappedNode .AsBinaryExpression ()
23042340 op := binExpr .OperatorToken .Kind
2341+ left := ast .SkipParentheses (binExpr .Left )
2342+ right := ast .SkipParentheses (binExpr .Right )
23052343
23062344 var value * ast.Node
2307- if utils .IsAccessExpression (binExpr .Left ) {
2308- value = binExpr .Right
2309- } else if utils .IsAccessExpression (binExpr .Right ) {
2310- value = binExpr .Left
2311- }
2312-
2313- if value != nil {
2314- isNull := utils .IsNullLiteral (value )
2315- isUndefined := utils .IsUndefinedLiteral (value )
2316- isNullish := isNull || isUndefined
2317- isLiteral := value .Kind == ast .KindNumericLiteral ||
2318- value .Kind == ast .KindStringLiteral ||
2319- value .Kind == ast .KindTrueKeyword ||
2320- value .Kind == ast .KindFalseKeyword ||
2321- value .Kind == ast .KindObjectLiteralExpression ||
2322- value .Kind == ast .KindArrayLiteralExpression
2323- isUndeclaredVar := ast .IsIdentifier (value ) && ! isUndefined && ! isLiteral
2324-
2325- unsafe := false
2326- switch op {
2327- case ast .KindEqualsEqualsToken :
2328- if isNullish || isUndeclaredVar {
2329- unsafe = true
2330- }
2331- case ast .KindEqualsEqualsEqualsToken :
2332- if isUndefined || isUndeclaredVar {
2333- unsafe = true
2334- }
2335- case ast .KindExclamationEqualsToken :
2336- if ! isNullish {
2337- unsafe = true
2338- }
2339- case ast .KindExclamationEqualsEqualsToken :
2340- if ! isUndefined {
2341- unsafe = true
2342- }
2343- // Relational operators are unsafe because undefined/null comparisons
2344- // produce unexpected results
2345- case ast .KindLessThanToken ,
2346- ast .KindGreaterThanToken ,
2347- ast .KindLessThanEqualsToken ,
2348- ast .KindGreaterThanEqualsToken :
2349- unsafe = true
2350- }
2351-
2352- if unsafe && ! processor .opts .AllowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing {
2353- return true
2354- }
2345+ switch {
2346+ case lastOp .comparedExpr != nil && areNodesStructurallyEqual (lastOp .comparedExpr , left ):
2347+ value = right
2348+ case lastOp .comparedExpr != nil && areNodesStructurallyEqual (lastOp .comparedExpr , right ):
2349+ value = left
2350+ case utils .IsAccessExpression (left ):
2351+ value = right
2352+ case utils .IsAccessExpression (right ):
2353+ value = left
2354+ }
2355+
2356+ if value != nil &&
2357+ ! processor .isSafeTrailingComparisonValue (op , value ) &&
2358+ ! processor .opts .AllowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing {
2359+ return true
23552360 }
23562361 }
23572362 }
23582363
23592364 return false
23602365}
23612366
2367+ func (processor * chainProcessor ) isSafeTrailingComparisonValue (operator ast.Kind , value * ast.Node ) bool {
2368+ if value == nil {
2369+ return false
2370+ }
2371+
2372+ info := processor .getTypeInfo (value )
2373+
2374+ switch operator {
2375+ case ast .KindEqualsEqualsToken :
2376+ return ! info .CanBeNullishLike ()
2377+ case ast .KindEqualsEqualsEqualsToken :
2378+ return ! info .CanBeUndefinedLike ()
2379+ case ast .KindExclamationEqualsToken :
2380+ return info .IsAlwaysNullishLike ()
2381+ case ast .KindExclamationEqualsEqualsToken :
2382+ return info .IsAlwaysUndefinedLike ()
2383+ case ast .KindLessThanToken ,
2384+ ast .KindGreaterThanToken ,
2385+ ast .KindLessThanEqualsToken ,
2386+ ast .KindGreaterThanEqualsToken :
2387+ return false
2388+ default :
2389+ return true
2390+ }
2391+ }
2392+
23622393func (processor * chainProcessor ) validateOrChainNullishChecks (chain []Operand ) []Operand {
23632394 if processor .opts .AllowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing {
23642395 return chain
0 commit comments