diff --git a/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts b/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts index 69fc21736de..b0649d05745 100644 --- a/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts +++ b/packages/eslint-plugin/src/rules/non-nullable-type-assertion-style.ts @@ -17,7 +17,7 @@ export default util.createRule({ fixable: 'code', messages: { preferNonNullAssertion: - 'Use a ! assertion to more succintly remove null and undefined from the type.', + 'Use a ! assertion to more succinctly remove null and undefined from the type.', }, schema: [], type: 'suggestion', @@ -43,14 +43,31 @@ export default util.createRule({ return tsutils.unionTypeParts(type); }; + const couldBeNullish = (type: ts.Type): boolean => { + if (type.flags & ts.TypeFlags.TypeParameter) { + const constraint = type.getConstraint(); + return constraint == null || couldBeNullish(constraint); + } else if (tsutils.isUnionType(type)) { + for (const part of type.types) { + if (couldBeNullish(part)) { + return true; + } + } + return false; + } else { + return ( + (type.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0 + ); + } + }; + const sameTypeWithoutNullish = ( assertedTypes: ts.Type[], originalTypes: ts.Type[], ): boolean => { const nonNullishOriginalTypes = originalTypes.filter( type => - type.flags !== ts.TypeFlags.Null && - type.flags !== ts.TypeFlags.Undefined, + (type.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) === 0, ); if (nonNullishOriginalTypes.length === originalTypes.length) { @@ -58,7 +75,10 @@ export default util.createRule({ } for (const assertedType of assertedTypes) { - if (!nonNullishOriginalTypes.includes(assertedType)) { + if ( + couldBeNullish(assertedType) || + !nonNullishOriginalTypes.includes(assertedType) + ) { return false; } } diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig.noUncheckedIndexedAccess.json b/packages/eslint-plugin/tests/fixtures/tsconfig.noUncheckedIndexedAccess.json new file mode 100644 index 00000000000..c452514f949 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/tsconfig.noUncheckedIndexedAccess.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noUncheckedIndexedAccess": true + } +} diff --git a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts index fadd5064abd..9bfa209168d 100644 --- a/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts +++ b/packages/eslint-plugin/tests/rules/non-nullable-type-assertion-style.test.ts @@ -7,7 +7,7 @@ const ruleTester = new RuleTester({ parserOptions: { sourceType: 'module', tsconfigRootDir: rootDir, - project: './tsconfig.json', + project: './tsconfig.noUncheckedIndexedAccess.json', }, parser: '@typescript-eslint/parser', }); @@ -61,6 +61,35 @@ const x = 1 as 1; declare function foo(): T; const bar = foo() as number; `, + ` +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ` +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ` +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ` +function first( + array: ArrayLike, +): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + ` +type A = 'a' | 'A'; +type B = 'b' | 'B'; +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, ], invalid: [ @@ -199,5 +228,26 @@ declare const x: T; const y = x!; `, }, + { + code: ` +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0] as T) : null; +} + `, + errors: [ + { + column: 30, + line: 3, + messageId: 'preferNonNullAssertion', + }, + ], + // Output is not expected to match required formatting due to excess parentheses + // eslint-disable-next-line @typescript-eslint/internal/plugin-test-formatting + output: ` +function first(array: ArrayLike): T | null { + return array.length > 0 ? (array[0]!) : null; +} + `, + }, ], });