8000 feat(eslint-plugin): [no-unnecessary-type-assertion] add option to ig… · phaux/typescript-eslint@baa4b47 · GitHub
[go: up one dir, main page]

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)
0