Skip to content

Commit 8eb3822

Browse files
authored
Merge pull request #28290 from rflorian/add-codefix-cannot-find-name-in-for-loop
Add codefix for 'Cannot find name' diagnostic
2 parents 6839973 + 196db5b commit 8eb3822

22 files changed

+313
-0
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5079,6 +5079,14 @@
50795079
"category": "Message",
50805080
"code": 95080
50815081
},
5082+
"Add 'const' to unresolved variable": {
5083+
"category": "Message",
5084+
"code": 95081
5085+
},
5086+
"Add 'const' to all unresolved variables": {
5087+
"category": "Message",
5088+
"code": 95082
5089+
},
50825090
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
50835091
"category": "Error",
50845092
"code": 18004
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixId = "addMissingConst";
4+
const errorCodes = [
5+
Diagnostics.Cannot_find_name_0.code,
6+
Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer.code
7+
];
8+
9+
registerCodeFix({
10+
errorCodes,
11+
getCodeActions: (context) => {
12+
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start, context.program));
13+
if (changes.length > 0) {
14+
return [createCodeFixAction(fixId, changes, Diagnostics.Add_const_to_unresolved_variable, fixId, Diagnostics.Add_const_to_all_unresolved_variables)];
15+
}
16+
},
17+
fixIds: [fixId],
18+
getAllCodeActions: context => {
19+
const fixedNodes = new NodeSet();
20+
return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag.start, context.program, fixedNodes));
21+
},
22+
});
23+
24+
function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, program: Program, fixedNodes?: NodeSet<Node>) {
25+
const token = getTokenAtPosition(sourceFile, pos);
26+
const forInitializer = findAncestor(token, node =>
27+
isForInOrOfStatement(node.parent) ? node.parent.initializer === node :
28+
isPossiblyPartOfDestructuring(node) ? false : "quit"
29+
);
30+
if (forInitializer) return applyChange(changeTracker, forInitializer, sourceFile, fixedNodes);
31+
32+
const parent = token.parent;
33+
if (isBinaryExpression(parent) && isExpressionStatement(parent.parent)) {
34+
return applyChange(changeTracker, token, sourceFile, fixedNodes);
35+
}
36+
37+
if (isArrayLiteralExpression(parent)) {
38+
const checker = program.getTypeChecker();
39+
if (!every(parent.elements, element => arrayElementCouldBeVariableDeclaration(element, checker))) {
40+
return;
41+
}
42+
43+
return applyChange(changeTracker, parent, sourceFile, fixedNodes);
44+
}
45+
46+
const commaExpression = findAncestor(token, node =>
47+
isExpressionStatement(node.parent) ? true :
48+
isPossiblyPartOfCommaSeperatedInitializer(node) ? false : "quit"
49+
);
50+
if (commaExpression) {
51+
const checker = program.getTypeChecker();
52+
if (!expressionCouldBeVariableDeclaration(commaExpression, checker)) {
53+
return;
54+
}
55+
56+
return applyChange(changeTracker, commaExpression, sourceFile, fixedNodes);
57+
}
58+
}
59+
60+
function applyChange(changeTracker: textChanges.ChangeTracker, initializer: Node, sourceFile: SourceFile, fixedNodes?: NodeSet<Node>) {
61+
if (!fixedNodes || fixedNodes.tryAdd(initializer)) {
62+
changeTracker.insertModifierBefore(sourceFile, SyntaxKind.ConstKeyword, initializer);
63+
}
64+
}
65+
66+
function isPossiblyPartOfDestructuring(node: Node): boolean {
67+
switch (node.kind) {
68+
case SyntaxKind.Identifier:
69+
case SyntaxKind.ArrayLiteralExpression:
70+
case SyntaxKind.ObjectLiteralExpression:
71+
case SyntaxKind.PropertyAssignment:
72+
case SyntaxKind.ShorthandPropertyAssignment:
73+
return true;
74+
default:
75+
return false;
76+
}
77+
}
78+
79+
function arrayElementCouldBeVariableDeclaration(expression: Expression, checker: TypeChecker): boolean {
80+
const identifier =
81+
isIdentifier(expression) ? expression :
82+
isAssignmentExpression(expression, /*excludeCompoundAssignment*/ true) && isIdentifier(expression.left) ? expression.left :
83+
undefined;
84+
return !!identifier && !checker.getSymbolAtLocation(identifier);
85+
}
86+
87+
function isPossiblyPartOfCommaSeperatedInitializer(node: Node): boolean {
88+
switch (node.kind) {
89+
case SyntaxKind.Identifier:
90+
case SyntaxKind.BinaryExpression:
91+
case SyntaxKind.CommaToken:
92+
return true;
93+
default:
94+
return false;
95+
}
96+
}
97+
98+
function expressionCouldBeVariableDeclaration(expression: Node, checker: TypeChecker): boolean {
99+
if (!isBinaryExpression(expression)) {
100+
return false;
101+
}
102+
103+
if (expression.operatorToken.kind === SyntaxKind.CommaToken) {
104+
return every([expression.left, expression.right], expression => expressionCouldBeVariableDeclaration(expression, checker));
105+
}
106+
107+
return isIdentifier(expression.left) && !checker.getSymbolAtLocation(expression.left);
108+
}
109+
}

