Skip to content

Commit 7abdb9e

Browse files
authored
Format completion snippet text before escaping (microsoft#48793)
* Format snippet text before escaping * Reset `escapes` before printing so printer can be reused
1 parent ab2523b commit 7abdb9e

File tree

2 files changed

+63
-10
lines changed

2 files changed

+63
-10
lines changed

Diff for: src/services/completions.ts

+41-9
Original file line numberDiff line numberDiff line change
@@ -1199,31 +1199,59 @@ namespace ts.Completions {
11991199
function createSnippetPrinter(
12001200
printerOptions: PrinterOptions,
12011201
) {
1202+
let escapes: TextChange[] | undefined;
12021203
const baseWriter = textChanges.createWriter(getNewLineCharacter(printerOptions));
12031204
const printer = createPrinter(printerOptions, baseWriter);
12041205
const writer: EmitTextWriter = {
12051206
...baseWriter,
1206-
write: s => baseWriter.write(escapeSnippetText(s)),
1207+
write: s => escapingWrite(s, () => baseWriter.write(s)),
12071208
nonEscapingWrite: baseWriter.write,
1208-
writeLiteral: s => baseWriter.writeLiteral(escapeSnippetText(s)),
1209-
writeStringLiteral: s => baseWriter.writeStringLiteral(escapeSnippetText(s)),
1210-
writeSymbol: (s, symbol) => baseWriter.writeSymbol(escapeSnippetText(s), symbol),
1211-
writeParameter: s => baseWriter.writeParameter(escapeSnippetText(s)),
1212-
writeComment: s => baseWriter.writeComment(escapeSnippetText(s)),
1213-
writeProperty: s => baseWriter.writeProperty(escapeSnippetText(s)),
1209+
writeLiteral: s => escapingWrite(s, () => baseWriter.writeLiteral(s)),
1210+
writeStringLiteral: s => escapingWrite(s, () => baseWriter.writeStringLiteral(s)),
1211+
writeSymbol: (s, symbol) => escapingWrite(s, () => baseWriter.writeSymbol(s, symbol)),
1212+
writeParameter: s => escapingWrite(s, () => baseWriter.writeParameter(s)),
1213+
writeComment: s => escapingWrite(s, () => baseWriter.writeComment(s)),
1214+
writeProperty: s => escapingWrite(s, () => baseWriter.writeProperty(s)),
12141215
};
12151216

12161217
return {
12171218
printSnippetList,
12181219
printAndFormatSnippetList,
12191220
};
12201221

1222+
// The formatter/scanner will have issues with snippet-escaped text,
1223+
// so instead of writing the escaped text directly to the writer,
1224+
// generate a set of changes that can be applied to the unescaped text
1225+
// to escape it post-formatting.
1226+
function escapingWrite(s: string, write: () => void) {
1227+
const escaped = escapeSnippetText(s);
1228+
if (escaped !== s) {
1229+
const start = baseWriter.getTextPos();
1230+
write();
1231+
const end = baseWriter.getTextPos();
1232+
escapes = append(escapes ||= [], { newText: escaped, span: { start, length: end - start } });
1233+
}
1234+
else {
1235+
write();
1236+
}
1237+
}
1238+
12211239
/* Snippet-escaping version of `printer.printList`. */
12221240
function printSnippetList(
12231241
format: ListFormat,
12241242
list: NodeArray<Node>,
12251243
sourceFile: SourceFile | undefined,
12261244
): string {
1245+
const unescaped = printUnescapedSnippetList(format, list, sourceFile);
1246+
return escapes ? textChanges.applyChanges(unescaped, escapes) : unescaped;
1247+
}
1248+
1249+
function printUnescapedSnippetList(
1250+
format: ListFormat,
1251+
list: NodeArray<Node>,
1252+
sourceFile: SourceFile | undefined,
1253+
): string {
1254+
escapes = undefined;
12271255
writer.clear();
12281256
printer.writeList(format, list, sourceFile, writer);
12291257
return writer.getText();
@@ -1236,7 +1264,7 @@ namespace ts.Completions {
12361264
formatContext: formatting.FormatContext,
12371265
): string {
12381266
const syntheticFile = {
1239-
text: printSnippetList(
1267+
text: printUnescapedSnippetList(
12401268
format,
12411269
list,
12421270
sourceFile),
@@ -1256,7 +1284,11 @@ namespace ts.Completions {
12561284
/* delta */ 0,
12571285
{ ...formatContext, options: formatOptions });
12581286
});
1259-
return textChanges.applyChanges(syntheticFile.text, changes);
1287+
1288+
const allChanges = escapes
1289+
? stableSort(concatenate(changes, escapes), (a, b) => compareTextSpans(a.span, b.span))
1290+
: changes;
1291+
return textChanges.applyChanges(syntheticFile.text, allChanges);
12601292
}
12611293
}
12621294

Diff for: tests/cases/fourslash/completionsOverridingMethod2.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
// Case: Snippet text needs escaping
66
////interface DollarSign {
77
//// "$usd"(a: number): number;
8+
//// $cad(b: number): number;
9+
//// cla$$y(c: number): number;
10+
//// isDollarAmountString(s: string): s is `$${number}`
811
////}
912
////class USD implements DollarSign {
1013
//// /*a*/
@@ -25,6 +28,24 @@ verify.completions({
2528
sortText: completion.SortText.ClassMemberSnippets,
2629
isSnippet: true,
2730
insertText: "\"\\$usd\"(a: number): number {\n $0\n}",
28-
}
31+
},
32+
{
33+
name: "$cad",
34+
sortText: completion.SortText.ClassMemberSnippets,
35+
isSnippet: true,
36+
insertText: "\\$cad(b: number): number {\n $0\n}",
37+
},
38+
{
39+
name: "cla$$y",
40+
sortText: completion.SortText.ClassMemberSnippets,
41+
isSnippet: true,
42+
insertText: "cla\\$\\$y(c: number): number {\n $0\n}",
43+
},
44+
{
45+
name: "isDollarAmountString",
46+
sortText: completion.SortText.ClassMemberSnippets,
47+
isSnippet: true,
48+
insertText: "isDollarAmountString(s: string): s is `\\$\\${number}` {\n $0\n}"
49+
},
2950
],
3051
});

0 commit comments

Comments
 (0)