copy comments from string to template literal

This commit is contained in:
BigAru
2019-03-28 15:05:22 +01:00
parent 17f3861699
commit dd89a49b1d
8 changed files with 144 additions and 26 deletions

View File

@@ -64,9 +64,24 @@ namespace ts.refactor.convertStringOrTemplateLiteral {
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));
const file = context.file;
const templateLiteral = nodesToTemplate(treeToArray(maybeBinary), file);
const trailingCommentRanges = getTrailingCommentRanges(file.text, maybeBinary.end);
if (trailingCommentRanges) {
const lastComment = trailingCommentRanges[trailingCommentRanges.length - 1];
const trailingRange = { pos: trailingCommentRanges[0].pos, end: lastComment.end };
return textChanges.ChangeTracker.with(context, t => {
t.deleteRange(file, trailingRange);
t.replaceNode(file, maybeBinary, templateLiteral);
});
}
else {
return textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral));
}
}
const templateSpanToExpressions = (file: SourceFile) => (templateSpan: TemplateSpan): Expression[] => {
@@ -132,59 +147,97 @@ namespace ts.refactor.convertStringOrTemplateLiteral {
return containsString && areOperatorsValid;
}
function transformTreeToArray(node: Node): ReadonlyArray<Expression> {
return treeToArray(node).nodes;
}
function treeToArray(node: Node): { nodes: ReadonlyArray<Expression>, containsString: boolean, areOperatorsValid: boolean} {
function treeToArray(node: Node): { nodes: ReadonlyArray<Expression>, operators: Token<BinaryOperator>[], containsString: boolean, areOperatorsValid: boolean} {
if (isBinaryExpression(node)) {
const { nodes: leftNodes, containsString: leftHasString, areOperatorsValid: leftOperatorValid } = treeToArray(node.left);
const { nodes: leftNodes, operators: leftOperator, 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 };
return { nodes: [node], operators: [], containsString: false, areOperatorsValid: true };
}
const currentOperatorValid = node.operatorToken.kind === SyntaxKind.PlusToken;
const areOperatorsValid = leftOperatorValid && currentOperatorValid && rightOperatorValid;
leftOperator.push(node.operatorToken);
return { nodes: leftNodes.concat(rightNodes), containsString: true, areOperatorsValid };
return { nodes: leftNodes.concat(rightNodes), operators: leftOperator, containsString: true, areOperatorsValid };
}
return { nodes: [node as Expression], containsString: isStringLiteral(node), areOperatorsValid: true };
return { nodes: [node as Expression], operators: [], containsString: isStringLiteral(node), areOperatorsValid: true };
}
function concatConsecutiveString(index: number, nodes: ReadonlyArray<Expression>): [number, string] {
const copyTrailingOperatorComments = (operators: Token<BinaryOperator>[], file: SourceFile) => (index: number, targetNode: Node) => {
if (index < operators.length) {
copyTrailingComments(operators[index], targetNode, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
}
};
const copyCommentFromMultiNode = (nodes: ReadonlyArray<Expression>, file: SourceFile, copyOperatorComments: (index: number, targetNode: Node) => void) =>
(indexes: number[], targetNode: Node) => {
while (indexes.length > 0) {
const index = indexes.shift()!;
copyTrailingComments(nodes[index], targetNode, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
copyOperatorComments(index, targetNode);
}
};
function concatConsecutiveString(index: number, nodes: ReadonlyArray<Expression>): [number, string, number[]] {
let text = "";
const indexes = [];
while (index < nodes.length && isStringLiteral(nodes[index])) {
text = text + decodeRawString(nodes[index].getText());
indexes.push(index);
index++;
}
text = escapeText(text);
return [index, text];
return [index, text, indexes];
}
function nodesToTemplate(nodes: ReadonlyArray<Expression>) {
const templateSpans: TemplateSpan[] = [];
const [begin, headText] = concatConsecutiveString(0, nodes);
const templateHead = createTemplateHead(headText);
function nodesToTemplate({nodes, operators}: {nodes: ReadonlyArray<Expression>, operators: Token<BinaryOperator>[]}, file: SourceFile) {
const copyOperatorComments = copyTrailingOperatorComments(operators, file);
const copyCommentFromStringLiterals = copyCommentFromMultiNode(nodes, file, copyOperatorComments);
if (begin === nodes.length) return createNoSubstitutionTemplateLiteral(headText);
const templateSpans: TemplateSpan[] = [];
const [begin, headText, headIndexes] = concatConsecutiveString(0, nodes);
if (begin === nodes.length) {
const noSubstitutionTemplateLiteral = createNoSubstitutionTemplateLiteral(headText);
copyCommentFromStringLiterals(headIndexes, noSubstitutionTemplateLiteral);
return noSubstitutionTemplateLiteral;
}
const templateHead = createTemplateHead(headText);
copyCommentFromStringLiterals(headIndexes, templateHead);
for (let i = begin; i < nodes.length; i++) {
const expression = isParenthesizedExpression(nodes[i]) ? (nodes[i] as ParenthesizedExpression).expression : nodes[i];
const [newIndex, subsequentText] = concatConsecutiveString(i + 1, nodes);
let currentNode = nodes[i];
if (isParenthesizedExpression(currentNode)) {
copyCommentsWhenParenthesized(currentNode);
currentNode = currentNode.expression;
}
copyOperatorComments(i, currentNode);
const [newIndex, subsequentText, stringIndexes] = concatConsecutiveString(i + 1, nodes);
i = newIndex - 1;
const templatePart = i === nodes.length - 1 ? createTemplateTail(subsequentText) : createTemplateMiddle(subsequentText);
templateSpans.push(createTemplateSpan(expression, templatePart));
copyCommentFromStringLiterals(stringIndexes, templatePart);
templateSpans.push(createTemplateSpan(currentNode, templatePart));
}
return createTemplateExpression(templateHead, templateSpans);
}
function copyCommentsWhenParenthesized(node: ParenthesizedExpression) {
const file = node.getSourceFile();
copyTrailingComments(node, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
copyTrailingAsLeadingComments(node.expression, node.expression, file, SyntaxKind.MultiLineCommentTrivia, /* hasTrailingNewLine */ false);
}
const hexToUnicode = (_match: string, grp: string) => String.fromCharCode(parseInt(grp, 16));
const octalToUnicode = (_match: string, grp: string) => String.fromCharCode(parseInt(grp, 8));

View File

@@ -1,6 +1,6 @@
/// <reference path='fourslash.ts' />
//// const foo = `/*x*/H/*y*/EAD ${ /* C0 */ 42 /* C1 */} Span1 ${ /* C2 */ 43 /* C3 */} Span2 ${ /* C4 */ 44 /* C5 */} Span3`
//// const foo = /* H */ `/*x*/H/*y*/EAD ${ /* C0 */ 42 /* C1 */} Span1 ${ /* C2 */ 43 /* C3 */} Span2 ${ /* C4 */ 44 /* C5 */} Span3` /* T */
goTo.select("x", "y");
edit.applyRefactor({
@@ -8,6 +8,6 @@ edit.applyRefactor({
actionName: "Convert to string concatenation",
actionDescription: "Convert to string concatenation",
newContent:
`const foo = "HEAD " + /* C0 */ 42 /* C1 */ + " Span1 " + /* C2 */ 43 /* C3 */ + " Span2 " + /* C4 */ 44 /* C5 */ + " Span3"`,
`const foo = /* H */ "HEAD " + /* C0 */ 42 /* C1 */ + " Span1 " + /* C2 */ 43 /* C3 */ + " Span2 " + /* C4 */ 44 /* C5 */ + " Span3" /* T */`,
});

View File

@@ -1,6 +1,6 @@
/// <reference path='fourslash.ts' />
//// const foo = /* C0 */ /*x*/"/*y*/foobar" /* C1 */ + " is" /* C2 */ + 42 /* C3 */ + "years old" /* C4 */
//// const foo = /* C0 */ /*x*/"/*y*/foo" /* C1 */ + " is" /* C2 */ + 42 /* C3 */ + " and bar" /* C4 */ + " is" /* C5 */ + 52/* C6 */
goTo.select("x", "y");
edit.applyRefactor({
@@ -8,6 +8,6 @@ edit.applyRefactor({
actionName: "Convert to template literal",
actionDescription: "Convert to template literal",
newContent:
`const foo = /* C0 */ \`foobar is \${ /* C1 */ /* C2 */ 42 /* C3 */}years old\` /* C4 */`,
"const foo = /* C0 */ `foo is\${ /* C1 */ /* C2 */42 /* C3 */} and bar is\${ /* C4 */ /* C5 */52 /* C6 */}`",
});

View File

@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
//// const foo = /* C0 */ /*x*/"/*y*/foo" + /* C1 */ " is" + /* C2 */ 42 + /* C3 */ " and bar" + /* C4 */ " is" + /* C5 */ 52/* C6 */
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 = /* C0 */ `foo is\${ /* C1 */ /* C2 */42 /* C3 */} and bar is\${ /* C4 */ /* C5 */52 /* C6 */}`",
});

View File

@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
//// const foo = /* C0 */ /*x*/"/*y*/foo" /* C1 */ + " is"/* C2 */ /* C3 */
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 = /* C0 */ `foo is` /* C1 */ /* C2 */ /* C3 */",
});

View File

@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
//// const foo = /*x*/"/*y*/foobar is" + ( /* C1 */ 42 ) /* C2 */ + /* C3 */ " 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\${/* C1 */ 42 /* C2 */ /* C3 */} years old`",
});

View File

@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
//// const foo = /* C0 */ /*x*/"/*y*/foo" /* C1 */ + " is" /* C2 */ + 42/* C3 */
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 = /* C0 */ `foo is\${ /* C1 */ /* C2 */42 /* C3 */}`",
});

View File

@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
//// const foo = /* C0 */ /*x*/"/*y*/foo" /* C1 */ + " is" /* C2 */ + 42 + " years old"/* C3 */
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 = /* C0 */ `foo is\${ /* C1 */ /* C2 */42} years old` /* C3 */",
});