diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 85ad60b9850..cc6e8922c64 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4800,11 +4800,11 @@ "category": "Message", "code": 95073 }, - "Add const modifier to unresolved variable": { + "Add 'const' to unresolved variable": { "category": "Message", "code": 95074 }, - "Add const modifiers to all unresolved variables": { + "Add 'const' to all unresolved variables": { "category": "Message", "code": 95075 } diff --git a/src/services/codefixes/addMissingConstInForLoop.ts b/src/services/codefixes/addMissingConstInForLoop.ts index cd4507729de..d1534c7020f 100644 --- a/src/services/codefixes/addMissingConstInForLoop.ts +++ b/src/services/codefixes/addMissingConstInForLoop.ts @@ -6,18 +6,48 @@ namespace ts.codefix { errorCodes, getCodeActions: (context) => { const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start)); - return [createCodeFixAction(fixId, changes, Diagnostics.Add_const_modifier_to_unresolved_variable, fixId, Diagnostics.Add_const_modifiers_to_all_unresolved_variables)]; + if (changes) { + return [createCodeFixAction(fixId, changes, Diagnostics.Add_const_to_unresolved_variable, fixId, Diagnostics.Add_const_to_all_unresolved_variables)]; + } }, fixIds: [fixId], getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag.start)), }); function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) { - const token = getTokenAtPosition(sourceFile, pos); - // fails on 'for ([x, y] of [[1,2]]) {}' when called for y - // since findPrecedingMatchingToken does not return the open paren here after iterating over another identifier (x) - const openParenToken = findPrecedingMatchingToken(token, SyntaxKind.OpenParenToken, sourceFile); - Debug.assert(!!openParenToken, "openParenToken must be defined"); - changeTracker.insertNodeAt(sourceFile, openParenToken.getEnd(), createToken(SyntaxKind.ConstKeyword), { suffix: " " }); + const forInitializer = findAncestor(getTokenAtPosition(sourceFile, pos), node => + isForInOrOfStatement(node.parent) ? node.parent.initializer === node + : isPossiblyPartOfDestructuring(node) ? false : "quit"); + if (!forInitializer) return; + if (alreadyContainsConstCodeFixForInitializer(changeTracker, forInitializer, sourceFile)) return; + changeTracker.insertNodeBefore(sourceFile, forInitializer, createToken(SyntaxKind.ConstKeyword)); + } + + function isPossiblyPartOfDestructuring(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + return true; + default: + return false; + } + } + + function alreadyContainsConstCodeFixForInitializer(changeTracker: textChanges.ChangeTracker, forInitializer: Node, sourceFile: SourceFile): boolean { + return changeTracker.getChanges().some(change => { + const textChanges = change.textChanges; + if (!textChanges) return false; + return textChanges.some(textChange => { + if (textChange.newText !== "const ") return false; + const changeStart = textChange.span.start; + const changeEnd = changeStart + textChange.span.length; + const initStart = forInitializer.getStart(sourceFile); + const initEnd = forInitializer.getEnd(); + return initStart <= changeEnd && changeStart <= initEnd; + }); + }); } } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 9c773f73f43..86076ecb877 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -403,6 +403,9 @@ namespace ts.textChanges { else if (isStringLiteral(before) && isImportDeclaration(before.parent) || isNamedImports(before)) { return { suffix: ", " }; } + else if (isForInitializer(before)) { + return { suffix: " " }; + } return Debug.failBadSyntaxKind(before); // We haven't handled this kind of node yet -- add it } diff --git a/tests/cases/fourslash/codeFixAddMissingConstInForInLoop1.ts b/tests/cases/fourslash/codeFixAddMissingConstInForInLoop1.ts new file mode 100644 index 00000000000..76b752465a9 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingConstInForInLoop1.ts @@ -0,0 +1,8 @@ +/// + +////for (x in []) {} + +verify.codeFix({ + description: "Add 'const' to unresolved variable", + newFileContent: "for (const x in []) {}" +}); diff --git a/tests/cases/fourslash/codeFixAddMissingConstInForInLoop2.ts b/tests/cases/fourslash/codeFixAddMissingConstInForInLoop2.ts new file mode 100644 index 00000000000..d101f384044 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingConstInForInLoop2.ts @@ -0,0 +1,12 @@ +/// + +////for (x in []) {} +////for (y in []) {} + +verify.codeFixAll({ + fixId: "addMissingConstInForLoop", + fixAllDescription: "Add 'const' to all unresolved variables", + newFileContent: +`for (const x in []) {} +for (const y in []) {}` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingConstInForLoop1.ts b/tests/cases/fourslash/codeFixAddMissingConstInForLoop1.ts deleted file mode 100644 index be35538b59b..00000000000 --- a/tests/cases/fourslash/codeFixAddMissingConstInForLoop1.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// - -////[|for (x of []) {}|] - -verify.codeFix({ - description: "Add const modifier to unresolved variable", - newRangeContent: "for (const x of []) {}" -}); diff --git a/tests/cases/fourslash/codeFixAddMissingConstInForLoop3.ts b/tests/cases/fourslash/codeFixAddMissingConstInForLoop3.ts deleted file mode 100644 index 388ab0b5503..00000000000 --- a/tests/cases/fourslash/codeFixAddMissingConstInForLoop3.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// - -////[|for ([x] of [[1,2]]) {}|] - -verify.codeFix({ - description: "Add const modifier to unresolved variable", - newRangeContent: "for (const [x] of [[1,2]]) {}" -}); diff --git a/tests/cases/fourslash/codeFixAddMissingConstInForLoop4.ts b/tests/cases/fourslash/codeFixAddMissingConstInForLoop4.ts deleted file mode 100644 index 4e57f453cfd..00000000000 --- a/tests/cases/fourslash/codeFixAddMissingConstInForLoop4.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// - -////[|for ([x, y] of [[1,2]]) {}|] - -verify.codeFix({ - description: "Add const modifier to unresolved variable", - newRangeContent: "for (const [x, y] of [[1,2]]) {}" -}); diff --git a/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithArrayDestructuring1.ts b/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithArrayDestructuring1.ts new file mode 100644 index 00000000000..791615f5ea0 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithArrayDestructuring1.ts @@ -0,0 +1,8 @@ +/// + +////for ([x] of [[1,2]]) {} + +verify.codeFix({ + description: "Add 'const' to unresolved variable", + newFileContent: "for (const [x] of [[1,2]]) {}" +}); diff --git a/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithArrayDestructuring2.ts b/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithArrayDestructuring2.ts new file mode 100644 index 00000000000..734b53ed4fb --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithArrayDestructuring2.ts @@ -0,0 +1,12 @@ +/// + +////for ([x, y] of [[1,2]]) {} +////for ([x] of [[1,2]]) {} + +verify.codeFixAll({ + fixId: "addMissingConstInForLoop", + fixAllDescription: "Add 'const' to all unresolved variables", + newFileContent: +`for (const [x, y] of [[1,2]]) {} +for (const [x] of [[1,2]]) {}` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithObjectDestructuring1.ts b/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithObjectDestructuring1.ts new file mode 100644 index 00000000000..f926091da80 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithObjectDestructuring1.ts @@ -0,0 +1,8 @@ +/// + +////for ({ x } of [{ x: 0 }]) { } + +verify.codeFix({ + description: "Add 'const' to unresolved variable", + newFileContent: "for (const { x } of [{ x: 0 }]) { }" +}); diff --git a/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithObjectDestructuring2.ts b/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithObjectDestructuring2.ts new file mode 100644 index 00000000000..82cd2db6dc2 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingConstInForLoopWithObjectDestructuring2.ts @@ -0,0 +1,12 @@ +/// + +////for ({ x, y } of [{ x: 0, y: 1 }]) { } +////for ({ x } of [{ x: 0 }]) { } + +verify.codeFixAll({ + fixId: "addMissingConstInForLoop", + fixAllDescription: "Add 'const' to all unresolved variables", + newFileContent: +`for (const { x, y } of [{ x: 0, y: 1 }]) { } +for (const { x } of [{ x: 0 }]) { }` +}); diff --git a/tests/cases/fourslash/codeFixAddMissingConstInForOfLoop1.ts b/tests/cases/fourslash/codeFixAddMissingConstInForOfLoop1.ts new file mode 100644 index 00000000000..49efe2bc9a6 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingConstInForOfLoop1.ts @@ -0,0 +1,8 @@ +/// + +////for (x of []) {} + +verify.codeFix({ + description: "Add 'const' to unresolved variable", + newFileContent: "for (const x of []) {}" +}); diff --git a/tests/cases/fourslash/codeFixAddMissingConstInForLoop2.ts b/tests/cases/fourslash/codeFixAddMissingConstInForOfLoop2.ts similarity index 55% rename from tests/cases/fourslash/codeFixAddMissingConstInForLoop2.ts rename to tests/cases/fourslash/codeFixAddMissingConstInForOfLoop2.ts index ce79b68a769..3dbab65de32 100644 --- a/tests/cases/fourslash/codeFixAddMissingConstInForLoop2.ts +++ b/tests/cases/fourslash/codeFixAddMissingConstInForOfLoop2.ts @@ -1,11 +1,11 @@ /// -////[|for (x of []) {}|] -////[|for (y of []) {}|] +////for (x of []) {} +////for (y of []) {} verify.codeFixAll({ fixId: "addMissingConstInForLoop", - fixAllDescription: "Add const modifiers to all unresolved variables", + fixAllDescription: "Add 'const' to all unresolved variables", newFileContent: `for (const x of []) {} for (const y of []) {}` diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction4.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction4.ts index dd123e25d0b..24506bb39cb 100644 --- a/tests/cases/fourslash/codeFixAwaitInSyncFunction4.ts +++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction4.ts @@ -1,7 +1,7 @@ /// ////class Foo { -//// constructor { +//// constructor() { //// await Promise.resolve(); //// } ////} diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction7.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction7.ts index a467e9ee0ce..1d058fe473b 100644 --- a/tests/cases/fourslash/codeFixAwaitInSyncFunction7.ts +++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction7.ts @@ -1,7 +1,7 @@ /// ////function f() { -//// for await (const x of g()) { +//// for await (const x of []) { //// console.log(x); //// } ////} @@ -10,7 +10,7 @@ verify.codeFix({ description: "Add async modifier to containing function", newFileContent: `async function f() { - for await (const x of g()) { + for await (const x of []) { console.log(x); } }`, diff --git a/tests/cases/fourslash/codeFixAwaitShouldNotCrashIfNotInFunction.ts b/tests/cases/fourslash/codeFixAwaitShouldNotCrashIfNotInFunction.ts index 1ce5c771ed4..4d916fe25b6 100644 --- a/tests/cases/fourslash/codeFixAwaitShouldNotCrashIfNotInFunction.ts +++ b/tests/cases/fourslash/codeFixAwaitShouldNotCrashIfNotInFunction.ts @@ -1,5 +1,6 @@ /// +////async function a() {} ////await a verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNoFix.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNoFix.ts index 07896b4cdb3..39470dfdd50 100644 --- a/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNoFix.ts +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceIndexSignaturesNoFix.ts @@ -4,7 +4,7 @@ //// [x: string, y: number]: number; //// } //// -//// class C implements I {[| |]} +//// class C implements I4 {[| |]} verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused.ts index 4f3f9a3e61e..97556092ade 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused.ts @@ -4,7 +4,7 @@ // @noUnusedParameters: true ////export {}; -////const { x, y } = o; +////const { x, y } = [{}]; verify.codeFix({ description: "Remove destructuring", diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_all.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_all.ts index c9d4f62f2db..066ba07c4f4 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_all.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_all.ts @@ -3,8 +3,8 @@ // @noUnusedLocals: true // @noUnusedParameters: true -////const { x, y } = o; -////const { a, b } = o; +////const { x, y } = [{}]; +////const { a, b } = [{}]; ////a; ////export function f({ a, b }, { x, y }) { //// a; @@ -14,7 +14,7 @@ verify.codeFixAll({ fixId: "unusedIdentifier_delete", fixAllDescription: "Delete all unused declarations", newFileContent: -`const { a } = o; +`const { a } = [{}]; a; export function f({ a }) { a; diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_for.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_for.ts index 27103357d3d..ea94e5275aa 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_for.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_for.ts @@ -3,10 +3,10 @@ // @noUnusedLocals: true // @noUnusedParameters: true -////for (const { x } of o) {} +////for (const { x } of [{}]) {} verify.codeFix({ description: "Remove destructuring", newFileContent: -`for (const {} of o) {}`, +`for (const {} of [{}]) {}`, }); diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_nested.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_nested.ts index 73fca113928..dc1ae26b3d0 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_nested.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_nested.ts @@ -4,11 +4,11 @@ // @noUnusedParameters: true ////export {}; -////const { x: { a, b } } = o; +////const { x: { a, b } } = [{}]; verify.codeFix({ description: "Remove destructuring", newFileContent: `export {}; -const { } = o;`, +const { } = [{}];`, }); diff --git a/tests/cases/fourslash/incompleteFunctionCallCodefix3.ts b/tests/cases/fourslash/incompleteFunctionCallCodefix3.ts index 29072c7c855..db2e82f94bf 100644 --- a/tests/cases/fourslash/incompleteFunctionCallCodefix3.ts +++ b/tests/cases/fourslash/incompleteFunctionCallCodefix3.ts @@ -3,4 +3,6 @@ // @noImplicitAny: true //// function ...q) {}} f(10); -verify.not.codeFixAvailable(); +verify.not.codeFixAvailable([ + { "description": "Infer parameter types from usage" } +]);