Skip to content

Commit 7d58f8a

Browse files
committed
Add noUnnecessaryCasts compiler option, quickfix
1 parent b4715d3 commit 7d58f8a

File tree

30 files changed

+555
-4
lines changed

30 files changed

+555
-4
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19882,6 +19882,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1988219882
}
1988319883
}
1988419884
if (sourceFlags & TypeFlags.Conditional) {
19885+
if ((source as ConditionalType).root === (target as ConditionalType).root) {
19886+
// Two instantiations of the same conditional type, just check instantiated outer type parameter equality
19887+
const params = (source as ConditionalType).root.outerTypeParameters || [];
19888+
const sourceTypeArguments = map(params, t => (source as ConditionalType).mapper ? getMappedType(t, (source as ConditionalType).mapper!) : t);
19889+
const targetTypeArguments = map(params, t => (target as ConditionalType).mapper ? getMappedType(t, (target as ConditionalType).mapper!) : t);
19890+
return typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, map(params, () => VarianceFlags.Unmeasurable), /*reportErrors*/ false, IntersectionState.None);
19891+
}
1988519892
if ((source as ConditionalType).root.isDistributive === (target as ConditionalType).root.isDistributive) {
1988619893
if (result = isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both, /*reportErrors*/ false)) {
1988719894
if (result &= isRelatedTo((source as ConditionalType).extendsType, (target as ConditionalType).extendsType, RecursionFlags.Both, /*reportErrors*/ false)) {
@@ -32779,6 +32786,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3277932786
checkTypeComparableTo(exprType, targetType, errNode,
3278032787
Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first);
3278132788
}
32789+
else if (isTypeIdenticalTo(targetType, widenedType)) {
32790+
errorOrSuggestion(!!compilerOptions.noUnnecessaryCasts, errNode, Diagnostics.Type_cast_has_no_effect_on_the_type_of_this_expression);
32791+
}
3278232792
});
3278332793
}
3278432794
return targetType;

src/compiler/commandLineParser.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,15 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
800800
description: Diagnostics.Raise_an_error_when_a_function_parameter_isn_t_read,
801801
defaultValueDescription: false,
802802
},
803+
{
804+
name: "noUnnecessaryCasts",
805+
type: "boolean",
806+
affectsSemanticDiagnostics: true,
807+
affectsBuildInfo: true,
808+
category: Diagnostics.Type_Checking,
809+
description: Diagnostics.Raise_an_error_when_a_type_cast_does_not_affect_the_type_of_an_expression,
810+
defaultValueDescription: false,
811+
},
803812
{
804813
name: "exactOptionalPropertyTypes",
805814
type: "boolean",

src/compiler/diagnosticMessages.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5919,6 +5919,10 @@
59195919
"category": "Message",
59205920
"code": 6803
59215921
},
5922+
"Raise an error when a type cast does not affect the type of an expression.": {
5923+
"category": "Message",
5924+
"code": 6804
5925+
},
59225926

59235927
"one of:": {
59245928
"category": "Message",
@@ -6534,6 +6538,10 @@
65346538
"category": "Suggestion",
65356539
"code": 80008
65366540
},
6541+
"Type cast has no effect on the type of this expression.": {
6542+
"category": "Error",
6543+
"code": 80009
6544+
},
65376545

65386546
"Add missing 'super()' call": {
65396547
"category": "Message",
@@ -7392,6 +7400,14 @@
73927400
"category": "Message",
73937401
"code": 95175
73947402
},
7403+
"Remove unnecessary type cast.": {
7404+
"category": "Message",
7405+
"code": 95176
7406+
},
7407+
"Remove all unnecessary type casts.": {
7408+
"category": "Message",
7409+
"code": 95177
7410+
},
73957411

