mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
Fix template string refactoring and nodeFactory bug
Instead of letting `createTemplate*` generate a broken raw string from
the cooked one, grab the source code for it.
Also, add a missing bit to `\`-quote `$`s. As the comment in the code
says, it could just `\`-quote `${` since other `$`s are valid, but I
think that it's less confusing to always quote $s (but the change is in
the comment if minimalism is preferred).
Also, a small-but-confusing bug in `getCookedText()`.
Many tests for all of this.
Fixes #40625
This commit is contained in:
parent
52ca2dd9cf
commit
7e8bba6908
@ -6148,7 +6148,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
let token = rawTextScanner.scan();
|
||||
if (token === SyntaxKind.CloseBracketToken) {
|
||||
if (token === SyntaxKind.CloseBraceToken) {
|
||||
token = rawTextScanner.reScanTemplateToken(/*isTaggedTemplate*/ false);
|
||||
}
|
||||
|
||||
|
||||
@ -147,61 +147,85 @@ namespace ts.refactor.convertStringOrTemplateLiteral {
|
||||
}
|
||||
};
|
||||
|
||||
function concatConsecutiveString(index: number, nodes: readonly Expression[]): [number, string, number[]] {
|
||||
function escapeStringForTemplate(s: string) {
|
||||
// Escaping for $s in strings that are to be used in template strings
|
||||
// Naive implementation: replace \x by itself and otherwise $ by \$.
|
||||
// But to complicate it a bit, this should work for raw strings too.
|
||||
// And another bit: escape the $ in the replacement for JS's .replace().
|
||||
return s.replace(/\\.|\$/g, m => m === "$" ? "\\\$" : m);
|
||||
// Finally, a less-backslash-happy version can work too, doing only ${ instead of all $s:
|
||||
// s.replace(/\\.|\${/g, m => m === "${" ? "\\\${" : m);
|
||||
// but `\$${foo}` is likely more clear than the more-confusing-but-still-working `$${foo}`.
|
||||
}
|
||||
|
||||
function getRawTextOfTemplate(node: TemplateHead | TemplateMiddle | TemplateTail) {
|
||||
// in these cases the right side is ${
|
||||
const rightShaving = isTemplateHead(node) || isTemplateMiddle(node) ? -2 : -1;
|
||||
return getTextOfNode(node).slice(1, rightShaving);
|
||||
}
|
||||
|
||||
function concatConsecutiveString(index: number, nodes: readonly Expression[]): [nextIndex: number, text: string, rawText: string, usedIndexes: number[]] {
|
||||
const indexes = [];
|
||||
let text = "";
|
||||
let text = "", rawText = "";
|
||||
while (index < nodes.length) {
|
||||
const node = nodes[index];
|
||||
if (isStringLiteralLike(node)) { // includes isNoSubstitutionTemplateLiteral(node)
|
||||
text = text + node.text;
|
||||
text += escapeStringForTemplate(node.text);
|
||||
rawText += escapeStringForTemplate(getTextOfNode(node).slice(1, -1));
|
||||
indexes.push(index);
|
||||
index++;
|
||||
}
|
||||
else if (isTemplateExpression(node)) {
|
||||
text = text + node.head.text;
|
||||
text += node.head.text;
|
||||
rawText += getRawTextOfTemplate(node.head);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [index, text, indexes];
|
||||
return [index, text, rawText, indexes];
|
||||
}
|
||||
|
||||
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);
|
||||
const [begin, headText, rawHeadText, headIndexes] = concatConsecutiveString(0, nodes);
|
||||
|
||||
if (begin === nodes.length) {
|
||||
const noSubstitutionTemplateLiteral = factory.createNoSubstitutionTemplateLiteral(headText);
|
||||
const noSubstitutionTemplateLiteral = factory.createNoSubstitutionTemplateLiteral(headText, rawHeadText);
|
||||
copyCommentFromStringLiterals(headIndexes, noSubstitutionTemplateLiteral);
|
||||
return noSubstitutionTemplateLiteral;
|
||||
}
|
||||
|
||||
const templateSpans: TemplateSpan[] = [];
|
||||
const templateHead = factory.createTemplateHead(headText);
|
||||
const templateHead = factory.createTemplateHead(headText, rawHeadText);
|
||||
copyCommentFromStringLiterals(headIndexes, templateHead);
|
||||
|
||||
for (let i = begin; i < nodes.length; i++) {
|
||||
const currentNode = getExpressionFromParenthesesOrExpression(nodes[i]);
|
||||
copyOperatorComments(i, currentNode);
|
||||
|
||||
const [newIndex, subsequentText, stringIndexes] = concatConsecutiveString(i + 1, nodes);
|
||||
const [newIndex, subsequentText, rawSubsequentText, stringIndexes] = concatConsecutiveString(i + 1, nodes);
|
||||
i = newIndex - 1;
|
||||
const isLast = i === nodes.length - 1;
|
||||
|
||||
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));
|
||||
const isLastSpan = index === currentNode.templateSpans.length - 1;
|
||||
const text = span.literal.text + (isLastSpan ? subsequentText : "");
|
||||
const rawText = getRawTextOfTemplate(span.literal) + (isLastSpan ? rawSubsequentText : "");
|
||||
return factory.createTemplateSpan(span.expression, isLast
|
||||
? factory.createTemplateTail(text, rawText)
|
||||
: factory.createTemplateMiddle(text, rawText));
|
||||
});
|
||||
templateSpans.push(...spans);
|
||||
}
|
||||
else {
|
||||
const templatePart = isLast ? factory.createTemplateTail(subsequentText) : factory.createTemplateMiddle(subsequentText);
|
||||
const templatePart = isLast
|
||||
? factory.createTemplateTail(subsequentText, rawSubsequentText)
|
||||
: factory.createTemplateMiddle(subsequentText, rawSubsequentText);
|
||||
copyCommentFromStringLiterals(stringIndexes, templatePart);
|
||||
templateSpans.push(factory.createTemplateSpan(currentNode, templatePart));
|
||||
}
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////let s = /*a1*/"\0\b\f\t\r\n" + text + "\n"/*a2*/;
|
||||
|
||||
goTo.select("a1", "a2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Convert to template string",
|
||||
actionName: "Convert to template string",
|
||||
actionDescription: ts.Diagnostics.Convert_to_template_string.message,
|
||||
newContent: 'let s = `\\0\\b\\f\\t\\r\\n${text}\\n`;'
|
||||
});
|
||||
|
||||
// @Filename: /b.ts
|
||||
////let s = /*b1*/'"' + text + "'"/*b2*/;
|
||||
|
||||
goTo.select("b1", "b2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Convert to template string",
|
||||
actionName: "Convert to template string",
|
||||
actionDescription: ts.Diagnostics.Convert_to_template_string.message,
|
||||
// newContent is: let s = `"${text}'`;
|
||||
newContent: 'let s = `"${text}\'`;'
|
||||
});
|
||||
|
||||
// @Filename: /c.ts
|
||||
////let s = /*c1*/'$' + text + "\\"/*c2*/;
|
||||
|
||||
goTo.select("c1", "c2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Convert to template string",
|
||||
actionName: "Convert to template string",
|
||||
actionDescription: ts.Diagnostics.Convert_to_template_string.message,
|
||||
// newContent is: let s = `\$${text}\\`;
|
||||
newContent: 'let s = `\\$${text}\\\\`;'
|
||||
});
|
||||
|
||||
// @Filename: /d.ts
|
||||
////let s = /*d1*/`$` + text + `\\`/*d2*/;
|
||||
|
||||
goTo.select("d1", "d2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Convert to template string",
|
||||
actionName: "Convert to template string",
|
||||
actionDescription: ts.Diagnostics.Convert_to_template_string.message,
|
||||
// newContent is: let s = `\$${text}\\`;
|
||||
newContent: 'let s = `\\$${text}\\\\`;'
|
||||
});
|
||||
|
||||
// @Filename: /e.ts
|
||||
////let s = /*e1*/'${' + text + "}"/*e2*/;
|
||||
|
||||
goTo.select("e1", "e2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Convert to template string",
|
||||
actionName: "Convert to template string",
|
||||
actionDescription: ts.Diagnostics.Convert_to_template_string.message,
|
||||
// newContent is: let s = `\${${text}}`;
|
||||
newContent: 'let s = `\\${${text}}`;'
|
||||
});
|
||||
|
||||
// @Filename: /f.ts
|
||||
////let s = /*f1*/`\${` + text + `}`/*f2*/;
|
||||
|
||||
goTo.select("f1", "f2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Convert to template string",
|
||||
actionName: "Convert to template string",
|
||||
actionDescription: ts.Diagnostics.Convert_to_template_string.message,
|
||||
// newContent is: let s = `\${${text}}`;
|
||||
newContent: 'let s = `\\${${text}}`;'
|
||||
});
|
||||
|
||||
// @Filename: /g.ts
|
||||
////let s = /*g1*/'\\$' + text + "\\"/*g2*/;
|
||||
|
||||
goTo.select("g1", "g2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Convert to template string",
|
||||
actionName: "Convert to template string",
|
||||
actionDescription: ts.Diagnostics.Convert_to_template_string.message,
|
||||
// newContent is: let s = `\\\$${text}\\`;
|
||||
newContent: 'let s = `\\\\\\$${text}\\\\`;'
|
||||
});
|
||||
|
||||
// @Filename: /h.ts
|
||||
////let s = /*h1*/"\u0041\u0061" + text + "\0\u0000"/*h2*/;
|
||||
|
||||
goTo.select("h1", "h2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Convert to template string",
|
||||
actionName: "Convert to template string",
|
||||
actionDescription: ts.Diagnostics.Convert_to_template_string.message,
|
||||
// newContent is: let s = `\u0041\u0061${text}\0\u0000`;
|
||||
newContent: 'let s = `\\u0041\\u0061${text}\\0\\u0000`;'
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user