mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-11 02:15:10 -05:00
support escaping in snippets
This commit is contained in:
@@ -867,6 +867,27 @@ namespace ts {
|
||||
const newLine = getNewLineCharacter(printerOptions);
|
||||
const moduleKind = getEmitModuleKind(printerOptions);
|
||||
const bundledHelpers = new Map<string, boolean>();
|
||||
const hasSnippet = !!printerOptions.hasSnippet;
|
||||
|
||||
// Writers that must handle snippet text escaping, if enabled
|
||||
let writeLiteral = noEscapeWriteLiteral;
|
||||
let writeStringLiteral = noEscapeWriteStringLiteral;
|
||||
let writeBase = noEscapeWriteBase;
|
||||
let writeSymbol = noEscapeWriteSymbol;
|
||||
let writeParameter = noEscapeWriteParameter;
|
||||
let writeComment = noEscapeWriteComment;
|
||||
let writeProperty = noEscapeWriteProperty;
|
||||
|
||||
if (hasSnippet) {
|
||||
writeLiteral = escapeWriteLiteral;
|
||||
writeStringLiteral = escapeWriteStringLiteral;
|
||||
writeBase = escapeWriteBase;
|
||||
writeSymbol = escapeWriteSymbol;
|
||||
writeParameter = escapeWriteParameter;
|
||||
writeComment = escapeWriteComment;
|
||||
writeProperty = escapeWriteProperty;
|
||||
}
|
||||
|
||||
|
||||
let currentSourceFile: SourceFile | undefined;
|
||||
let nodeIdToGeneratedName: string[]; // Map of generated names for specific nodes.
|
||||
@@ -911,9 +932,6 @@ namespace ts {
|
||||
const parenthesizer = factory.parenthesizer;
|
||||
const emitBinaryExpression = createEmitBinaryExpression();
|
||||
|
||||
// Snippets
|
||||
let inSnippet = false;
|
||||
|
||||
reset();
|
||||
return {
|
||||
// public API
|
||||
@@ -1286,14 +1304,16 @@ namespace ts {
|
||||
currentParenthesizerRule = undefined;
|
||||
}
|
||||
|
||||
// >> TODO: remove allowSnippets
|
||||
function pipelineEmitWithHintWorker(hint: EmitHint, node: Node, _allowSnippets = true): void {
|
||||
if (!inSnippet) {
|
||||
function pipelineEmitWithHintWorker(hint: EmitHint, node: Node, allowSnippets = true): void {
|
||||
if (allowSnippets) {
|
||||
const snippet = getSnippetElement(node);
|
||||
if (snippet) {
|
||||
return emitSnippetNode(hint, node, snippet);
|
||||
}
|
||||
}
|
||||
// else {
|
||||
// Debug.assert(!getSnippetElement(node), "A snippet cannot exist inside another snippet.");
|
||||
// }
|
||||
if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile));
|
||||
if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier));
|
||||
if (hint === EmitHint.JsxAttributeValue) return emitLiteral(cast(node, isStringLiteral), /*jsxAttributeEscape*/ true);
|
||||
@@ -1881,19 +1901,14 @@ namespace ts {
|
||||
const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape, jsxAttributeEscape);
|
||||
if ((printerOptions.sourceMap || printerOptions.inlineSourceMap)
|
||||
&& (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) {
|
||||
writeLiteral(inSnippet ? escapeSnippetText(text) : text);
|
||||
writeLiteral(text);
|
||||
}
|
||||
else {
|
||||
// Quick info expects all literals to be called with writeStringLiteral, as there's no specific type for numberLiterals
|
||||
writeStringLiteral(inSnippet ? escapeSnippetText(text) : text);
|
||||
writeStringLiteral(text);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move this
|
||||
function escapeSnippetText(text: string): string {
|
||||
return text.replace(/\$/gm, "\\$");
|
||||
}
|
||||
|
||||
// SyntaxKind.UnparsedSource
|
||||
// SyntaxKind.UnparsedPrepend
|
||||
function emitUnparsedSourceOrPrepend(unparsed: UnparsedSource | UnparsedPrepend) {
|
||||
@@ -1944,7 +1959,6 @@ namespace ts {
|
||||
//
|
||||
|
||||
function emitSnippetNode(hint: EmitHint, node: Node, snippet: SnippetElement) {
|
||||
inSnippet = true;
|
||||
switch (snippet.kind) {
|
||||
case SnippetKind.Placeholder:
|
||||
emitPlaceholder(hint, node, snippet);
|
||||
@@ -1953,18 +1967,17 @@ namespace ts {
|
||||
emitTabStop(snippet);
|
||||
break;
|
||||
}
|
||||
inSnippet = false;
|
||||
}
|
||||
|
||||
function emitPlaceholder(hint: EmitHint, node: Node, snippet: Placeholder) {
|
||||
write(`\$\{${snippet.order}:`); // `${2:`
|
||||
noEscapeWrite(`\$\{${snippet.order}:`); // `${2:`
|
||||
pipelineEmitWithHintWorker(hint, node, /*allowSnippets*/ false); // `...`
|
||||
write(`\}`); // `}`
|
||||
noEscapeWrite(`\}`); // `}`
|
||||
// `${2:...}`
|
||||
}
|
||||
|
||||
function emitTabStop(snippet: TabStop) {
|
||||
write(`\$${snippet.order}`);
|
||||
noEscapeWrite(`\$${snippet.order}`);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1973,8 +1986,7 @@ namespace ts {
|
||||
|
||||
function emitIdentifier(node: Identifier) {
|
||||
const writeText = node.symbol ? writeSymbol : write;
|
||||
const text = getTextOfNode(node, /*includeTrivia*/ false);
|
||||
writeText(inSnippet ? escapeSnippetText(text) : text, node.symbol);
|
||||
writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol);
|
||||
emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments
|
||||
}
|
||||
|
||||
@@ -1984,8 +1996,7 @@ namespace ts {
|
||||
|
||||
function emitPrivateIdentifier(node: PrivateIdentifier) {
|
||||
const writeText = node.symbol ? writeSymbol : write;
|
||||
const text = getTextOfNode(node, /*includeTrivia*/ false);
|
||||
writeText(inSnippet ? escapeSnippetText(text) : text, node.symbol);
|
||||
writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol);
|
||||
}
|
||||
|
||||
|
||||
@@ -4454,22 +4465,38 @@ namespace ts {
|
||||
|
||||
// Writers
|
||||
|
||||
function writeLiteral(s: string) {
|
||||
function noEscapeWriteLiteral(s: string) {
|
||||
writer.writeLiteral(s);
|
||||
}
|
||||
|
||||
function writeStringLiteral(s: string) {
|
||||
function escapeWriteLiteral(s: string) {
|
||||
writer.writeLiteral(escapeSnippetText(s));
|
||||
}
|
||||
|
||||
function noEscapeWriteStringLiteral(s: string) {
|
||||
writer.writeStringLiteral(s);
|
||||
}
|
||||
|
||||
function writeBase(s: string) {
|
||||
function escapeWriteStringLiteral(s: string) {
|
||||
writer.writeStringLiteral(escapeSnippetText(s));
|
||||
}
|
||||
|
||||
function noEscapeWriteBase(s: string) {
|
||||
writer.write(s);
|
||||
}
|
||||
|
||||
function writeSymbol(s: string, sym: Symbol) {
|
||||
function escapeWriteBase(s: string) {
|
||||
writer.write(escapeSnippetText(s));
|
||||
}
|
||||
|
||||
function noEscapeWriteSymbol(s: string, sym: Symbol) {
|
||||
writer.writeSymbol(s, sym);
|
||||
}
|
||||
|
||||
function escapeWriteSymbol(s: string, sym: Symbol) {
|
||||
writer.writeSymbol(escapeSnippetText(s), sym);
|
||||
}
|
||||
|
||||
function writePunctuation(s: string) {
|
||||
writer.writePunctuation(s);
|
||||
}
|
||||
@@ -4486,28 +4513,48 @@ namespace ts {
|
||||
writer.writeOperator(s);
|
||||
}
|
||||
|
||||
function writeParameter(s: string) {
|
||||
function noEscapeWriteParameter(s: string) {
|
||||
writer.writeParameter(s);
|
||||
}
|
||||
|
||||
function writeComment(s: string) {
|
||||
function escapeWriteParameter(s: string) {
|
||||
writer.writeParameter(escapeSnippetText(s));
|
||||
}
|
||||
|
||||
function noEscapeWriteComment(s: string) {
|
||||
writer.writeComment(s);
|
||||
}
|
||||
|
||||
function escapeWriteComment(s: string) {
|
||||
writer.writeComment(escapeSnippetText(s));
|
||||
}
|
||||
|
||||
function writeSpace() {
|
||||
writer.writeSpace(" ");
|
||||
}
|
||||
|
||||
function writeProperty(s: string) {
|
||||
function noEscapeWriteProperty(s: string) {
|
||||
writer.writeProperty(s);
|
||||
}
|
||||
|
||||
function escapeWriteProperty(s: string) {
|
||||
writer.writeProperty(escapeSnippetText(s));
|
||||
}
|
||||
|
||||
function writeLine(count = 1) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
writer.writeLine(i > 0);
|
||||
}
|
||||
}
|
||||
|
||||
function noEscapeWrite(s: string) { // >> update
|
||||
writer.write(s);
|
||||
}
|
||||
|
||||
function escapeSnippetText(text: string): string {
|
||||
return text.replace(/\$/gm, "\\$");
|
||||
}
|
||||
|
||||
function increaseIndent() {
|
||||
writer.increaseIndent();
|
||||
}
|
||||
|
||||
@@ -8201,6 +8201,7 @@ namespace ts {
|
||||
/*@internal*/ preserveSourceNewlines?: boolean;
|
||||
/*@internal*/ terminateUnterminatedLiterals?: boolean;
|
||||
/*@internal*/ relativeToBuildInfo?: (path: string) => string;
|
||||
/*@internal*/ hasSnippet?: boolean;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
||||
@@ -807,6 +807,7 @@ namespace ts.Completions {
|
||||
module: options.module,
|
||||
target: options.target,
|
||||
omitTrailingSemicolon: true,
|
||||
hasSnippet: true,
|
||||
});
|
||||
const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host);
|
||||
|
||||
@@ -908,7 +909,7 @@ namespace ts.Completions {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move to snippets file?
|
||||
// TODO: move to services/utilities?
|
||||
function escapeSnippetText(text: string): string {
|
||||
return text.replace(/\$/gm, "\\$");
|
||||
}
|
||||
|
||||
38
tests/cases/fourslash/completionsOverridingMethod2.ts
Normal file
38
tests/cases/fourslash/completionsOverridingMethod2.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: a.ts
|
||||
// Case: Snippet text needs escaping
|
||||
////interface DollarSign {
|
||||
//// "$usd"(a: number): number;
|
||||
////}
|
||||
////class USD implements DollarSign {
|
||||
//// /*a*/
|
||||
////}
|
||||
|
||||
// format.setFormatOptions({
|
||||
// newLineCharacter: "\n",
|
||||
// });
|
||||
// format.setOption("newline", "\n");
|
||||
|
||||
verify.completions({
|
||||
marker: "a",
|
||||
isNewIdentifierLocation: true,
|
||||
preferences: {
|
||||
includeCompletionsWithInsertText: true,
|
||||
includeCompletionsWithSnippetText: true,
|
||||
},
|
||||
includes: [
|
||||
{
|
||||
name: "$usd",
|
||||
sortText: completion.SortText.LocationPriority,
|
||||
replacementSpan: {
|
||||
fileName: "",
|
||||
pos: 0,
|
||||
end: 0,
|
||||
},
|
||||
isSnippet: true,
|
||||
insertText:
|
||||
"$usd(a: number): number {\r\n $0\r\n}\r\n",
|
||||
}
|
||||
],
|
||||
});
|
||||
Reference in New Issue
Block a user