Skip to content

Commit 4598943

Browse files
committed
Add codefix for 'convert to unknown' diagnostic
Codefix for diagnostic error 2352: "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." Add codefix for both AsExpressions and TypeAssertions
1 parent c780186 commit 4598943

12 files changed

+137
-0
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4724,5 +4724,13 @@
47244724
"Generate types for all packages without types": {
47254725
"category": "Message",
47264726
"code": 95068
4727+
},
4728+
"Add 'unknown' conversion for non-overlapping types": {
4729+
"category": "Message",
4730+
"code": 95069
4731+
},
4732+
"Add 'unknown' to all conversions of non-overlapping types": {
4733+
"category": "Message",
4734+
"code": 95070
47274735
}
47284736
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixId = "addConvertToUnknownForNonOverlappingTypes";
4+
const errorCodes = [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.code];
5+
registerCodeFix({
6+
errorCodes,
7+
getCodeActions: (context) => {
8+
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start));
9+
return [createCodeFixAction(fixId, changes, Diagnostics.Add_unknown_conversion_for_non_overlapping_types, fixId, Diagnostics.Add_unknown_to_all_conversions_of_non_overlapping_types)];
10+
},
11+
fixIds: [fixId],
12+
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag.start)),
13+
});
14+
15+
function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) {
16+
const token = getTokenAtPosition(sourceFile, pos);
17+
18+
const asExpression = findAncestor<AsExpression>(token, isAsExpression)!;
19+
if (!!asExpression) {
20+
Debug.assert(!!asExpression, "Expected position to be owned by an as-expression.");
21+
22+
const nodeBeingCast = asExpression.getChildAt(0);
23+
const expressionBeingCast = findAncestor<Expression>(nodeBeingCast, isExpression)!;
24+
Debug.assert(!!expressionBeingCast, "Expected position to be owned by an expression.");
25+
26+
const replacement = createAsExpression(expressionBeingCast, createKeywordTypeNode(SyntaxKind.UnknownKeyword));
27+
changeTracker.replaceNode(sourceFile, expressionBeingCast, replacement);
28+
}
29+
30+
const typeAssertion = findAncestor<TypeAssertion>(token, isTypeAssertion)!;
31+
if (!!typeAssertion) {
32+
Debug.assert(!!typeAssertion, "Expected position to be owned by a type assertion.");
33+
34+
const nodeBeingCast = typeAssertion.getLastToken();
35+
const expressionBeingCast = findAncestor<Expression>(nodeBeingCast, isExpression)!;
36+
Debug.assert(!!expressionBeingCast, "Expected position to be owned by an expression.");
37+
38+
const replacement = createTypeAssertion(createKeywordTypeNode(SyntaxKind.UnknownKeyword), expressionBeingCast);
39+
changeTracker.replaceNode(sourceFile, expressionBeingCast, replacement);
40+
}
41+
}
42+
}

src/services/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"textChanges.ts",
4444
"codeFixProvider.ts",
4545
"refactorProvider.ts",
46+
"codefixes/addConvertToUnknownForNonOverlappingTypes.ts",
4647
"codefixes/addMissingInvocationForDecorator.ts",
4748
"codefixes/annotateWithTypeFromJSDoc.ts",
4849
"codefixes/convertFunctionToEs6Class.ts",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////[|0 as string|]
4+
5+
verify.codeFix({
6+
description: "Add 'unknown' conversion for non-overlapping types",
7+
newRangeContent: `0 as unknown as string`
8+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////[|0 * (4 + 3) / 100 as string|]
4+
5+
verify.codeFix({
6+
description: "Add 'unknown' conversion for non-overlapping types",
7+
newRangeContent: `0 * (4 + 3) / 100 as unknown as string`
8+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////[|["words"] as string|]
4+
5+
verify.codeFix({
6+
description: "Add 'unknown' conversion for non-overlapping types",
7+
newRangeContent: `["words"] as unknown as string`
8+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////[|"words" as object|]
4+
5+
verify.codeFix({
6+
description: "Add 'unknown' conversion for non-overlapping types",
7+
newRangeContent: `"words" as unknown as object`
8+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////[|<string>0|]
4+
5+
verify.codeFix({
6+
description: "Add 'unknown' conversion for non-overlapping types",
7+
newRangeContent: `<string><unknown>0`
8+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////[|<string>0 * (4 + 3) / 100|]
4+
5+
verify.codeFix({
6+
description: "Add 'unknown' conversion for non-overlapping types",
7+
newRangeContent: `<string><unknown>0 * (4 + 3) / 100`
8+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////[|<string>["words"]|]
4+
5+
verify.codeFix({
6+
description: "Add 'unknown' conversion for non-overlapping types",
7+
newRangeContent: `<string><unknown>["words"]`
8+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////[|<object>"words"|]
4+
5+
verify.codeFix({
6+
description: "Add 'unknown' conversion for non-overlapping types",
7+
newRangeContent: `<object><unknown>"words"`
8+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////class C {
4+
//// const s1 = 1 as string;
5+
//// const o1 = s + " word" as object;
6+
////
7+
//// const s2 = <string>2;
8+
//// const o2 = <object>s2;
9+
////}
10+
11+
verify.codeFixAll({
12+
fixId: "addConvertToUnknownForNonOverlappingTypes",
13+
fixAllDescription: "Add 'unknown' to all conversions of non-overlapping types",
14+
newFileContent:
15+
`class C {
16+
const s1 = 1 as unknown as string;
17+
const o1 = s + " word" as unknown as object;
18+
19+
const s2 = <string><unknown>2;
20+
const o2 = <object>s2;
21+
}`
22+
});

0 commit comments

Comments
 (0)