diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index e3f8e1a7ad5a6..0372150ed8c8f 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4799,5 +4799,17 @@ "Add names to all parameters without names": { "category": "Message", "code": 95073 + }, + "Convert string concatenation or template literal": { + "category": "Message", + "code": 95074 + }, + "Convert to template literal": { + "category": "Message", + "code": 95075 + }, + "Convert to string concatenation": { + "category": "Message", + "code": 95076 } } diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts new file mode 100644 index 0000000000000..80d5fac3708e9 --- /dev/null +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -0,0 +1,201 @@ +/* @internal */ +namespace ts.refactor.convertStringOrTemplateLiteral { + const refactorName = "Convert string concatenation or template literal"; + const toTemplateLiteralActionName = "Convert to template literal"; + const toStringConcatenationActionName = "Convert to string concatenation"; + + const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_string_concatenation_or_template_literal); + const toTemplateLiteralDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_literal); + const toStringConcatenationDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_string_concatenation); + + registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); + + function getAvailableActions(context: RefactorContext): ReadonlyArray { + const { file, startPosition } = context; + const node = getNodeOrParentOfParentheses(file, startPosition); + const maybeBinary = getParentBinaryExpression(node); + const actions: RefactorActionInfo[] = []; + + if ((isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) && isStringConcatenationValid(maybeBinary)) { + actions.push({ name: toTemplateLiteralActionName, description: toTemplateLiteralDescription }); + } + + const templateLiteral = findAncestor(node, n => isTemplateLiteral(n)); + + if (templateLiteral && !isTaggedTemplateExpression(templateLiteral.parent)) { + actions.push({ name: toStringConcatenationActionName, description: toStringConcatenationDescription }); + } + + return [{ name: refactorName, description: refactorDescription, actions }]; + } + + function getNodeOrParentOfParentheses(file: SourceFile, startPosition: number) { + const node = getTokenAtPosition(file, startPosition); + if (isParenthesizedExpression(node.parent) && isBinaryExpression(node.parent.parent)) return node.parent.parent; + return node; + } + + function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const { file, startPosition } = context; + const node = getNodeOrParentOfParentheses(file, startPosition); + + switch (actionName) { + case toTemplateLiteralActionName: + return { edits: getEditsForToTemplateLiteral(context, node) }; + + case toStringConcatenationActionName: + return { edits: getEditsForToStringConcatenation(context, node) }; + + default: + return Debug.fail("invalid action"); + } + } + + function getEditsForToTemplateLiteral(context: RefactorContext, node: Node) { + const maybeBinary = getParentBinaryExpression(node); + const arrayOfNodes = transformTreeToArray(maybeBinary); + const templateLiteral = nodesToTemplate(arrayOfNodes); + return textChanges.ChangeTracker.with(context, t => t.replaceNode(context.file, maybeBinary, templateLiteral)); + } + + function getEditsForToStringConcatenation(context: RefactorContext, node: Node) { + const templateLiteral = findAncestor(node, n => isTemplateLiteral(n))! as TemplateLiteral; + + if (isTemplateExpression(templateLiteral)) { + const { head, templateSpans } = templateLiteral; + const arrayOfNodes = templateSpans.map(templateSpanToExpressions) + .reduce((accumulator, nextArray) => accumulator.concat(nextArray)); + + if (head.text.length !== 0) arrayOfNodes.unshift(createStringLiteral(head.text)); + + const binaryExpression = arrayToTree(arrayOfNodes); + return textChanges.ChangeTracker.with(context, t => t.replaceNode(context.file, templateLiteral, binaryExpression)); + } + else { + const stringLiteral = createStringLiteral(templateLiteral.text); + return textChanges.ChangeTracker.with(context, t => t.replaceNode(context.file, node, stringLiteral)); + } + } + + function templateSpanToExpressions(templateSpan: TemplateSpan): Expression[] { + const { expression, literal } = templateSpan; + const text = literal.text; + return text.length === 0 ? [expression] : [expression, createStringLiteral(text)]; + } + + function getParentBinaryExpression(expr: Node) { + while (isBinaryExpression(expr.parent)) { + expr = expr.parent; + } + return expr; + } + + function arrayToTree(nodes: ReadonlyArray, accumulator?: BinaryExpression): BinaryExpression { + if (nodes.length === 0) return accumulator!; + + if (!accumulator) { + const left = nodes[0]; + const right = nodes[1]; + + const binary = createBinary(left, SyntaxKind.PlusToken, right); + return arrayToTree(nodes.slice(2), binary); + } + + const right = nodes[0]; + const binary = createBinary(accumulator, SyntaxKind.PlusToken, right); + return arrayToTree(nodes.slice(1), binary); + } + + function isStringConcatenationValid(node: Node): boolean { + const { containsString, areOperatorsValid } = treeToArray(node); + return containsString && areOperatorsValid; + } + + function transformTreeToArray(node: Node): ReadonlyArray { + return treeToArray(node).nodes; + } + + function treeToArray(node: Node): { nodes: ReadonlyArray, containsString: boolean, areOperatorsValid: boolean} { + if (isBinaryExpression(node)) { + const { nodes: leftNodes, containsString: leftHasString, areOperatorsValid: leftOperatorValid } = treeToArray(node.left); + const { nodes: rightNodes, containsString: rightHasString, areOperatorsValid: rightOperatorValid } = treeToArray(node.right); + + if (!leftHasString && !rightHasString) { + return { nodes: [node], containsString: false, areOperatorsValid: true }; + } + + const nodeOperatorValid = node.operatorToken.kind === SyntaxKind.PlusToken; + const isPlus = leftOperatorValid && nodeOperatorValid && rightOperatorValid; + + return { nodes: leftNodes.concat(rightNodes), containsString: true, areOperatorsValid: isPlus }; + } + + return { nodes: [node as Expression], containsString: isStringLiteral(node), areOperatorsValid: true }; + } + + function createHead(nodes: ReadonlyArray): [number, TemplateHead] { + let begin = 0; + let text = ""; + + while (begin < nodes.length && isStringLiteral(nodes[begin])) { + const next = nodes[begin] as StringLiteral; + text = text + decodeRawString(next.getText()); + begin++; + } + + text = escapeText(text); + return [begin, createTemplateHead(text)]; + } + + function nodesToTemplate(nodes: ReadonlyArray) { + const templateSpans: TemplateSpan[] = []; + const [begin, head] = createHead(nodes); + + if (begin === nodes.length) { + return createNoSubstitutionTemplateLiteral(head.text); + } + + for (let i = begin; i < nodes.length; i++) { + let current = nodes[i]; + let text = ""; + + while (i + 1 < nodes.length && isStringLiteral(nodes[i + 1])) { + const next = nodes[i + 1] as StringLiteral; + text = text + decodeRawString(next.getText()); + i++; + } + + text = escapeText(text); + const templatePart = i === nodes.length - 1 ? createTemplateTail(text) : createTemplateMiddle(text); + + if (isParenthesizedExpression(current)) current = current.expression; + templateSpans.push(createTemplateSpan(current, templatePart)); + } + + return createTemplateExpression(head, templateSpans); + } + + const hexToUnicode = (_match: string, grp: string) => String.fromCharCode(parseInt(grp, 16)); + const octalToUnicode = (_match: string, grp: string) => String.fromCharCode(parseInt(grp, 8)); + + function decodeRawString(content: string) { + const outerQuotes = /"((.|\s)*)"/; + const unicodeEscape = /\\u([\d\w]+)/gi; + const unicodeEscapeWithBraces = /\\u\{([\d\w]+\})/gi; + const hexEscape = /\\x([\d\w]+)/gi; + const octalEscape = /\\([0-7]+)/g; + + return content.replace(outerQuotes, (_match, grp) => grp) + .replace(unicodeEscape, hexToUnicode) + .replace(unicodeEscapeWithBraces, hexToUnicode) + .replace(hexEscape, hexToUnicode) + .replace(octalEscape, octalToUnicode); + + } + + function escapeText(content: string) { + return content.replace("`", "\`") // back-tick + .replace("\${", `$\\{`); // placeholder alike beginning + } + +} diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 15044416f8426..6a23ea94ea563 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -83,6 +83,7 @@ "refactors/generateGetAccessorAndSetAccessor.ts", "refactors/moveToNewFile.ts", "refactors/addOrRemoveBracesToArrowFunction.ts", + "refactors/convertStringOrTemplateLiteral.ts", "services.ts", "breakpoints.ts", "transform.ts", diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAsFnArgument.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAsFnArgument.ts new file mode 100644 index 0000000000000..de79522269a1c --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAsFnArgument.ts @@ -0,0 +1,12 @@ +/// + +//// console.log(`/*x*/f/*y*/oobar is ${ 32 } years old`) + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`console.log("foobar is " + 32 + " years old")`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts new file mode 100644 index 0000000000000..03e0882bb2f58 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts @@ -0,0 +1,29 @@ +/// + +//// const age = 22 +//// const name = "Eddy" +//// const /*z*/f/*y*/oo = /*x*/`/*w*/M/*v*/r/*u*/ /*t*/$/*s*/{ /*r*/n/*q*/ame } is ${ /*p*/a/*o*/ge + 34 } years old` + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("x", "w"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("v", "u"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("t", "s"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("r", "q"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("p", "o"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailabilityTagged.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailabilityTagged.ts new file mode 100644 index 0000000000000..9327ff0911cbe --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailabilityTagged.ts @@ -0,0 +1,17 @@ +/// + +//// function tag(literals: TemplateStringsArray, ...placeholders: string[]) { return "tagged" } +//// const alpha = tag/*z*/`/*y*/foobar` +//// const beta = tag/*x*/`/*w*/foobar ${/*v*/4/*u*/2}` + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("x", "w"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("v", "u"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBackTick.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBackTick.ts new file mode 100644 index 0000000000000..6587f7037f057 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBackTick.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = `/*x*/w/*y*/ith back\`tick` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +"const foo = \"with back`tick\"", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBinaryExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBinaryExpr.ts new file mode 100644 index 0000000000000..1850b40f22c26 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBinaryExpr.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = `/*x*/f/*y*/oobar is ${ 42 + 6 } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const foo = "foobar is " + (42 + 6) + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts new file mode 100644 index 0000000000000..667580796717a --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = `/*x*/w/*y*/ith \${dollar}` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +"const foo = \"with \${dollar}\"", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringExprInRow.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringExprInRow.ts new file mode 100644 index 0000000000000..4b8e581b8c69b --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringExprInRow.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = `/*x*/f/*y*/oobar is ${ 42 }${ 6 } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const foo = "foobar is " + 42 + 6 + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts new file mode 100644 index 0000000000000..3fdd7fb6362a9 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts @@ -0,0 +1,16 @@ +/// + +//// const age = 22 +//// const name = "Eddy" +//// const foo = `/*x*/$/*y*/{ name } is ${ age } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 22 +const name = "Eddy" +const foo = name + " is " + age + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInner.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInner.ts new file mode 100644 index 0000000000000..a094d5847c433 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInner.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `foobar is a ${ age < 18 ? 'child' : /*x*/`/*y*/grown-up ${ age > 40 ? 'who needs probaply assistance' : ''}` }` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = \`foobar is a \${ age < 18 ? 'child' : "grown-up " + (age > 40 ? 'who needs probaply assistance' : '') }\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInnerNonSub.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInnerNonSub.ts new file mode 100644 index 0000000000000..83783e4d658e8 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInnerNonSub.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `foobar is a ${ `/*x*/3/*y*/4` }` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = \`foobar is a \${ "34" }\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedOuter.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedOuter.ts new file mode 100644 index 0000000000000..fb09d7876b9da --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedOuter.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `foobar is a ${ /*x*/a/*y*/ge < 18 ? 'child' : `grown-up ${ age > 40 ? 'who needs probaply assistance': ''}` }` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = "foobar is a " + (age < 18 ? 'child' : \`grown-up \${age > 40 ? 'who needs probaply assistance' : ''}\`)`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringOneExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringOneExpr.ts new file mode 100644 index 0000000000000..0e5cb6ae2573b --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringOneExpr.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `/*x*/f/*y*/oobar is ${ age } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = "foobar is " + age + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromExpr.ts new file mode 100644 index 0000000000000..7229e9e9f7a44 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromExpr.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `foobar is ${ /*x*/a/*y*/ge } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = "foobar is " + age + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromMiddle.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromMiddle.ts new file mode 100644 index 0000000000000..c44e4ee646f1d --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromMiddle.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `foobar is ${ age } /*x*/y/*y*/ears old ${ false }` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = "foobar is " + age + " years old " + false`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSimple.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSimple.ts new file mode 100644 index 0000000000000..3da488fcf2515 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSimple.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = `/*x*/f/*y*/oobar rocks` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const foo = "foobar rocks"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAsFnArgument.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAsFnArgument.ts new file mode 100644 index 0000000000000..a41eef34b41e5 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAsFnArgument.ts @@ -0,0 +1,12 @@ +/// + +//// console.log("/*x*/f/*y*/oobar is " + 32 + " years old") + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`console.log(\`foobar is \${32} years old\`)`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts new file mode 100644 index 0000000000000..fa58f01392d79 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts @@ -0,0 +1,30 @@ +/// + +//// const age = 22 +//// const name = "Eddy" +//// const /*z*/f/*y*/oo = /*x*/"/*w*/M/*v*/r/*u*/ " /*t*/+/*s*/ /*r*/n/*q*/ame + " is " + /*p*/a/*o*/ge + " years old" + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("x", "w"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("v", "u"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("t", "s"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("r", "q"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("p", "o"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityMinus.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityMinus.ts new file mode 100644 index 0000000000000..50c7921836c22 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityMinus.ts @@ -0,0 +1,25 @@ +/// + +//// const age = 22 +//// const name = "Eddy" +//// const /*z*/f/*y*/oo = /*x*/"/*w*/M/*v*/r/*u*/ " /*t*/+/*s*/ name + " is " - /*r*/a/*q*/ge - " years old" + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("x", "w"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("v", "u"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("t", "s"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("r", "q"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityNoStrings.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityNoStrings.ts new file mode 100644 index 0000000000000..f015af3b3c75e --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityNoStrings.ts @@ -0,0 +1,19 @@ +/// + +//// const /*z*/f/*y*/oo = /*x*/4/*w*/2 /*v*/-/*u*/ 56 + /*t*/2/*s*/2 * 4 / 33 + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("x", "w"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("v", "u"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("t", "s"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityPrecedingMinus.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityPrecedingMinus.ts new file mode 100644 index 0000000000000..04f24dffc8f0f --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityPrecedingMinus.ts @@ -0,0 +1,24 @@ +/// + +//// const age = 22 +//// const /*z*/f/*y*/oo = /*x*/a/*w*/ge * 4 /*v*/-/*u*/ 2 / 4 /*t*/+/*s*/ " /*r*/y/*q*/ears old" + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("x", "w"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("v", "u"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("t", "s"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("r", "q"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBackTick.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBackTick.ts new file mode 100644 index 0000000000000..205f84c19a111 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBackTick.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/w/*y*/ith back`tick" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `with back\\`tick`", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExpr.ts new file mode 100644 index 0000000000000..2519ac34664e2 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExpr.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar is " + (42 + 6) + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar is \${42 + 6} years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExprInEnding.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExprInEnding.ts new file mode 100644 index 0000000000000..a7ab291657b17 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExprInEnding.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar is " + (42 + 6) + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `foobar is \${42 + 6}`", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateConsecutiveStr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateConsecutiveStr.ts new file mode 100644 index 0000000000000..a053a6c5e3bca --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateConsecutiveStr.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar is " + 42 + " years" + " old" + " and " + 6 + " cars" + " are" + " missing" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar is \${42} years old and \${6} cars are missing\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts new file mode 100644 index 0000000000000..a073b6838f585 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/w/*y*/ith ${dollar}" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `with $\\\\{dollar}`", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprFromBrace.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprFromBrace.ts new file mode 100644 index 0000000000000..5924da73ebc20 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprFromBrace.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "foobar is " + /*x*/(/*y*/42 + 6) + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar is \${42 + 6} years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprInRow.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprInRow.ts new file mode 100644 index 0000000000000..aadc6068a68ed --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprInRow.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar is " + 42 + 6 + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar is \${42}\${6} years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiExpr.ts new file mode 100644 index 0000000000000..e781bae6634b3 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiExpr.ts @@ -0,0 +1,16 @@ +/// + +//// const age = 22 +//// const name = "Eddy" +//// const foo = /*x*/n/*y*/ame + " is " + age + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const age = 22 +const name = "Eddy" +const foo = \`\${name} is \${age} years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOctalEscape.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOctalEscape.ts new file mode 100644 index 0000000000000..e607240f39fcf --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOctalEscape.ts @@ -0,0 +1,13 @@ +/// + +debugger; +//// const foo = "/*x*/U/*y*/nicode \u0023 \u{0023} " + "Hex \x23 " + "Octal \43"; + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `Unicode # # Hex # Octal #\`;", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOneExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOneExpr.ts new file mode 100644 index 0000000000000..a790a623aaa77 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOneExpr.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = "/*x*/f/*y*/oobar is " + age + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const age = 42 +const foo = \`foobar is \${age} years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOnlyStr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOnlyStr.ts new file mode 100644 index 0000000000000..bd1fa6009fc16 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOnlyStr.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar " + "rocks" + " fantastically" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `foobar rocks fantastically`", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplatePrefixExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplatePrefixExpr.ts new file mode 100644 index 0000000000000..dfaa3ab8d66ec --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplatePrefixExpr.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = /*x*/4/*y*/2 + 6 + 23 + 12 +" years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`\${42 + 6 + 23 + 12} years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateSimple.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateSimple.ts new file mode 100644 index 0000000000000..2c5663b07dc2a --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateSimple.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar rocks" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar rocks\``, +});