Skip to content

Commit 627b329

Browse files
committed
Fix noUnnecessaryTypeAssertion edge case [publish]
1 parent 7928db9 commit 627b329

File tree

6 files changed

+43
-21
lines changed

6 files changed

+43
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Changelog
22

3-
## 1.0.16
3+
## 1.0.17
44

55
Beta release

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ const b = 2;
210210

211211
Writing custom rules is part of the core value of tsl.
212212

213-
Rules run on the TS AST, which is less known than ESTree but allows to query type information for a given node with `context.checker.getTypeAtLocation(node)`. Use [ast-explorer.dev](https://ast-explorer.dev/#eNo9zEEKwjAQheGrxLdSKG5ctV5AcO9qNiEdQmSYCUlVpPTuTSl0+72fN0Mw4O2/voaS8oQOucH0z3xAaBBMqwlfxeKZ8GARcz8rMp4Ilztpy6xlM6lzBPEaPz7yi0tNpoTB9X23b/vtM+m48Y10wbIChCAraw==) to explore the AST.
213+
Rules run on the TS AST, which is less known than ESTree but allows to query type information for a given node with `context.checker.getTypeAtLocation(node)`. Use [ast-explorer.dev](https://ast-explorer.dev/#eNo9zEEKwjAQheGrxLdSKG5ctV5AcO9qNiEdQmSYCUlVpPTuTSl0+72fN0Mw4O2/voaS8oQOucH0z3xAaBBMqwlfxeKZ8GARcz8rMp4Ilztpy6xlM6lzBPEaPz7yi0tNpoTB9X23b/vtM+m48Y10wbIChCAraw==) to explore the AST. To explore type information, use [ts-ast-viewer](https://ts-ast-viewer.com/).
214214

215215
By default, the TS AST is, funny enough, poorly typed. That's why tsl ships with rewritten AST types that allows for type narrowing and exhaustive switches.
216216

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "tsl",
33
"description": "An extension of tsc for type-aware linting",
4-
"version": "1.0.16",
4+
"version": "1.0.17",
55
"license": "MIT",
66
"author": "Arnaud Barré (https://github.com/ArnaudBarre)",
77
"private": true,

src/ruleTester.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { AST, Checker, Context, ReportDescriptor, Rule } from "./types.ts";
55
import { visitorEntries } from "./visitorEntries.ts";
66

77
export function print(...args: any[]) {
8-
console.log(...args.map(transform));
8+
console.log(...args.map((value) => transform(value, new Set())));
99
}
1010

1111
const allFlags = Object.entries(NodeFlags).filter(
@@ -17,9 +17,14 @@ for (const [key, value] of Object.entries(SyntaxKind)) {
1717
syntaxKinds[value] = key;
1818
}
1919
}
20-
const transform = (value: unknown): unknown => {
20+
const transform = (
21+
value: unknown,
22+
alreadyTransformed: Set<unknown>,
23+
): unknown => {
24+
if (alreadyTransformed.has(value)) return value;
25+
alreadyTransformed.add(value);
2126
if (Array.isArray(value)) {
22-
return value.map(transform);
27+
return value.map((value) => transform(value, alreadyTransformed));
2328
}
2429
if (typeof value === "object" && value) {
2530
if (isNode(value)) {
@@ -43,7 +48,10 @@ const transform = (value: unknown): unknown => {
4348
return node;
4449
} else {
4550
return Object.fromEntries(
46-
Object.entries(value).map(([key, value]) => [key, transform(value)]),
51+
Object.entries(value).map(([key, value]) => [
52+
key,
53+
transform(value, alreadyTransformed),
54+
]),
4755
);
4856
}
4957
}

src/rules/noUnnecessaryTypeAssertion/noUnnecessaryTypeAssertion.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,11 @@ x!;
357357
// https://www.typescriptlang.org/play/?#code/MYewdgzgLgBAZiEMC8MBECRpgQwjUSKAbgCgYKYB6KmAPQH5TDoYAjHAJxXQ87TKVqtRs3CtMAeTYArAKbBYqAN7xEMAL6DKNekxaw+0+Yp6q+m8jpEMgA
358358
"const a = 'a' as const;",
359359
"const a = <const>'a';",
360+
`
361+
function castNonNull<T extends { input: any }>(arg: T["input"] | null) {
362+
return arg!;
363+
}
364+
`,
360365
],
361366
invalid: [
362367
{

src/rules/noUnnecessaryTypeAssertion/noUnnecessaryTypeAssertion.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,31 @@ export const noUnnecessaryTypeAssertion = defineRule(
5858
return;
5959
}
6060

61-
const type = context.utils.getConstrainedTypeAtLocation(
61+
const type = context.checker.getTypeAtLocation(node.expression);
62+
const constraintType = context.utils.getConstrainedTypeAtLocation(
6263
node.expression,
6364
);
65+
const nullableFlags =
66+
TypeFlags.Null
67+
| TypeFlags.Undefined
68+
| TypeFlags.Void
69+
| TypeFlags.Unknown;
6470

65-
if (
66-
!context.utils.typeOrUnionHasFlag(
67-
type,
68-
TypeFlags.Null
69-
| TypeFlags.Undefined
70-
| TypeFlags.Void
71-
| TypeFlags.Unknown,
72-
)
73-
) {
71+
if (!context.utils.typeOrUnionHasFlag(constraintType, nullableFlags)) {
7472
if (
7573
node.expression.kind === SyntaxKind.Identifier
7674
&& isPossiblyUsedBeforeAssigned(context, node.expression)
7775
) {
7876
return;
7977
}
78+
if (
79+
context.utils.typeHasFlag(constraintType, TypeFlags.Any)
80+
&& context.utils.typeOrUnionHasFlag(type, nullableFlags)
81+
) {
82+
// If the generic contains any, T | null will be any,
83+
// so we need to check the non constrained type
84+
return;
85+
}
8086

8187
context.report({
8288
node,
@@ -90,7 +96,10 @@ export const noUnnecessaryTypeAssertion = defineRule(
9096
const contextualType = getContextualType(context, node);
9197
if (contextualType) {
9298
if (
93-
context.utils.typeOrUnionHasFlag(type, TypeFlags.Unknown)
99+
context.utils.typeOrUnionHasFlag(
100+
constraintType,
101+
TypeFlags.Unknown,
102+
)
94103
&& !context.utils.typeOrUnionHasFlag(
95104
contextualType,
96105
TypeFlags.Unknown,
@@ -102,15 +111,15 @@ export const noUnnecessaryTypeAssertion = defineRule(
102111
// in strict mode you can't assign null to undefined, so we have to make sure that
103112
// the two types share a nullable type
104113
const typeIncludesUndefined = context.utils.typeOrUnionHasFlag(
105-
type,
114+
constraintType,
106115
TypeFlags.Undefined,
107116
);
108117
const typeIncludesNull = context.utils.typeOrUnionHasFlag(
109-
type,
118+
constraintType,
110119
TypeFlags.Null,
111120
);
112121
const typeIncludesVoid = context.utils.typeOrUnionHasFlag(
113-
type,
122+
constraintType,
114123
TypeFlags.Void,
115124
);
116125

0 commit comments

Comments
 (0)