fix(40610): handle template string concatenation (#40653)

This commit is contained in:
Oleksandr T 2020-11-05 20:09:21 +02:00 committed by GitHub
parent 45b698b3f6
commit 98aaeb7f48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 185 additions and 15 deletions

View File

@ -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<BinaryOperator>[]}, file: SourceFile) {
function nodesToTemplate({ nodes, operators }: { nodes: readonly Expression[], operators: Token<BinaryOperator>[] }, 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;

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
////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 = ``;",
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
////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}.`;"
});

View File

@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />
////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")
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
////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 = ``;"
});

View File

@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />
////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")
});

View File

@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />
////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")
});

View File

@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />
////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")
});

View File

@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />
////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")
});

View File

@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />
////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")
});

View File

@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />
////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")
});