diff --git a/src/services/refactors/inlineVariable.ts b/src/services/refactors/inlineVariable.ts index 0bcf841ce39e2..d18b94f04472b 100644 --- a/src/services/refactors/inlineVariable.ts +++ b/src/services/refactors/inlineVariable.ts @@ -24,6 +24,9 @@ import { isObjectLiteralExpression, isPropertyAccessExpression, isShorthandPropertyAssignment, + isStringLiteral, + isTaggedTemplateExpression, + isTemplateSpan, isTypeQueryNode, isVariableDeclarationInVariableStatement, isVariableStatement, @@ -33,11 +36,14 @@ import { refactor, some, SourceFile, + StringLiteral, SymbolFlags, + TemplateSpan, textChanges, textRangeContainsPositionInclusive, TypeChecker, VariableDeclaration, + walkUpParenthesizedExpressions, } from "../_namespaces/ts.js"; import { RefactorErrorInfo, @@ -115,7 +121,13 @@ registerRefactor(refactorName, { const { references, declaration, replacement } = info; const edits = textChanges.ChangeTracker.with(context, tracker => { for (const node of references) { - tracker.replaceNode(file, node, getReplacementExpression(node, replacement)); + const closestStringIdentifierParent = isStringLiteral(replacement) && isIdentifier(node) && walkUpParenthesizedExpressions(node.parent); + if (closestStringIdentifierParent && isTemplateSpan(closestStringIdentifierParent) && !isTaggedTemplateExpression(closestStringIdentifierParent.parent.parent)) { + replaceTemplateStringVariableWithLiteral(tracker, file, closestStringIdentifierParent, replacement); + } + else { + tracker.replaceNode(file, node, getReplacementExpression(node, replacement)); + } } tracker.delete(file, declaration); }); @@ -255,3 +267,17 @@ function getReplacementExpression(reference: Node, replacement: Expression) { return replacement; } + +function replaceTemplateStringVariableWithLiteral(tracker: textChanges.ChangeTracker, sourceFile: SourceFile, reference: TemplateSpan, replacement: StringLiteral) { + const templateExpression = reference.parent; + const index = templateExpression.templateSpans.indexOf(reference); + const prevNode = index === 0 ? templateExpression.head : templateExpression.templateSpans[index - 1]; + tracker.replaceRangeWithText( + sourceFile, + { + pos: prevNode.getEnd() - 2, + end: reference.literal.getStart() + 1, + }, + replacement.text.replace(/\\/g, "\\\\").replace(/`/g, "\\`"), + ); +} diff --git a/tests/cases/fourslash/inlineVariableTemplateString1.ts b/tests/cases/fourslash/inlineVariableTemplateString1.ts new file mode 100644 index 0000000000000..48df225455a55 --- /dev/null +++ b/tests/cases/fourslash/inlineVariableTemplateString1.ts @@ -0,0 +1,13 @@ +/// + +////const /*a*/pizza/*b*/ = "🍕"; +////export const prompt = `Hello, would you like some ${pizza}?`; + +goTo.select("a", "b"); +verify.refactorAvailable("Inline variable"); +edit.applyRefactor({ + refactorName: "Inline variable", + actionName: "Inline variable", + actionDescription: "Inline variable", + newContent: "export const prompt = `Hello, would you like some 🍕?`;" +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlineVariableTemplateString10.ts b/tests/cases/fourslash/inlineVariableTemplateString10.ts new file mode 100644 index 0000000000000..b6409d3133bac --- /dev/null +++ b/tests/cases/fourslash/inlineVariableTemplateString10.ts @@ -0,0 +1,13 @@ +/// + +////const /*a*/message/*b*/ = "Hello, World!"; +////await $`echo ${((message))}`; + +goTo.select("a", "b"); +verify.refactorAvailable("Inline variable"); +edit.applyRefactor({ + refactorName: "Inline variable", + actionName: "Inline variable", + actionDescription: "Inline variable", + newContent: 'await $`echo ${(("Hello, World!"))}`;', +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlineVariableTemplateString11.ts b/tests/cases/fourslash/inlineVariableTemplateString11.ts new file mode 100644 index 0000000000000..d626b3e4cf5f4 --- /dev/null +++ b/tests/cases/fourslash/inlineVariableTemplateString11.ts @@ -0,0 +1,13 @@ +/// + +////const /*a*/pizza/*b*/ = "🍕"; +////export const prompt = `Hello, would you like some ${((pizza))}?`; + +goTo.select("a", "b"); +verify.refactorAvailable("Inline variable"); +edit.applyRefactor({ + refactorName: "Inline variable", + actionName: "Inline variable", + actionDescription: "Inline variable", + newContent: "export const prompt = `Hello, would you like some 🍕?`;" +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlineVariableTemplateString2.ts b/tests/cases/fourslash/inlineVariableTemplateString2.ts new file mode 100644 index 0000000000000..0984278c1357c --- /dev/null +++ b/tests/cases/fourslash/inlineVariableTemplateString2.ts @@ -0,0 +1,13 @@ +/// + +////const /*a*/codeText/*b*/ = "Code-formatted text looks `like this` and requires surrounding by backticks (\\`)."; +////export const mdTutorial = `Let's talk about markdown.\n${codeText}?`; + +goTo.select("a", "b"); +verify.refactorAvailable("Inline variable"); +edit.applyRefactor({ + refactorName: "Inline variable", + actionName: "Inline variable", + actionDescription: "Inline variable", + newContent: "export const mdTutorial = `Let's talk about markdown.\\nCode-formatted text looks \\`like this\\` and requires surrounding by backticks (\\\\\\\`).?`;" +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlineVariableTemplateString3.ts b/tests/cases/fourslash/inlineVariableTemplateString3.ts new file mode 100644 index 0000000000000..9851f87961774 --- /dev/null +++ b/tests/cases/fourslash/inlineVariableTemplateString3.ts @@ -0,0 +1,15 @@ +/// + +////const /*a*/pizza/*b*/ = "🍕"; +////export const prompt = `Hello, would you like some ${ +//// pizza +//// }?`; + +goTo.select("a", "b"); +verify.refactorAvailable("Inline variable"); +edit.applyRefactor({ + refactorName: "Inline variable", + actionName: "Inline variable", + actionDescription: "Inline variable", + newContent: "export const prompt = `Hello, would you like some 🍕?`;" +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlineVariableTemplateString4.ts b/tests/cases/fourslash/inlineVariableTemplateString4.ts new file mode 100644 index 0000000000000..9a0dc1805f5a1 --- /dev/null +++ b/tests/cases/fourslash/inlineVariableTemplateString4.ts @@ -0,0 +1,13 @@ +/// + +////const /*a*/pizza/*b*/ = "🍕"; +////export const prompt = `Hello, would you like some ${pizza} or ${pizza}?`; + +goTo.select("a", "b"); +verify.refactorAvailable("Inline variable"); +edit.applyRefactor({ + refactorName: "Inline variable", + actionName: "Inline variable", + actionDescription: "Inline variable", + newContent: "export const prompt = `Hello, would you like some 🍕 or 🍕?`;" +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlineVariableTemplateString5.ts b/tests/cases/fourslash/inlineVariableTemplateString5.ts new file mode 100644 index 0000000000000..fd1f1c510b605 --- /dev/null +++ b/tests/cases/fourslash/inlineVariableTemplateString5.ts @@ -0,0 +1,13 @@ +/// + +////const /*a*/pizza/*b*/ = "🍕"; +////export const prompt = `Hello, would you like some ${pizza}`; + +goTo.select("a", "b"); +verify.refactorAvailable("Inline variable"); +edit.applyRefactor({ + refactorName: "Inline variable", + actionName: "Inline variable", + actionDescription: "Inline variable", + newContent: "export const prompt = `Hello, would you like some 🍕`;" +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlineVariableTemplateString6.ts b/tests/cases/fourslash/inlineVariableTemplateString6.ts new file mode 100644 index 0000000000000..9d04b8f4b292f --- /dev/null +++ b/tests/cases/fourslash/inlineVariableTemplateString6.ts @@ -0,0 +1,13 @@ +/// + +////const /*a*/x/*b*/ = "\\`"; +////export const y = `${x}`; + +goTo.select("a", "b"); +verify.refactorAvailable("Inline variable"); +edit.applyRefactor({ + refactorName: "Inline variable", + actionName: "Inline variable", + actionDescription: "Inline variable", + newContent: "export const y = `\\\\\\``;" +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlineVariableTemplateString7.ts b/tests/cases/fourslash/inlineVariableTemplateString7.ts new file mode 100644 index 0000000000000..c0315aa54b9e7 --- /dev/null +++ b/tests/cases/fourslash/inlineVariableTemplateString7.ts @@ -0,0 +1,13 @@ +/// + +////const /*a*/x/*b*/ = "`"; +////export const y = `${x}`; + +goTo.select("a", "b"); +verify.refactorAvailable("Inline variable"); +edit.applyRefactor({ + refactorName: "Inline variable", + actionName: "Inline variable", + actionDescription: "Inline variable", + newContent: "export const y = `\\``;" +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlineVariableTemplateString8.ts b/tests/cases/fourslash/inlineVariableTemplateString8.ts new file mode 100644 index 0000000000000..82e0abb8562e4 --- /dev/null +++ b/tests/cases/fourslash/inlineVariableTemplateString8.ts @@ -0,0 +1,13 @@ +/// + +////const /*a*/message/*b*/ = "Hello, World!"; +////await $`echo ${message}`; + +goTo.select("a", "b"); +verify.refactorAvailable("Inline variable"); +edit.applyRefactor({ + refactorName: "Inline variable", + actionName: "Inline variable", + actionDescription: "Inline variable", + newContent: 'await $`echo ${"Hello, World!"}`;', +}); \ No newline at end of file diff --git a/tests/cases/fourslash/inlineVariableTemplateString9.ts b/tests/cases/fourslash/inlineVariableTemplateString9.ts new file mode 100644 index 0000000000000..fc6ea1847964c --- /dev/null +++ b/tests/cases/fourslash/inlineVariableTemplateString9.ts @@ -0,0 +1,13 @@ +/// + +////const /*a*/message/*b*/ = "Hello, World!"; +////await $`echo ${(message)}`; + +goTo.select("a", "b"); +verify.refactorAvailable("Inline variable"); +edit.applyRefactor({ + refactorName: "Inline variable", + actionName: "Inline variable", + actionDescription: "Inline variable", + newContent: 'await $`echo ${("Hello, World!")}`;', +}); \ No newline at end of file