src/services/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"codeFixProvider.ts",
4646
"refactorProvider.ts",
4747
"codefixes/addConvertToUnknownForNonOverlappingTypes.ts",
48+
"codefixes/addMissingConst.ts",
4849
"codefixes/addMissingInvocationForDecorator.ts",
4950
"codefixes/addNameToNamelessParameter.ts",
5051
"codefixes/annotateWithTypeFromJSDoc.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+
////for (x in []) {}
4+
5+
verify.codeFix({
6+
description: "Add 'const' to unresolved variable",
7+
newFileContent: "for (const x in []) {}"
8+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////for (x in []) {}
4+
////for (y in []) {}
5+
6+
verify.codeFixAll({
7+
fixId: "addMissingConst",
8+
fixAllDescription: "Add 'const' to all unresolved variables",
9+
newFileContent:
10+
`for (const x in []) {}
11+
for (const y in []) {}`
12+
});
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+
////for ([x] of [[1,2]]) {}
4+
5+
verify.codeFix({
6+
description: "Add 'const' to unresolved variable",
7+
newFileContent: "for (const [x] of [[1,2]]) {}"
8+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////for ([x, y] of [[1,2]]) {}
4+
////for ([x] of [[1,2]]) {}
5+
6+
verify.codeFixAll({
7+
fixId: "addMissingConst",
8+
fixAllDescription: "Add 'const' to all unresolved variables",
9+
newFileContent:
10+
`for (const [x, y] of [[1,2]]) {}
11+
for (const [x] of [[1,2]]) {}`
12+
});
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+
////for ({ x } of [{ x: 0 }]) { }
4+
5+
verify.codeFix({
6+
description: "Add 'const' to unresolved variable",
7+
newFileContent: "for (const { x } of [{ x: 0 }]) { }"
8+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////for ({ x, y } of [{ x: 0, y: 1 }]) { }
4+
////for ({ x } of [{ x: 0 }]) { }
5+
6+
verify.codeFixAll({
7+
fixId: "addMissingConst",
8+
fixAllDescription: "Add 'const' to all unresolved variables",
9+
newFileContent:
10+
`for (const { x, y } of [{ x: 0, y: 1 }]) { }
11+
for (const { x } of [{ x: 0 }]) { }`
12+
});
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+
////for (x of []) {}
4+
5+
verify.codeFix({
6+
description: "Add 'const' to unresolved variable",
7+
newFileContent: "for (const x of []) {}"
8+
});

0 commit comments

Comments
 (0)