From 7b22e0c45719bdcf527f1e0527d2502fc039a7af Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Fri, 18 Apr 2025 12:26:58 +0200 Subject: [PATCH 1/5] feat(eslint-plugin): add option to ignore string const assertiions in no-unnecessary-type-assertion rule Fixes #8721 --- .../docs/rules/no-unnecessary-type-assertion.mdx | 10 ++++++++++ .../src/rules/no-unnecessary-type-assertion.ts | 13 +++++++++++++ .../no-unnecessary-type-assertion.shot | 4 ++++ .../rules/no-unnecessary-type-assertion.test.ts | 12 ++++++++++++ .../no-unnecessary-type-assertion.shot | 6 ++++++ 5 files changed, 45 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx index 497f843180e3..a2835c657027 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx @@ -73,6 +73,16 @@ function foo(x: number | undefined): number { ## Options +### `ignoreStringConstAssertion` + +{/* insert option description */} + +With `@typescript-eslint/no-unnecessary-type-assertion: ["error", { ignoreStringConstAssertion: true }]`, the following is **correct** code: + +```ts option='{ "ignoreStringConstAssertion": true }' showPlaygroundButton +const foo = `foo` as const; +``` + ### `typesToIgnore` {/* insert option description */} diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 4e26d6e20aa2..c2aaeb584055 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -21,6 +21,7 @@ import { export type Options = [ { + ignoreStringConstAssertion?: boolean; typesToIgnore?: string[]; }, ]; @@ -48,6 +49,10 @@ export default createRule({ type: 'object', additionalProperties: false, properties: { + ignoreStringConstAssertion: { + type: 'boolean', + description: 'Whether to ignore string const assertions.', + }, typesToIgnore: { type: 'array', description: 'A list of type names to ignore.', @@ -233,6 +238,14 @@ export default createRule({ const uncastType = services.getTypeAtLocation(node.expression); const typeIsUnchanged = isTypeUnchanged(uncastType, castType); + if ( + options.ignoreStringConstAssertion && + uncastType.isStringLiteral() && + isConstAssertion(node.typeAnnotation) + ) { + return; + } + const wouldSameTypeBeInferred = castType.isLiteral() ? isImplicitlyNarrowedLiteralDeclaration(node) : !isConstAssertion(node.typeAnnotation); diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot index 7dfc8a79864f..37ed6d8def41 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot @@ -51,6 +51,10 @@ function foo(x: number | undefined): number { return x!; } +Options: { "ignoreStringConstAssertion": true } + +const foo = `foo` as const; + Options: { "typesToIgnore": ["Foo"] } type Foo = 3; diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index c28260e79b4a..4752d3c638b1 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -429,6 +429,18 @@ declare function foo(bar: T): T; const baz: unknown = {}; foo(baz!); `, + { + code: 'const a = `a` as const;', + options: [{ ignoreStringConstAssertion: true }], + }, + { + code: "const a = 'a' as const;", + options: [{ ignoreStringConstAssertion: true }], + }, + { + code: "const a = 'a';", + options: [{ ignoreStringConstAssertion: true }], + }, ], invalid: [ diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot index bf9e0527ae55..ed49f3617f86 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot @@ -5,6 +5,10 @@ { "additionalProperties": false, "properties": { + "ignoreStringConstAssertion": { + "description": "Whether to ignore string const assertions.", + "type": "boolean" + }, "typesToIgnore": { "description": "A list of type names to ignore.", "items": { @@ -22,6 +26,8 @@ type Options = [ { + /** Whether to ignore string const assertions. */ + ignoreStringConstAssertion?: boolean; /** A list of type names to ignore. */ typesToIgnore?: string[]; }, From fa1849f39eb26301d348e75bb04db794d96e5b79 Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Fri, 18 Apr 2025 12:37:15 +0200 Subject: [PATCH 2/5] review: Enable const assertions on all literals by default --- .../rules/no-unnecessary-type-assertion.mdx | 12 +- .../rules/no-unnecessary-type-assertion.ts | 10 +- .../no-unnecessary-type-assertion.shot | 10 +- .../no-unnecessary-type-assertion.test.ts | 159 ++++++++++-------- .../no-unnecessary-type-assertion.shot | 8 +- 5 files changed, 104 insertions(+), 95 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx index a2835c657027..7c9cbb65b313 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx @@ -37,10 +37,6 @@ type Foo = number; const foo = (3 + 5) as Foo; ``` -```ts -const foo = 'foo' as const; -``` - ```ts function foo(x: number): number { return x!; // unnecessary non-null @@ -73,14 +69,14 @@ function foo(x: number | undefined): number { ## Options -### `ignoreStringConstAssertion` +### `checkLiteralConstAssertion` {/* insert option description */} -With `@typescript-eslint/no-unnecessary-type-assertion: ["error", { ignoreStringConstAssertion: true }]`, the following is **correct** code: +With `@typescript-eslint/no-unnecessary-type-assertion: ["error", { checkLiteralConstAssertion: true }]`, the following is **incorrect** code: -```ts option='{ "ignoreStringConstAssertion": true }' showPlaygroundButton -const foo = `foo` as const; +```ts option='{ "checkLiteralConstAssertion": true }' showPlaygroundButton +const foo = 'foo' as const; ``` ### `typesToIgnore` diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index c2aaeb584055..ac9501aaa1a2 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -21,7 +21,7 @@ import { export type Options = [ { - ignoreStringConstAssertion?: boolean; + checkLiteralConstAssertion?: boolean; typesToIgnore?: string[]; }, ]; @@ -49,9 +49,9 @@ export default createRule({ type: 'object', additionalProperties: false, properties: { - ignoreStringConstAssertion: { + checkLiteralConstAssertion: { type: 'boolean', - description: 'Whether to ignore string const assertions.', + description: 'Whether to check literal const assertions.', }, typesToIgnore: { type: 'array', @@ -239,8 +239,8 @@ export default createRule({ const typeIsUnchanged = isTypeUnchanged(uncastType, castType); if ( - options.ignoreStringConstAssertion && - uncastType.isStringLiteral() && + !options.checkLiteralConstAssertion && + node.expression.type === AST_NODE_TYPES.Literal && isConstAssertion(node.typeAnnotation) ) { return; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot index 37ed6d8def41..2db87a5e5402 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot @@ -23,11 +23,6 @@ const foo = (3 + 5) as Foo; Incorrect -const foo = 'foo' as const; - ~~~~~~~~~~~~~~ This assertion is unnecessary since it does not change the type of the expression. - -Incorrect - function foo(x: number): number { return x!; // unnecessary non-null ~~ This assertion is unnecessary since it does not change the type of the expression. @@ -51,9 +46,10 @@ function foo(x: number | undefined): number { return x!; } -Options: { "ignoreStringConstAssertion": true } +Options: { "checkLiteralConstAssertion": true } -const foo = `foo` as const; +const foo = 'foo' as const; + ~~~~~~~~~~~~~~ This assertion is unnecessary since it does not change the type of the expression. Options: { "typesToIgnore": ["Foo"] } diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 4752d3c638b1..a80ff1443b02 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -429,37 +429,9 @@ declare function foo(bar: T): T; const baz: unknown = {}; foo(baz!); `, - { - code: 'const a = `a` as const;', - options: [{ ignoreStringConstAssertion: true }], - }, - { - code: "const a = 'a' as const;", - options: [{ ignoreStringConstAssertion: true }], - }, - { - code: "const a = 'a';", - options: [{ ignoreStringConstAssertion: true }], - }, ], invalid: [ - // https://github.com/typescript-eslint/typescript-eslint/issues/8737 - { - code: 'const a = `a` as const;', - errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - output: 'const a = `a`;', - }, - { - code: "const a = 'a' as const;", - errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - output: "const a = 'a';", - }, - { - code: "const a = 'a';", - errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - output: "const a = 'a';", - }, { code: 'const foo = <3>3;', errors: [{ column: 13, line: 1, messageId: 'unnecessaryAssertion' }], @@ -1221,24 +1193,6 @@ var x = 1; }, { code: ` -class T { - readonly a = 'a' as const; -} - `, - errors: [ - { - line: 3, - messageId: 'unnecessaryAssertion', - }, - ], - output: ` -class T { - readonly a = 'a'; -} - `, - }, - { - code: ` class T { readonly a = 3 as 3; } @@ -1331,31 +1285,6 @@ enum T { Value2, } -declare const a: T.Value1; -const b = a; - `, - }, - { - code: ` -enum T { - Value1, - Value2, -} - -declare const a: T.Value1; -const b = a as const; - `, - errors: [ - { - messageId: 'unnecessaryAssertion', - }, - ], - output: ` -enum T { - Value1, - Value2, -} - declare const a: T.Value1; const b = a; `, @@ -1392,5 +1321,93 @@ const baz: unknown = {}; foo(baz); `, }, + { + code: 'const a = 1 as const;', + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: 'const a = 1;', + }, + { + code: 'const a = 1;', + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: 'const a = 1;', + }, + { + code: 'const a = 1n as const;', + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: 'const a = 1n;', + }, + { + code: 'const a = 1n;', + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: 'const a = 1n;', + }, + // https://github.com/typescript-eslint/typescript-eslint/issues/8737 + { + code: 'const a = `a` as const;', + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: 'const a = `a`;', + }, + { + code: "const a = 'a' as const;", + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: "const a = 'a';", + }, + { + code: "const a = 'a';", + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: "const a = 'a';", + }, + { + code: ` +class T { + readonly a = 'a' as const; +} + `, + errors: [ + { + line: 3, + messageId: 'unnecessaryAssertion', + }, + ], + options: [{ checkLiteralConstAssertion: true }], + output: ` +class T { + readonly a = 'a'; +} + `, + }, + { + code: ` +enum T { + Value1, + Value2, +} + +declare const a: T.Value1; +const b = a as const; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + options: [{ checkLiteralConstAssertion: true }], + output: ` +enum T { + Value1, + Value2, +} + +declare const a: T.Value1; +const b = a; + `, + }, ], }); diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot index ed49f3617f86..28e536ee5982 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot @@ -5,8 +5,8 @@ { "additionalProperties": false, "properties": { - "ignoreStringConstAssertion": { - "description": "Whether to ignore string const assertions.", + "checkLiteralConstAssertion": { + "description": "Whether to check literal const assertions.", "type": "boolean" }, "typesToIgnore": { @@ -26,8 +26,8 @@ type Options = [ { - /** Whether to ignore string const assertions. */ - ignoreStringConstAssertion?: boolean; + /** Whether to check literal const assertions. */ + checkLiteralConstAssertion?: boolean; /** A list of type names to ignore. */ typesToIgnore?: string[]; }, From 1213898ada0a9b0967bf18245494641c793f179a Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Fri, 18 Apr 2025 12:39:57 +0200 Subject: [PATCH 3/5] fix: Handle null, undefined and boolean literal expressions --- .../rules/no-unnecessary-type-assertion.ts | 16 +++++++-- .../no-unnecessary-type-assertion.test.ts | 36 +++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index ac9501aaa1a2..88db05ac349f 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -222,6 +222,16 @@ export default createRule({ return false; } + function isTypeLiteral(type: ts.Type) { + // type.isLiteral() only covers numbers/bigints and strings, hence the rest of the conditions. + return ( + type.isLiteral() || + tsutils.isBooleanLiteralType(type) || + type.flags === ts.TypeFlags.Undefined || + type.flags === ts.TypeFlags.Null + ); + } + return { 'TSAsExpression, TSTypeAssertion'( node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, @@ -240,13 +250,13 @@ export default createRule({ if ( !options.checkLiteralConstAssertion && - node.expression.type === AST_NODE_TYPES.Literal && - isConstAssertion(node.typeAnnotation) + isConstAssertion(node.typeAnnotation) && + isTypeLiteral(castType) ) { return; } - const wouldSameTypeBeInferred = castType.isLiteral() + const wouldSameTypeBeInferred = isTypeLiteral(castType) ? isImplicitlyNarrowedLiteralDeclaration(node) : !isConstAssertion(node.typeAnnotation); diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index a80ff1443b02..442d0adbc5b6 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -1321,6 +1321,42 @@ const baz: unknown = {}; foo(baz); `, }, + { + code: 'const a = null as const;', + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: 'const a = null;', + }, + { + code: 'const a = null;', + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: 'const a = null;', + }, + { + code: 'const a = undefined as const;', + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: 'const a = undefined;', + }, + { + code: 'const a = undefined;', + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: 'const a = undefined;', + }, + { + code: 'const a = true as const;', + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: 'const a = true;', + }, + { + code: 'const a = true;', + errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], + options: [{ checkLiteralConstAssertion: true }], + output: 'const a = true;', + }, { code: 'const a = 1 as const;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], From e3778054835e9b9d6b3174386d093ee8ffeb2d78 Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Fri, 18 Apr 2025 15:00:18 +0200 Subject: [PATCH 4/5] =?UTF-8?q?Apply=20suggestions=20from=20code=20review?= =?UTF-8?q?=20Co-authored-by:=20Josh=20Goldberg=20=E2=9C=A8=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rules/no-unnecessary-type-assertion.mdx | 8 +++-- .../rules/no-unnecessary-type-assertion.ts | 19 +++++++----- .../no-unnecessary-type-assertion.shot | 2 +- .../no-unnecessary-type-assertion.test.ts | 30 +++++++++---------- .../no-unnecessary-type-assertion.shot | 4 +-- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx index 7c9cbb65b313..9ba5fc3d4e85 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx @@ -69,16 +69,18 @@ function foo(x: number | undefined): number { ## Options -### `checkLiteralConstAssertion` +### `checkLiteralConstAssertions` {/* insert option description */} -With `@typescript-eslint/no-unnecessary-type-assertion: ["error", { checkLiteralConstAssertion: true }]`, the following is **incorrect** code: +With `@typescript-eslint/no-unnecessary-type-assertion: ["error", { checkLiteralConstAssertions: true }]`, the following is **incorrect** code: -```ts option='{ "checkLiteralConstAssertion": true }' showPlaygroundButton +```ts option='{ "checkLiteralConstAssertions": true }' showPlaygroundButton const foo = 'foo' as const; ``` +See [#8721 False positives for "as const" assertions (issue comment)](https://github.com/typescript-eslint/typescript-eslint/issues/8721#issuecomment-2145291966) for more information on this option. + ### `typesToIgnore` {/* insert option description */} diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 88db05ac349f..6bd0fa32c691 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -21,7 +21,7 @@ import { export type Options = [ { - checkLiteralConstAssertion?: boolean; + checkLiteralConstAssertions?: boolean; typesToIgnore?: string[]; }, ]; @@ -49,7 +49,7 @@ export default createRule({ type: 'object', additionalProperties: false, properties: { - checkLiteralConstAssertion: { + checkLiteralConstAssertions: { type: 'boolean', description: 'Whether to check literal const assertions.', }, @@ -223,7 +223,6 @@ export default createRule({ } function isTypeLiteral(type: ts.Type) { - // type.isLiteral() only covers numbers/bigints and strings, hence the rest of the conditions. return ( type.isLiteral() || tsutils.isBooleanLiteralType(type) || @@ -247,18 +246,22 @@ export default createRule({ const castType = services.getTypeAtLocation(node); const uncastType = services.getTypeAtLocation(node.expression); const typeIsUnchanged = isTypeUnchanged(uncastType, castType); + const castTypeIsLiteral = isTypeLiteral(castType); + const typeAnnotationIsConstAssertion = isConstAssertion( + node.typeAnnotation, + ); if ( - !options.checkLiteralConstAssertion && - isConstAssertion(node.typeAnnotation) && - isTypeLiteral(castType) + !options.checkLiteralConstAssertions && + castTypeIsLiteral && + typeAnnotationIsConstAssertion ) { return; } - const wouldSameTypeBeInferred = isTypeLiteral(castType) + const wouldSameTypeBeInferred = castTypeIsLiteral ? isImplicitlyNarrowedLiteralDeclaration(node) - : !isConstAssertion(node.typeAnnotation); + : !typeAnnotationIsConstAssertion; if (typeIsUnchanged && wouldSameTypeBeInferred) { context.report({ diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot index 2db87a5e5402..5c7cf3a7f5e0 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot @@ -46,7 +46,7 @@ function foo(x: number | undefined): number { return x!; } -Options: { "checkLiteralConstAssertion": true } +Options: { "checkLiteralConstAssertions": true } const foo = 'foo' as const; ~~~~~~~~~~~~~~ This assertion is unnecessary since it does not change the type of the expression. diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 442d0adbc5b6..79f0c2a6650f 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -1324,80 +1324,80 @@ foo(baz); { code: 'const a = null as const;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: 'const a = null;', }, { code: 'const a = null;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: 'const a = null;', }, { code: 'const a = undefined as const;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: 'const a = undefined;', }, { code: 'const a = undefined;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: 'const a = undefined;', }, { code: 'const a = true as const;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: 'const a = true;', }, { code: 'const a = true;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: 'const a = true;', }, { code: 'const a = 1 as const;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: 'const a = 1;', }, { code: 'const a = 1;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: 'const a = 1;', }, { code: 'const a = 1n as const;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: 'const a = 1n;', }, { code: 'const a = 1n;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: 'const a = 1n;', }, // https://github.com/typescript-eslint/typescript-eslint/issues/8737 { code: 'const a = `a` as const;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: 'const a = `a`;', }, { code: "const a = 'a' as const;", errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: "const a = 'a';", }, { code: "const a = 'a';", errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: "const a = 'a';", }, { @@ -1412,7 +1412,7 @@ class T { messageId: 'unnecessaryAssertion', }, ], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: ` class T { readonly a = 'a'; @@ -1434,7 +1434,7 @@ const b = a as const; messageId: 'unnecessaryAssertion', }, ], - options: [{ checkLiteralConstAssertion: true }], + options: [{ checkLiteralConstAssertions: true }], output: ` enum T { Value1, diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot index 28e536ee5982..8a0ba9015278 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot @@ -5,7 +5,7 @@ { "additionalProperties": false, "properties": { - "checkLiteralConstAssertion": { + "checkLiteralConstAssertions": { "description": "Whether to check literal const assertions.", "type": "boolean" }, @@ -27,7 +27,7 @@ type Options = [ { /** Whether to check literal const assertions. */ - checkLiteralConstAssertion?: boolean; + checkLiteralConstAssertions?: boolean; /** A list of type names to ignore. */ typesToIgnore?: string[]; }, From fa008e4d6e1d12d994a4cc0f147d16373ff9c7bd Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Fri, 18 Apr 2025 11:00:11 +0200 Subject: [PATCH 5/5] Apply suggestions from code review #2 --- .../rules/no-unnecessary-type-assertion.ts | 11 ++-- .../no-unnecessary-type-assertion.test.ts | 50 ++++++++++--------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 6bd0fa32c691..3225e418ce3c 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -223,12 +223,7 @@ export default createRule({ } function isTypeLiteral(type: ts.Type) { - return ( - type.isLiteral() || - tsutils.isBooleanLiteralType(type) || - type.flags === ts.TypeFlags.Undefined || - type.flags === ts.TypeFlags.Null - ); + return type.isLiteral() || tsutils.isBooleanLiteralType(type); } return { @@ -244,8 +239,6 @@ export default createRule({ } const castType = services.getTypeAtLocation(node); - const uncastType = services.getTypeAtLocation(node.expression); - const typeIsUnchanged = isTypeUnchanged(uncastType, castType); const castTypeIsLiteral = isTypeLiteral(castType); const typeAnnotationIsConstAssertion = isConstAssertion( node.typeAnnotation, @@ -259,6 +252,8 @@ export default createRule({ return; } + const uncastType = services.getTypeAtLocation(node.expression); + const typeIsUnchanged = isTypeUnchanged(uncastType, castType); const wouldSameTypeBeInferred = castTypeIsLiteral ? isImplicitlyNarrowedLiteralDeclaration(node) : !typeAnnotationIsConstAssertion; diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 79f0c2a6650f..e8af03e47d2c 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -429,6 +429,32 @@ declare function foo(bar: T): T; const baz: unknown = {}; foo(baz!); `, + { + code: 'const a = `a` as const;', + }, + { + code: "const a = 'a' as const;", + }, + { + code: "const a = 'a';", + }, + { + code: ` +class T { + readonly a = 'a' as const; +} + `, + }, + { + code: ` +enum T { + Value1, + Value2, +} +declare const a: T.Value1; +const b = a as const; + `, + }, ], invalid: [ @@ -1321,30 +1347,6 @@ const baz: unknown = {}; foo(baz); `, }, - { - code: 'const a = null as const;', - errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertions: true }], - output: 'const a = null;', - }, - { - code: 'const a = null;', - errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertions: true }], - output: 'const a = null;', - }, - { - code: 'const a = undefined as const;', - errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertions: true }], - output: 'const a = undefined;', - }, - { - code: 'const a = undefined;', - errors: [{ line: 1, messageId: 'unnecessaryAssertion' }], - options: [{ checkLiteralConstAssertions: true }], - output: 'const a = undefined;', - }, { code: 'const a = true as const;', errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],