From 3163fe7e3898c1f48cd9bc097b96e3426cd2a453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 4 Jul 2024 19:38:25 +0200 Subject: [PATCH] Fixed crash when cross-file reusing nodes for class member snippet completions (#58216) --- src/compiler/emitter.ts | 10 ++-- ...onClassMemberSnippetCrossFileNodeReuse1.ts | 47 +++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 tests/cases/fourslash/completionClassMemberSnippetCrossFileNodeReuse1.ts diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c3b8babf5f9..dfb366c6e24 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2088,7 +2088,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri // SyntaxKind.TemplateMiddle // SyntaxKind.TemplateTail function emitLiteral(node: LiteralLikeNode, jsxAttributeEscape: boolean) { - const text = getLiteralTextOfNode(node, printerOptions.neverAsciiEscape, jsxAttributeEscape); + const text = getLiteralTextOfNode(node, /*sourceFile*/ undefined, printerOptions.neverAsciiEscape, jsxAttributeEscape); if ( (printerOptions.sourceMap || printerOptions.inlineSourceMap) && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind)) @@ -2641,7 +2641,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri expression = skipPartiallyEmittedExpressions(expression); if (isNumericLiteral(expression)) { // check if numeric literal is a decimal literal that was originally written with a dot - const text = getLiteralTextOfNode(expression as LiteralExpression, /*neverAsciiEscape*/ true, /*jsxAttributeEscape*/ false); + const text = getLiteralTextOfNode(expression as LiteralExpression, /*sourceFile*/ undefined, /*neverAsciiEscape*/ true, /*jsxAttributeEscape*/ false); // If the number will be printed verbatim and it doesn't already contain a dot or an exponent indicator, add one // if the expression doesn't have any comments that will be emitted. return !(expression.numericLiteralFlags & TokenFlags.WithSpecifier) @@ -5169,7 +5169,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri return getSourceTextOfNodeFromSourceFile(sourceFile, node, includeTrivia); } - function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined, jsxAttributeEscape: boolean): string { + function getLiteralTextOfNode(node: LiteralLikeNode, sourceFile = currentSourceFile, neverAsciiEscape: boolean | undefined, jsxAttributeEscape: boolean): string { if (node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).textSourceNode) { const textSourceNode = (node as StringLiteral).textSourceNode!; if (isIdentifier(textSourceNode) || isPrivateIdentifier(textSourceNode) || isNumericLiteral(textSourceNode) || isJsxNamespacedName(textSourceNode)) { @@ -5179,7 +5179,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri `"${escapeNonAsciiString(text)}"`; } else { - return getLiteralTextOfNode(textSourceNode, neverAsciiEscape, jsxAttributeEscape); + return getLiteralTextOfNode(textSourceNode, getSourceFileOfNode(textSourceNode), neverAsciiEscape, jsxAttributeEscape); } } @@ -5188,7 +5188,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri | (printerOptions.terminateUnterminatedLiterals ? GetLiteralTextFlags.TerminateUnterminatedLiterals : 0) | (printerOptions.target && printerOptions.target >= ScriptTarget.ES2021 ? GetLiteralTextFlags.AllowNumericSeparator : 0); - return getLiteralText(node, currentSourceFile, flags); + return getLiteralText(node, sourceFile, flags); } /** diff --git a/tests/cases/fourslash/completionClassMemberSnippetCrossFileNodeReuse1.ts b/tests/cases/fourslash/completionClassMemberSnippetCrossFileNodeReuse1.ts new file mode 100644 index 00000000000..c03e698c86b --- /dev/null +++ b/tests/cases/fourslash/completionClassMemberSnippetCrossFileNodeReuse1.ts @@ -0,0 +1,47 @@ +/// + +// @strict: true + +// @filename: KlassConstructor.ts + +//// type GenericConstructor = new (...args: any[]) => T; +//// export type KlassConstructor> = +//// GenericConstructor> & { [k in keyof Cls]: Cls[k] }; + +// @filename: ElementNode.ts +//// import { KlassConstructor } from "./KlassConstructor"; +//// +//// export type NodeKey = string; +//// +//// export class ElementNode { +//// ["constructor"]!: KlassConstructor; +//// } + +// @filename: CollapsibleContainerNode.ts +//// import { ElementNode, NodeKey } from "./ElementNode"; +//// +//// export class CollapsibleContainerNode extends ElementNode { +//// __open: boolean; +//// +//// /*1*/ +//// } + +format.setFormatOptions({ + insertSpaceAfterConstructor: false, +}); + +verify.completions({ + marker: "1", + preferences: { + includeCompletionsWithClassMemberSnippets: true, + includeCompletionsWithInsertText: true, + }, + includes: [{ + name: `["constructor"]`, + insertText: `["constructor"]: KlassConstructor;`, + filterText: `["constructor"]`, + hasAction: true, + source: 'ClassMemberSnippet/' + }], + isNewIdentifierLocation: true, +});