diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 996cbfcca5c..cde27013087 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -911,6 +911,9 @@ namespace ts { const parenthesizer = factory.parenthesizer; const emitBinaryExpression = createEmitBinaryExpression(); + // Snippets + let inSnippet = false; + reset(); return { // public API @@ -1283,8 +1286,9 @@ namespace ts { currentParenthesizerRule = undefined; } - function pipelineEmitWithHintWorker(hint: EmitHint, node: Node, allowSnippets = true): void { - if (allowSnippets) { + // >> TODO: remove allowSnippets + function pipelineEmitWithHintWorker(hint: EmitHint, node: Node, _allowSnippets = true): void { + if (!inSnippet) { const snippet = getSnippetElement(node); if (snippet) { return emitSnippetNode(hint, node, snippet); @@ -1877,14 +1881,19 @@ namespace ts { const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape, jsxAttributeEscape); if ((printerOptions.sourceMap || printerOptions.inlineSourceMap) && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { - writeLiteral(text); + writeLiteral(inSnippet ? escapeSnippetText(text) : text); } else { // Quick info expects all literals to be called with writeStringLiteral, as there's no specific type for numberLiterals - writeStringLiteral(text); + writeStringLiteral(inSnippet ? escapeSnippetText(text) : text); } } + // TODO: move this + function escapeSnippetText(text: string): string { + return text.replace(/\$/gm, "\\$"); + } + // SyntaxKind.UnparsedSource // SyntaxKind.UnparsedPrepend function emitUnparsedSourceOrPrepend(unparsed: UnparsedSource | UnparsedPrepend) { @@ -1935,12 +1944,16 @@ namespace ts { // function emitSnippetNode(hint: EmitHint, node: Node, snippet: SnippetElement) { + inSnippet = true; switch (snippet.kind) { case SnippetKind.Placeholder: - return emitPlaceholder(hint, node, snippet); + emitPlaceholder(hint, node, snippet); + break; case SnippetKind.TabStop: - return emitTabStop(snippet); + emitTabStop(snippet); + break; } + inSnippet = false; } function emitPlaceholder(hint: EmitHint, node: Node, snippet: Placeholder) { @@ -1960,7 +1973,8 @@ namespace ts { function emitIdentifier(node: Identifier) { const writeText = node.symbol ? writeSymbol : write; - writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); + const text = getTextOfNode(node, /*includeTrivia*/ false); + writeText(inSnippet ? escapeSnippetText(text) : text, node.symbol); emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments } @@ -1970,7 +1984,8 @@ namespace ts { function emitPrivateIdentifier(node: PrivateIdentifier) { const writeText = node.symbol ? writeSymbol : write; - writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol); + const text = getTextOfNode(node, /*includeTrivia*/ false); + writeText(inSnippet ? escapeSnippetText(text) : text, node.symbol); } diff --git a/src/services/snippets.ts b/src/services/snippets.ts index b1590d57c27..f6e4a821c77 100644 --- a/src/services/snippets.ts +++ b/src/services/snippets.ts @@ -1,13 +1,36 @@ +/* eslint-disable no-fallthrough */ /* @internal */ namespace ts.snippets { // interface SnippetWriter extends EmitTextWriter, PrintHandlers {} // function createSnippetWriter(newLine: string): SnippetWriter { - // const substituteNode = (hint: EmitHint, node: Node) => { - // switch (hint) { - // case EmitHint.IdentifierName: - // // >> TODO: do escaping here - // return node; + // const substituteNode = (_hint: EmitHint, node: Node) => { + // switch (node.kind) { + // // Comments: + // // TODO: doesn't work because comments are `TextRanges` with a syntax kind, but are not nodes. + // // case SyntaxKind.SingleLineCommentTrivia: + // // case SyntaxKind.MultiLineCommentTrivia: + // // return escapeTrivia(node); + // // >> comments are emitted in previous phase + // // String-like literals: + // case SyntaxKind.StringLiteral: // >> emitLiteral + // return escapeLiteralLikeNode(node as StringLiteral); + // case SyntaxKind.RegularExpressionLiteral: // >> emitLiteral + // return escapeLiteralLikeNode(node as RegularExpressionLiteral); + // case SyntaxKind.NoSubstitutionTemplateLiteral: // >> emitLiteral + // // Has .rawText but also .text + // // Templates: + // // >> .rawText + // case SyntaxKind.TemplateHead: // >> emitLiteral + // case SyntaxKind.TemplateMiddle: // >> emitLiteral + // case SyntaxKind.TemplateTail: // >> emitLiteral + // // Identifiers: + // case SyntaxKind.Identifier: // >> emitIdentifier + // case SyntaxKind.PrivateIdentifier: // >> emitPrivateIdentifier + // // Other: + // // >> TODO: JSX? JSDoc? + + // return undefined; // } // return node; // }; @@ -20,4 +43,14 @@ namespace ts.snippets { // substituteNode, // }; // } + + // function escapeLiteralLikeNode(node: T): T { + // const copy = getSynthesizedDeepClone(node); + // copy.text = escapeSnippetText(node.text); + // return copy; + // } + + // function escapeSnippetText(text: string): string { + // return text.replace(/\$/gm, "\\$"); + // } }