73967412
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
73977413
"category": "Error",

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6713,6 +6713,7 @@ export interface CompilerOptions {
67136713
noImplicitReturns?: boolean;
67146714
noImplicitThis?: boolean; // Always combine with strict property
67156715
noStrictGenericChecks?: boolean;
6716+
noUnnecessaryCasts?: boolean;
67166717
noUnusedLocals?: boolean;
67176718
noUnusedParameters?: boolean;
67186719
noImplicitUseStrict?: boolean;

src/services/_namespaces/ts.codefix.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export * from "../codefixes/wrapJsxInFragment";
6666
export * from "../codefixes/convertToMappedObjectType";
6767
export * from "../codefixes/removeAccidentalCallParentheses";
6868
export * from "../codefixes/removeUnnecessaryAwait";
69+
export * from "../codefixes/removeUnnecessaryCast";
6970
export * from "../codefixes/splitTypeOnlyImport";
7071
export * from "../codefixes/convertConstToLet";
7172
export * from "../codefixes/fixExpectedComma";
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {
2+
Diagnostics, findAncestor, isAssertionExpression, isInJSDoc, Node,
3+
isParenthesizedExpression, ParenthesizedExpression, SourceFile, textChanges, TextSpan, tryCast, HasJSDoc, first, last, needsParentheses,
4+
} from "../_namespaces/ts";
5+
import { codeFixAll, createCodeFixAction, findAncestorMatchingSpan, registerCodeFix } from "../_namespaces/ts.codefix";
6+
7+
const fixId = "removeUnnecessaryCast";
8+
const errorCodes = [
9+
Diagnostics.Type_cast_has_no_effect_on_the_type_of_this_expression.code,
10+
];
11+
12+
registerCodeFix({
13+
errorCodes,
14+
getCodeActions: function getCodeActionsToRemoveUnnecessaryCast(context) {
15+
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span));
16+
if (changes.length > 0) {
17+
return [createCodeFixAction(fixId, changes, Diagnostics.Remove_unnecessary_type_cast, fixId, Diagnostics.Remove_all_unnecessary_type_casts)];
18+
}
19+
},
20+
fixIds: [fixId],
21+
getAllCodeActions: context => {
22+
return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag));
23+
},
24+
});
25+
26+
function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, span: TextSpan) {
27+
let node: Node | undefined = findAncestorMatchingSpan(sourceFile, span);
28+
if (node && isInJSDoc(node)) {
29+
node = findAncestor(node, isParenthesizedExpression);
30+
if (node) {
31+
changeTracker.deleteNodeRange(sourceFile, first((node as HasJSDoc).jsDoc!), last((node as HasJSDoc).jsDoc!));
32+
if (!needsParentheses((node as ParenthesizedExpression).expression)) {
33+
changeTracker.replaceNode(sourceFile, node, (node as ParenthesizedExpression).expression);
34+
}
35+
}
36+
return;
37+
}
38+
const castExpr = tryCast(node, isAssertionExpression);
39+
if (!castExpr) {
40+
return;
41+
}
42+
43+
changeTracker.replaceNode(sourceFile, castExpr, castExpr.expression);
44+
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7120,6 +7120,7 @@ declare namespace ts {
71207120
noImplicitReturns?: boolean;
71217121
noImplicitThis?: boolean;
71227122
noStrictGenericChecks?: boolean;
7123+
noUnnecessaryCasts?: boolean;
71237124
noUnusedLocals?: boolean;
71247125
noUnusedParameters?: boolean;
71257126
noImplicitUseStrict?: boolean;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3182,6 +3182,7 @@ declare namespace ts {
31823182
noImplicitReturns?: boolean;
31833183
noImplicitThis?: boolean;
31843184
noStrictGenericChecks?: boolean;
3185+
noUnnecessaryCasts?: boolean;
31853186
noUnusedLocals?: boolean;
31863187
noUnusedParameters?: boolean;
31873188
noImplicitUseStrict?: boolean;

tests/baselines/reference/config/initTSConfig/Default initialized TSConfig/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8888
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8989
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "noUnnecessaryCasts": true, /* Raise an error when a type cast does not affect the type of an expression. */
9091
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
9192
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
9293
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with --help/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8888
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8989
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "noUnnecessaryCasts": true, /* Raise an error when a type cast does not affect the type of an expression. */
9091
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
9192
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
9293
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with --watch/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8888
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8989
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "noUnnecessaryCasts": true, /* Raise an error when a type cast does not affect the type of an expression. */
9091
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
9192
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
9293
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with advanced options/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8888
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8989
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "noUnnecessaryCasts": true, /* Raise an error when a type cast does not affect the type of an expression. */
9091
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
9192
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
9293
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with boolean value compiler options/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8888
"noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8989
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "noUnnecessaryCasts": true, /* Raise an error when a type cast does not affect the type of an expression. */
9091
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
9192
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
9293
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with enum value compiler options/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8888
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8989
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "noUnnecessaryCasts": true, /* Raise an error when a type cast does not affect the type of an expression. */
9091
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
9192
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
9293
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with files options/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8888
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8989
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "noUnnecessaryCasts": true, /* Raise an error when a type cast does not affect the type of an expression. */
9091
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
9192
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
9293
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with incorrect compiler option value/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8888
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8989
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "noUnnecessaryCasts": true, /* Raise an error when a type cast does not affect the type of an expression. */
9091
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
9192
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
9293
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with incorrect compiler option/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8888
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8989
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "noUnnecessaryCasts": true, /* Raise an error when a type cast does not affect the type of an expression. */
9091
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
9192
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
9293
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with list compiler options with enum value/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8888
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8989
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "noUnnecessaryCasts": true, /* Raise an error when a type cast does not affect the type of an expression. */
9091
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
9192
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
9293
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */

tests/baselines/reference/config/initTSConfig/Initialized TSConfig with list compiler options/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
8888
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
8989
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
90+
// "noUnnecessaryCasts": true, /* Raise an error when a type cast does not affect the type of an expression. */
9091
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
9192
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
9293
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"compilerOptions": {
3+
"noUnnecessaryCasts": true
4+
}
5+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
tests/cases/conformance/additionalChecks/noUnnecessaryCasts1.ts(3,5): error TS80009: Type cast has no effect on the type of this expression.
2+
tests/cases/conformance/additionalChecks/noUnnecessaryCasts1.ts(8,19): error TS80009: Type cast has no effect on the type of this expression.
3+
tests/cases/conformance/additionalChecks/noUnnecessaryCasts1.ts(17,18): error TS80009: Type cast has no effect on the type of this expression.
4+
tests/cases/conformance/additionalChecks/noUnnecessaryCasts1.ts(18,18): error TS80009: Type cast has no effect on the type of this expression.
5+
6+
7+
==== tests/cases/conformance/additionalChecks/noUnnecessaryCasts1.ts (4 errors) ====
8+
const a = {};
9+
let b: {};
10+
b = a as {};
11+
~~~~~~~
12+
!!! error TS80009: Type cast has no effect on the type of this expression.
13+
interface Foo {
14+
x?: string;
15+
}
16+
const foo1: Foo = {x: "ok"} as Foo; // cast technically erases type information, not a no-op
17+
const foo2: Foo = foo1 as Foo;
18+
~~~~~~~~~~~
19+
!!! error TS80009: Type cast has no effect on the type of this expression.
20+
class A {
21+
item: any;
22+
}
23+
class B {
24+
item: any;
25+
}
26+
const aCls = new A();
27+
const bCls = new B();
28+
const aCls2: A = bCls as A;
29+
~~~~~~~~~
30+
!!! error TS80009: Type cast has no effect on the type of this expression.
31+
const bCls2: B = aCls as A;
32+
~~~~~~~~~
33+
!!! error TS80009: Type cast has no effect on the type of this expression.
34+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//// [noUnnecessaryCasts1.ts]
2+
const a = {};
3+
let b: {};
4+
b = a as {};
5+
interface Foo {
6+
x?: string;
7+
}
8+
const foo1: Foo = {x: "ok"} as Foo; // cast technically erases type information, not a no-op
9+
const foo2: Foo = foo1 as Foo;
10+
class A {
11+
item: any;
12+
}
13+
class B {
14+
item: any;
15+
}
16+
const aCls = new A();
17+
const bCls = new B();
18+
const aCls2: A = bCls as A;
19+
const bCls2: B = aCls as A;
20+
21+
22+
//// [noUnnecessaryCasts1.js]
23+
var a = {};
24+
var b;
25+
b = a;
26+
var foo1 = { x: "ok" }; // cast technically erases type information, not a no-op
27+
var foo2 = foo1;
28+
var A = /** @class */ (function () {
29+
function A() {
30+
}
31+
return A;
32+
}());
33+
var B = /** @class */ (function () {
34+
function B() {
35+
}
36+
return B;
37+
}());
38+
var aCls = new A();
39+
var bCls = new B();
40+
var aCls2 = bCls;
41+
var bCls2 = aCls;

0 commit comments

Comments
 (0)