From 98aaeb7f4830bf3e33f7efe713f2cae35fc13c32 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Thu, 5 Nov 2020 20:09:21 +0200 Subject: [PATCH] fix(40610): handle template string concatenation (#40653) --- .../convertStringOrTemplateLiteral.ts | 50 +++++++++++++------ ...StringOrTemplateLiteral_TemplateString1.ts | 11 ++++ ...tringOrTemplateLiteral_TemplateString10.ts | 11 ++++ ...StringOrTemplateLiteral_TemplateString2.ts | 15 ++++++ ...StringOrTemplateLiteral_TemplateString3.ts | 11 ++++ ...StringOrTemplateLiteral_TemplateString4.ts | 15 ++++++ ...StringOrTemplateLiteral_TemplateString5.ts | 15 ++++++ ...StringOrTemplateLiteral_TemplateString6.ts | 15 ++++++ ...StringOrTemplateLiteral_TemplateString7.ts | 15 ++++++ ...StringOrTemplateLiteral_TemplateString8.ts | 21 ++++++++ ...StringOrTemplateLiteral_TemplateString9.ts | 21 ++++++++ 11 files changed, 185 insertions(+), 15 deletions(-) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString1.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString10.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString2.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString3.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString4.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString5.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString6.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString7.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString8.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString9.ts diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 70965a16ed0..7641987153d 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -78,6 +78,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: return false; + case SyntaxKind.TemplateExpression: case SyntaxKind.BinaryExpression: return !(isBinaryExpression(n.parent) && isNotEqualsOperator(n.parent)); default: @@ -97,7 +98,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { if (isBinaryExpression(current)) { const { nodes, operators, containsString: leftHasString, areOperatorsValid: leftOperatorValid } = treeToArray(current.left); - if (!leftHasString && !isStringLiteral(current.right)) { + if (!leftHasString && !isStringLiteral(current.right) && !isTemplateExpression(current.right)) { return { nodes: [current], operators: [], containsString: false, areOperatorsValid: true }; } @@ -133,20 +134,27 @@ namespace ts.refactor.convertStringOrTemplateLiteral { }; function concatConsecutiveString(index: number, nodes: readonly Expression[]): [number, string, number[]] { - let text = ""; const indexes = []; - - while (index < nodes.length && isStringLiteral(nodes[index])) { - const stringNode = nodes[index] as StringLiteral; - text = text + stringNode.text; - indexes.push(index); - index++; + let text = ""; + while (index < nodes.length) { + const node = nodes[index]; + if (isStringLiteralLike(node)) { + text = text + node.text; + indexes.push(index); + index++; + } + else if (isTemplateExpression(node)) { + text = text + node.head.text; + break; + } + else { + break; + } } - return [index, text, indexes]; } - function nodesToTemplate({nodes, operators}: {nodes: readonly Expression[], operators: Token[]}, file: SourceFile) { + function nodesToTemplate({ nodes, operators }: { nodes: readonly Expression[], operators: Token[] }, file: SourceFile) { const copyOperatorComments = copyTrailingOperatorComments(operators, file); const copyCommentFromStringLiterals = copyCommentFromMultiNode(nodes, file, copyOperatorComments); const [begin, headText, headIndexes] = concatConsecutiveString(0, nodes); @@ -167,10 +175,22 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const [newIndex, subsequentText, stringIndexes] = concatConsecutiveString(i + 1, nodes); i = newIndex - 1; + const isLast = i === nodes.length - 1; - const templatePart = i === nodes.length - 1 ? factory.createTemplateTail(subsequentText) : factory.createTemplateMiddle(subsequentText); - copyCommentFromStringLiterals(stringIndexes, templatePart); - templateSpans.push(factory.createTemplateSpan(currentNode, templatePart)); + if (isTemplateExpression(currentNode)) { + const spans = map(currentNode.templateSpans, (span, index) => { + copyExpressionComments(span); + const nextSpan = currentNode.templateSpans[index + 1]; + const text = span.literal.text + (nextSpan ? "" : subsequentText); + return factory.createTemplateSpan(span.expression, isLast ? factory.createTemplateTail(text) : factory.createTemplateMiddle(text)); + }); + templateSpans.push(...spans); + } + else { + const templatePart = isLast ? factory.createTemplateTail(subsequentText) : factory.createTemplateMiddle(subsequentText); + copyCommentFromStringLiterals(stringIndexes, templatePart); + templateSpans.push(factory.createTemplateSpan(currentNode, templatePart)); + } } return factory.createTemplateExpression(templateHead, templateSpans); @@ -178,7 +198,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { // to copy comments following the opening & closing parentheses // "foo" + ( /* comment */ 5 + 5 ) /* comment */ + "bar" - function copyCommentsWhenParenthesized(node: ParenthesizedExpression) { + function copyExpressionComments(node: ParenthesizedExpression | TemplateSpan) { const file = node.getSourceFile(); copyTrailingComments(node, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); copyTrailingAsLeadingComments(node.expression, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false); @@ -186,7 +206,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { function getExpressionFromParenthesesOrExpression(node: Expression) { if (isParenthesizedExpression(node)) { - copyCommentsWhenParenthesized(node); + copyExpressionComments(node); node = node.expression; } return node; diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString1.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString1.ts new file mode 100644 index 00000000000..707368a0ca8 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString1.ts @@ -0,0 +1,11 @@ +/// + +////const foo = /*start*/"" + ``/*end*/; + +goTo.select("start", "end"); +edit.applyRefactor({ + refactorName: "Convert to template string", + actionName: "Convert to template string", + actionDescription: ts.Diagnostics.Convert_to_template_string.message, + newContent: "const foo = ``;", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString10.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString10.ts new file mode 100644 index 00000000000..a6e003698b8 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString10.ts @@ -0,0 +1,11 @@ +/// + +////const a = /*x*/`${1}`/*y*/ + "a" + "b" + ` ${2}.`; + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert to template string", + actionName: "Convert to template string", + actionDescription: ts.Diagnostics.Convert_to_template_string.message, + newContent: "const a = `${1}ab ${2}.`;" +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString2.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString2.ts new file mode 100644 index 00000000000..609f2b02144 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString2.ts @@ -0,0 +1,15 @@ +/// + +////const foo = 1; +////const bar = /*start*/"a" + `${foo}`/*end*/; + +goTo.select("start", "end"); +edit.applyRefactor({ + refactorName: "Convert to template string", + actionName: "Convert to template string", + actionDescription: ts.Diagnostics.Convert_to_template_string.message, + newContent: [ + "const foo = 1;", + "const bar = `a${foo}`;" + ].join("\n") +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString3.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString3.ts new file mode 100644 index 00000000000..4550b4c3a21 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString3.ts @@ -0,0 +1,11 @@ +/// + +////const foo = /*start*/`` + ""/*end*/; + +goTo.select("start", "end"); +edit.applyRefactor({ + refactorName: "Convert to template string", + actionName: "Convert to template string", + actionDescription: ts.Diagnostics.Convert_to_template_string.message, + newContent: "const foo = ``;" +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString4.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString4.ts new file mode 100644 index 00000000000..30172cfcc8c --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString4.ts @@ -0,0 +1,15 @@ +/// + +////const foo = 1; +////const bar = /*start*/`` + "1" + `` + `${foo}`/*end*/; + +goTo.select("start", "end"); +edit.applyRefactor({ + refactorName: "Convert to template string", + actionName: "Convert to template string", + actionDescription: ts.Diagnostics.Convert_to_template_string.message, + newContent: [ + "const foo = 1;", + "const bar = `1${foo}`;" + ].join("\n") +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString5.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString5.ts new file mode 100644 index 00000000000..f1049442589 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString5.ts @@ -0,0 +1,15 @@ +/// + +////const foo = 1; +////const bar = /*start*/`1` + "2" + `3` + `${foo}`/*end*/; + +goTo.select("start", "end"); +edit.applyRefactor({ + refactorName: "Convert to template string", + actionName: "Convert to template string", + actionDescription: ts.Diagnostics.Convert_to_template_string.message, + newContent: [ + "const foo = 1;", + "const bar = `123${foo}`;" + ].join("\n") +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString6.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString6.ts new file mode 100644 index 00000000000..eecb5cf7ba0 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString6.ts @@ -0,0 +1,15 @@ +/// + +////const foo = 1; +////const bar = /*start*/"a" + `${foo}` + "b"/*end*/; + +goTo.select("start", "end"); +edit.applyRefactor({ + refactorName: "Convert to template string", + actionName: "Convert to template string", + actionDescription: ts.Diagnostics.Convert_to_template_string.message, + newContent: [ + "const foo = 1;", + "const bar = `a${foo}b`;" + ].join("\n") +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString7.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString7.ts new file mode 100644 index 00000000000..88343d2e2e8 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString7.ts @@ -0,0 +1,15 @@ +/// + +////const foo = 1; +////const bar = /*start*/"a " + `${foo}` + " b " + " c"/*end*/; + +goTo.select("start", "end"); +edit.applyRefactor({ + refactorName: "Convert to template string", + actionName: "Convert to template string", + actionDescription: ts.Diagnostics.Convert_to_template_string.message, + newContent: [ + "const foo = 1;", + "const bar = `a ${foo} b c`;" + ].join("\n") +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString8.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString8.ts new file mode 100644 index 00000000000..e1e5f315c97 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString8.ts @@ -0,0 +1,21 @@ +/// + +////const a = 1; +////const b = 1; +////const c = 1; +////const d = 1; +////const d = `a${/* a */ a /* a */} ${b}` + /*start*/" other "/*end*/ + c + `${/* d */ d /* d */}`; + +goTo.select("start", "end"); +edit.applyRefactor({ + refactorName: "Convert to template string", + actionName: "Convert to template string", + actionDescription: ts.Diagnostics.Convert_to_template_string.message, + newContent: [ + "const a = 1;", + "const b = 1;", + "const c = 1;", + "const d = 1;", + "const d = `a${/* a */ a /* a */} ${b} other ${c}${/* d */ d /* d */}`;" + ].join("\n") +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString9.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString9.ts new file mode 100644 index 00000000000..5c5f35a76e5 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_TemplateString9.ts @@ -0,0 +1,21 @@ +/// + +////const a = 1; +////const b = 1; +////const c = 1; +////const d = 1; +////const d = /*start*/"start" + ` / a start ${a} a end / b start ${b} b end / ` + c + ` / d start ${d} d end / ` + "end"/*end*/; + +goTo.select("start", "end"); +edit.applyRefactor({ + refactorName: "Convert to template string", + actionName: "Convert to template string", + actionDescription: ts.Diagnostics.Convert_to_template_string.message, + newContent: [ + "const a = 1;", + "const b = 1;", + "const c = 1;", + "const d = 1;", + "const d = `start / a start ${a} a end / b start ${b} b end / ${c} / d start ${d} d end / end`;" + ].join("\n") +});