Format completion snippet text before escaping (#48793)

* Format snippet text before escaping

* Reset `escapes` before printing so printer can be reused
This commit is contained in:
Andrew Branch
2022-04-21 13:40:14 -07:00
committed by GitHub
parent ab2523bbe0
commit 7abdb9e7ef
2 changed files with 63 additions and 10 deletions

View File

@@ -1199,18 +1199,19 @@ namespace ts.Completions {
function createSnippetPrinter(
printerOptions: PrinterOptions,
) {
let escapes: TextChange[] | undefined;
const baseWriter = textChanges.createWriter(getNewLineCharacter(printerOptions));
const printer = createPrinter(printerOptions, baseWriter);
const writer: EmitTextWriter = {
...baseWriter,
write: s => baseWriter.write(escapeSnippetText(s)),
write: s => escapingWrite(s, () => baseWriter.write(s)),
nonEscapingWrite: baseWriter.write,
writeLiteral: s => baseWriter.writeLiteral(escapeSnippetText(s)),
writeStringLiteral: s => baseWriter.writeStringLiteral(escapeSnippetText(s)),
writeSymbol: (s, symbol) => baseWriter.writeSymbol(escapeSnippetText(s), symbol),
writeParameter: s => baseWriter.writeParameter(escapeSnippetText(s)),
writeComment: s => baseWriter.writeComment(escapeSnippetText(s)),
writeProperty: s => baseWriter.writeProperty(escapeSnippetText(s)),
writeLiteral: s => escapingWrite(s, () => baseWriter.writeLiteral(s)),
writeStringLiteral: s => escapingWrite(s, () => baseWriter.writeStringLiteral(s)),
writeSymbol: (s, symbol) => escapingWrite(s, () => baseWriter.writeSymbol(s, symbol)),
writeParameter: s => escapingWrite(s, () => baseWriter.writeParameter(s)),
writeComment: s => escapingWrite(s, () => baseWriter.writeComment(s)),
writeProperty: s => escapingWrite(s, () => baseWriter.writeProperty(s)),
};
return {
@@ -1218,12 +1219,39 @@ namespace ts.Completions {
printAndFormatSnippetList,
};
// The formatter/scanner will have issues with snippet-escaped text,
// so instead of writing the escaped text directly to the writer,
// generate a set of changes that can be applied to the unescaped text
// to escape it post-formatting.
function escapingWrite(s: string, write: () => void) {
const escaped = escapeSnippetText(s);
if (escaped !== s) {
const start = baseWriter.getTextPos();
write();
const end = baseWriter.getTextPos();
escapes = append(escapes ||= [], { newText: escaped, span: { start, length: end - start } });
}
else {
write();
}
}
/* Snippet-escaping version of `printer.printList`. */
function printSnippetList(
format: ListFormat,
list: NodeArray<Node>,
sourceFile: SourceFile | undefined,
): string {
const unescaped = printUnescapedSnippetList(format, list, sourceFile);
return escapes ? textChanges.applyChanges(unescaped, escapes) : unescaped;
}
function printUnescapedSnippetList(
format: ListFormat,
list: NodeArray<Node>,
sourceFile: SourceFile | undefined,
): string {
escapes = undefined;
writer.clear();
printer.writeList(format, list, sourceFile, writer);
return writer.getText();
@@ -1236,7 +1264,7 @@ namespace ts.Completions {
formatContext: formatting.FormatContext,
): string {
const syntheticFile = {
text: printSnippetList(
text: printUnescapedSnippetList(
format,
list,
sourceFile),
@@ -1256,7 +1284,11 @@ namespace ts.Completions {
/* delta */ 0,
{ ...formatContext, options: formatOptions });
});
return textChanges.applyChanges(syntheticFile.text, changes);
const allChanges = escapes
? stableSort(concatenate(changes, escapes), (a, b) => compareTextSpans(a.span, b.span))
: changes;
return textChanges.applyChanges(syntheticFile.text, allChanges);
}
}

View File

@@ -5,6 +5,9 @@
// Case: Snippet text needs escaping
////interface DollarSign {
//// "$usd"(a: number): number;
//// $cad(b: number): number;
//// cla$$y(c: number): number;
//// isDollarAmountString(s: string): s is `$${number}`
////}
////class USD implements DollarSign {
//// /*a*/
@@ -25,6 +28,24 @@ verify.completions({
sortText: completion.SortText.ClassMemberSnippets,
isSnippet: true,
insertText: "\"\\$usd\"(a: number): number {\n $0\n}",
}
},
{
name: "$cad",
sortText: completion.SortText.ClassMemberSnippets,
isSnippet: true,
insertText: "\\$cad(b: number): number {\n $0\n}",
},
{
name: "cla$$y",
sortText: completion.SortText.ClassMemberSnippets,
isSnippet: true,
insertText: "cla\\$\\$y(c: number): number {\n $0\n}",
},
{
name: "isDollarAmountString",
sortText: completion.SortText.ClassMemberSnippets,
isSnippet: true,
insertText: "isDollarAmountString(s: string): s is `\\$\\${number}` {\n $0\n}"
},
],
});