Skip to content

Commit baa4b47

Browse files
KuShphaux
authored andcommitted
feat(eslint-plugin): [no-unnecessary-type-assertion] add option to ignore string const assertions (typescript-eslint#10979)
* feat(eslint-plugin): add option to ignore string const assertiions in no-unnecessary-type-assertion rule Fixes typescript-eslint#8721 * review: Enable const assertions on all literals by default * fix: Handle null, undefined and boolean literal expressions * Apply suggestions from code review Co-authored-by: Josh Goldberg ✨ <git@joshuakgoldberg.com> * Apply suggestions from code review typescript-eslint#2
1 parent 31bc474 commit baa4b47

File tree

5 files changed

+167
-65
lines changed

5 files changed

+167
-65
lines changed

packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.mdx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,6 @@ type Foo = number;
3737
const foo = (3 + 5) as Foo;
3838
```
3939

40-
```ts
41-
const foo = 'foo' as const;
42-
```
43-
4440
```ts
4541
function foo(x: number): number {
4642
return x!; // unnecessary non-null
@@ -73,6 +69,18 @@ function foo(x: number | undefined): number {
7369

7470
## Options
7571

72+
### `checkLiteralConstAssertions`
73+
74+
{/* insert option description */}
75+
76+
With `@typescript-eslint/no-unnecessary-type-assertion: ["error", { checkLiteralConstAssertions: true }]`, the following is **incorrect** code:
77+
78+
```ts option='{ "checkLiteralConstAssertions": true }' showPlaygroundButton
79+
const foo = 'foo' as const;
80+
```
81+
82+
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.
83+
7684
### `typesToIgnore`
7785

7886
{/* insert option description */}

packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121

2222
export type Options = [
2323
{
24+
checkLiteralConstAssertions?: boolean;
2425
typesToIgnore?: string[];
2526
},
2627
];
@@ -48,6 +49,10 @@ export default createRule<Options, MessageIds>({
4849
type: 'object',
4950
additionalProperties: false,
5051
properties: {
52+
checkLiteralConstAssertions: {
53+
type: 'boolean',
54+
description: 'Whether to check literal const assertions.',
55+
},
5156
typesToIgnore: {
5257
type: 'array',
5358
description: 'A list of type names to ignore.',
@@ -217,6 +222,10 @@ export default createRule<Options, MessageIds>({
217222
return false;
218223
}
219224

225+
function isTypeLiteral(type: ts.Type) {
226+
return type.isLiteral() || tsutils.isBooleanLiteralType(type);
227+
}
228+
220229
return {
221230
'TSAsExpression, TSTypeAssertion'(
222231
node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion,
@@ -230,12 +239,24 @@ export default createRule<Options, MessageIds>({
230239
}
231240

232241
const castType = services.getTypeAtLocation(node);
242+
const castTypeIsLiteral = isTypeLiteral(castType);
243+
const typeAnnotationIsConstAssertion = isConstAssertion(
244+
node.typeAnnotation,
245+
);
246+
247+
if (
248+
!options.checkLiteralConstAssertions &&
249+
castTypeIsLiteral &&
250+
typeAnnotationIsConstAssertion
251+
) {
252+
return;
253+
}
254+
233255
const uncastType = services.getTypeAtLocation(node.expression);
234256
const typeIsUnchanged = isTypeUnchanged(uncastType, castType);
235-
236-
const wouldSameTypeBeInferred = castType.isLiteral()
257+
const wouldSameTypeBeInferred = castTypeIsLiteral
237258
? isImplicitlyNarrowedLiteralDeclaration(node)
238-
: !isConstAssertion(node.typeAnnotation);
259+
: !typeAnnotationIsConstAssertion;
239260

240261
if (typeIsUnchanged && wouldSameTypeBeInferred) {
241262
context.report({

packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-assertion.shot

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts

Lines changed: 120 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -429,25 +429,35 @@ declare function foo<T extends unknown>(bar: T): T;
429429
const baz: unknown = {};
430430
foo(baz!);
431431
`,
432-
],
433-
434-
invalid: [
435-
// https://github.com/typescript-eslint/typescript-eslint/issues/8737
436432
{
437433
code: 'const a = `a` as const;',
438-
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
439-
output: 'const a = `a`;',
440434
},
441435
{
442436
code: "const a = 'a' as const;",
443-
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
444-
output: "const a = 'a';",
445437
},
446438
{
447439
code: "const a = <const>'a';",
448-
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
449-
output: "const a = 'a';",
450440
},
441+
{
442+
code: `
443+
class T {
444+
readonly a = 'a' as const;
445+
}
446+
`,
447+
},
448+
{
449+
code: `
450+
enum T {
451+
Value1,
452+
Value2,
453+
}
454+
declare const a: T.Value1;
455+
const b = a as const;
456+
`,
457+
},
458+
],
459+
460+
invalid: [
451461
{
452462
code: 'const foo = <3>3;',
453463
errors: [{ column: 13, line: 1, messageId: 'unnecessaryAssertion' }],
@@ -1209,24 +1219,6 @@ var x = 1;
12091219
},
12101220
{
12111221
code: `
1212-
class T {
1213-
readonly a = 'a' as const;
1214-
}
1215-
`,
1216-
errors: [
1217-
{
1218-
line: 3,
1219-
messageId: 'unnecessaryAssertion',
1220-
},
1221-
],
1222-
output: `
1223-
class T {
1224-
readonly a = 'a';
1225-
}
1226-
`,
1227-
},
1228-
{
1229-
code: `
12301222
class T {
12311223
readonly a = 3 as 3;
12321224
}
@@ -1319,31 +1311,6 @@ enum T {
13191311
Value2,
13201312
}
13211313
1322-
declare const a: T.Value1;
1323-
const b = a;
1324-
`,
1325-
},
1326-
{
1327-
code: `
1328-
enum T {
1329-
Value1,
1330-
Value2,
1331-
}
1332-
1333-
declare const a: T.Value1;
1334-
const b = a as const;
1335-
`,
1336-
errors: [
1337-
{
1338-
messageId: 'unnecessaryAssertion',
1339-
},
1340-
],
1341-
output: `
1342-
enum T {
1343-
Value1,
1344-
Value2,
1345-
}
1346-
13471314
declare const a: T.Value1;
13481315
const b = a;
13491316
`,
@@ -1380,5 +1347,105 @@ const baz: unknown = {};
13801347
foo(baz);
13811348
`,
13821349
},
1350+
{
1351+
code: 'const a = true as const;',
1352+
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
1353+
options: [{ checkLiteralConstAssertions: true }],
1354+
output: 'const a = true;',
1355+
},
1356+
{
1357+
code: 'const a = <const>true;',
1358+
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
1359+
options: [{ checkLiteralConstAssertions: true }],
1360+
output: 'const a = true;',
1361+
},
1362+
{
1363+
code: 'const a = 1 as const;',
1364+
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
1365+
options: [{ checkLiteralConstAssertions: true }],
1366+
output: 'const a = 1;',
1367+
},
1368+
{
1369+
code: 'const a = <const>1;',
1370+
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
1371+
options: [{ checkLiteralConstAssertions: true }],
1372+
output: 'const a = 1;',
1373+
},
1374+
{
1375+
code: 'const a = 1n as const;',
1376+
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
1377+
options: [{ checkLiteralConstAssertions: true }],
1378+
output: 'const a = 1n;',
1379+
},
1380+
{
1381+
code: 'const a = <const>1n;',
1382+
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
1383+
options: [{ checkLiteralConstAssertions: true }],
1384+
output: 'const a = 1n;',
1385+
},
1386+
// https://github.com/typescript-eslint/typescript-eslint/issues/8737
1387+
{
1388+
code: 'const a = `a` as const;',
1389+
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
1390+
options: [{ checkLiteralConstAssertions: true }],
1391+
output: 'const a = `a`;',
1392+
},
1393+
{
1394+
code: "const a = 'a' as const;",
1395+
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
1396+
options: [{ checkLiteralConstAssertions: true }],
1397+
output: "const a = 'a';",
1398+
},
1399+
{
1400+
code: "const a = <const>'a';",
1401+
errors: [{ line: 1, messageId: 'unnecessaryAssertion' }],
1402+
options: [{ checkLiteralConstAssertions: true }],
1403+
output: "const a = 'a';",
1404+
},
1405+
{
1406+
code: `
1407+
class T {
1408+
readonly a = 'a' as const;
1409+
}
1410+
`,
1411+
errors: [
1412+
{
1413+
line: 3,
1414+
messageId: 'unnecessaryAssertion',
1415+
},
1416+
],
1417+
options: [{ checkLiteralConstAssertions: true }],
1418+
output: `
1419+
class T {
1420+
readonly a = 'a';
1421+
}
1422+
`,
1423+
},
1424+
{
1425+
code: `
1426+
enum T {
1427+
Value1,
1428+
Value2,
1429+
}
1430+
1431+
declare const a: T.Value1;
1432+
const b = a as const;
1433+
`,
1434+
errors: [
1435+
{
1436+
messageId: 'unnecessaryAssertion',
1437+
},
1438+
],
1439+
options: [{ checkLiteralConstAssertions: true }],
1440+
output: `
1441+
enum T {
1442+
Value1,
1443+
Value2,
1444+
}
1445+
1446+
declare const a: T.Value1;
1447+
const b = a;
1448+
`,
1449+
},
13831450
],
13841451
});

packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-type-assertion.shot

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy