@@ -1353,6 +1353,19 @@ var NoUnnecessaryConditionRule = rule.Rule{
13531353 }
13541354 }
13551355
1356+ // With noUncheckedIndexedAccess enabled, element accesses through index
1357+ // signatures can still be absent at runtime (e.g. Record<string, T>[key]).
1358+ // Treat these as potentially nullish for ??=.
1359+ if ctx .Program .Options ().NoUncheckedIndexedAccess .IsTrue () && isElementAccess (leftSkip ) {
1360+ elemAccess := leftSkip .AsElementAccessExpression ()
1361+ baseType := getResolvedType (elemAccess .Expression )
1362+ if baseType != nil &&
1363+ (ctx .TypeChecker .GetStringIndexType (baseType ) != nil ||
1364+ ctx .TypeChecker .GetNumberIndexType (baseType ) != nil ) {
1365+ return
1366+ }
1367+ }
1368+
13561369 // Skip optional property access - with exactOptionalPropertyTypes,
13571370 // the type doesn't include undefined but the property can still be absent
13581371 // Also skip private properties - they have complex semantics
@@ -1500,7 +1513,7 @@ var NoUnnecessaryConditionRule = rule.Rule{
15001513 if isEqualityOp {
15011514 // For equality operators, check if types can ever be equal
15021515 // Only skip if BOTH sides are nullish OR one side is a union that includes nullish
1503- hasOverlap := typesHaveOverlap (leftType , rightType )
1516+ hasOverlap := typesHaveOverlap (ctx . TypeChecker , leftType , rightType )
15041517
15051518 if ! hasOverlap {
15061519 // Check if this is a valid nullish check (e.g., `a: string | null` with `a === null`)
@@ -1958,7 +1971,7 @@ func isAllowedConstantLiteral(node *ast.Node) bool {
19581971// - Nullish types: null/undefined/void overlap with each other (treated as interchangeable in some contexts)
19591972// - Literals and base types: overlap (e.g., "hello" overlaps with string)
19601973// - Union types: checked part by part (e.g., string | number overlaps with "hello")
1961- func typesHaveOverlap (left , right * checker.Type ) bool {
1974+ func typesHaveOverlap (typeChecker * checker. Checker , left , right * checker.Type ) bool {
19621975 // Handle any/unknown types - they overlap with everything
19631976 leftFlags := checker .Type_flags (left )
19641977 rightFlags := checker .Type_flags (right )
@@ -1986,6 +1999,14 @@ func typesHaveOverlap(left, right *checker.Type) bool {
19861999 for _ , rightPart := range rightParts {
19872000 rightPartFlags := checker .Type_flags (rightPart )
19882001
2002+ // Use TypeScript assignability first. This catches subtype/alias
2003+ // relationships (e.g. Window extends EventTarget, template literals,
2004+ // branded intersections, unique symbols) that raw TypeFlags miss.
2005+ if checker .Checker_isTypeAssignableTo (typeChecker , leftPart , rightPart ) ||
2006+ checker .Checker_isTypeAssignableTo (typeChecker , rightPart , leftPart ) {
2007+ return true
2008+ }
2009+
19892010 // Check if both are the same primitive type
19902011 primitiveFlags := checker .TypeFlagsString | checker .TypeFlagsNumber |
19912012 checker .TypeFlagsBoolean | checker .TypeFlagsBigInt |
0 commit comments