Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 90 additions & 10 deletions packages/typescript/src/api/async/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CheckFlags } from "#enums/checkFlags";
import { CompletionItemKind } from "#enums/completionItemKind";
import { DiagnosticCategory } from "#enums/diagnosticCategory";
import { ElementFlags } from "#enums/elementFlags";
import { EmitFlags } from "#enums/emitFlags";
import { EmitOnly } from "#enums/emitOnly";
import { IndexKind } from "#enums/indexKind";
import { JsxEmit } from "#enums/jsxEmit";
Expand Down Expand Up @@ -170,7 +171,7 @@ import type {

export { formatDiagnostics, formatDiagnosticsWithColorAndContext } from "../diagnosticFormatter.ts";
export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts";
export { CheckFlags, CompletionItemKind, DiagnosticCategory, ElementFlags, EmitOnly, IndexKind, JsxEmit, ModifierFlags, ModuleKind, ModuleResolutionKind, NodeBuilderFlags, ObjectFlags, ScriptKind, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypeFormatFlags, TypePredicateKind };
export { CheckFlags, CompletionItemKind, DiagnosticCategory, ElementFlags, EmitFlags, EmitOnly, IndexKind, JsxEmit, ModifierFlags, ModuleKind, ModuleResolutionKind, NodeBuilderFlags, ObjectFlags, ScriptKind, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypeFormatFlags, TypePredicateKind };
export type {
APIImportAdderAction as ImportAdderAction,
APIOptions,
Expand Down Expand Up @@ -2937,6 +2938,36 @@ export interface PrintNodeOptions {
preserveSourceNewlines?: boolean | undefined;
neverAsciiEscape?: boolean | undefined;
terminateUnterminatedLiterals?: boolean | undefined;
emitContext?: EmitContext | undefined;
}
type CommentKind = SyntaxKind.MultiLineCommentTrivia | SyntaxKind.SingleLineCommentTrivia;
export interface SynthesizedComment {
kind: CommentKind;
text: string;
hasTrailingNewLine: boolean;
hasLeadingNewLine: boolean;
}

/** @internal */
interface EmitNode {
emitFlags: EmitFlags;
leadingComments: SynthesizedComment[] | undefined;
trailingComments: SynthesizedComment[] | undefined;
}

export interface EmitContext {
/** @internal */
getEmitNode(node: Node): EmitNode | undefined;
setEmitFlags<T extends Node>(node: T, emitFlags: EmitFlags): T;
getEmitFlags(node: Node): EmitFlags;

addSyntheticLeadingComment(node: Node, kind: CommentKind, text: string, hasTrailingNewLine: boolean, hasLeadingNewLine: boolean): Node;
setSyntheticLeadingComments<T extends Node>(node: T, comments: SynthesizedComment[]): T;
getSyntheticLeadingComments(node: Node): readonly SynthesizedComment[] | undefined;

addSyntheticTrailingComment(node: Node, kind: CommentKind, text: string, hasTrailingNewLine: boolean, hasLeadingNewLine: boolean): Node;
setSyntheticTrailingComments<T extends Node>(node: T, comments: SynthesizedComment[]): T;
getSyntheticTrailingComments(node: Node): readonly SynthesizedComment[] | undefined;
}

export class Printer {
Expand All @@ -2947,25 +2978,74 @@ export class Printer {
}

async printNode(node: Node, options: PrintNodeOptions = {}): Promise<string> {
const encoded = encodeNode(node);
const emitNodes: Record<number, EmitNode> = {};
const encoded = encodeNode(node, (node, index) => {
const emitNode = options.emitContext?.getEmitNode(node);
if (emitNode === undefined) return;
emitNodes[index] = emitNode;
});
const base64 = uint8ArrayToBase64(encoded);
return this.client.apiRequest("printNode", {
data: base64,
preserveSourceNewlines: options.preserveSourceNewlines,
neverAsciiEscape: options.neverAsciiEscape,
terminateUnterminatedLiterals: options.terminateUnterminatedLiterals,
emitNodes,
});
}

async printFile(sourceFile: SourceFile, options: PrintNodeOptions = {}): Promise<string> {
const encoded = encodeNode(sourceFile);
const base64 = uint8ArrayToBase64(encoded);
return this.client.apiRequest("printNode", {
data: base64,
preserveSourceNewlines: options.preserveSourceNewlines,
neverAsciiEscape: options.neverAsciiEscape,
terminateUnterminatedLiterals: options.terminateUnterminatedLiterals,
});
return this.printNode(sourceFile, options);
}

createEmitContext(): EmitContext {
const emitNodes = new WeakMap<Node, EmitNode>();

function getOrCreateEmitNode(node: Node): EmitNode {
let emitNode = emitNodes.get(node);
if (!emitNode) {
emitNode = { emitFlags: 0 as EmitFlags, leadingComments: undefined, trailingComments: undefined };
emitNodes.set(node, emitNode);
}
return emitNode;
}

return {
getEmitNode(node) {
return emitNodes.get(node);
},
setEmitFlags(node, emitFlags) {
getOrCreateEmitNode(node).emitFlags = emitFlags;
return node;
},
getEmitFlags(node) {
return emitNodes.get(node)?.emitFlags ?? 0 as EmitFlags;
},

addSyntheticLeadingComment(node, kind, text, hasTrailingNewLine, hasLeadingNewLine) {
(getOrCreateEmitNode(node).leadingComments ??= []).push({ kind, text, hasTrailingNewLine, hasLeadingNewLine });
return node;
},
setSyntheticLeadingComments(node, comments) {
getOrCreateEmitNode(node).leadingComments = comments;
return node;
},
getSyntheticLeadingComments(node) {
return emitNodes.get(node)?.leadingComments;
},

addSyntheticTrailingComment(node, kind, text, hasTrailingNewLine, hasLeadingNewLine) {
(getOrCreateEmitNode(node).trailingComments ??= []).push({ kind, text, hasTrailingNewLine, hasLeadingNewLine });
return node;
},
setSyntheticTrailingComments(node, comments) {
getOrCreateEmitNode(node).trailingComments = comments;
return node;
},
getSyntheticTrailingComments(node) {
return emitNodes.get(node)?.trailingComments;
},
};
}
}

Expand Down
5 changes: 3 additions & 2 deletions packages/typescript/src/api/node/encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export function encodeSourceFile(sourceFile: SourceFile): Uint8Array {
* Encode an arbitrary AST node into the binary format.
* When encoding a non-SourceFile node, the header hash and parse options fields will be zero.
*/
export function encodeNode(node: Node): Uint8Array {
export function encodeNode(node: Node, onNodeEncoded?: (node: Node, index: number) => void): Uint8Array {
const strs = new StringTable();
const extendedDataValues: number[] = [];
const structuredWriter = new MsgpackWriter();
Expand Down Expand Up @@ -233,7 +233,7 @@ export function encodeNode(node: Node): Uint8Array {
data,
node.flags,
);

onNodeEncoded?.(node, currentIndex);
const saveParentIndex = parentIndex;
const savePrevIndex = prevIndex;
parentIndex = currentIndex;
Expand Down Expand Up @@ -315,6 +315,7 @@ export function encodeNode(node: Node): Uint8Array {
const saveParent = parentIndex;
prevIndex = 0;
parentIndex = 1; // root is at index 1
onNodeEncoded?.(node, 1);
visitChildren(node);
parentIndex = saveParent;

Expand Down
16 changes: 16 additions & 0 deletions packages/typescript/src/api/proto.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,7 @@ export interface PrintNodeParams {
preserveSourceNewlines?: boolean | undefined;
neverAsciiEscape?: boolean | undefined;
terminateUnterminatedLiterals?: boolean | undefined;
emitNodes?: Record<string, PrintEmitNode | null> | undefined;
}

/** FormatNodeForInsertionParams are the parameters for the formatNodeForInsertion method. */
Expand Down Expand Up @@ -1727,6 +1728,13 @@ export interface DiagnosticSourceLineResponse {
text: string;
}

/** Extra information for a node used during node printing */
export interface PrintEmitNode {
emitFlags?: number | undefined;
leadingComments?: PrintSynthesizedComment[] | undefined;
trailingComments?: PrintSynthesizedComment[] | undefined;
}

export interface EmitOutputFile {
fileName: string;
text: string;
Expand Down Expand Up @@ -1798,6 +1806,14 @@ export interface CompletionEntryLabelDetailsResponse {
description?: string | undefined;
}

/** Synthetic comment added during node printing */
export interface PrintSynthesizedComment {
kind: number;
text: string;
hasTrailingNewLine?: boolean | undefined;
hasLeadingNewLine?: boolean | undefined;
}

export interface CreateProgramOptions {
projectReferences?: ProjectReference[] | undefined;
configFileParsingDiagnostics?: DiagnosticResponse[] | undefined;
Expand Down
117 changes: 98 additions & 19 deletions packages/typescript/src/api/sync/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { CheckFlags } from "#enums/checkFlags";
import { CompletionItemKind } from "#enums/completionItemKind";
import { DiagnosticCategory } from "#enums/diagnosticCategory";
import { ElementFlags } from "#enums/elementFlags";
import { EmitFlags } from "#enums/emitFlags";
import { EmitOnly } from "#enums/emitOnly";
import { IndexKind } from "#enums/indexKind";
import { JsxEmit } from "#enums/jsxEmit";
Expand Down Expand Up @@ -187,7 +188,7 @@ import type {

export { formatDiagnostics, formatDiagnosticsWithColorAndContext } from "../diagnosticFormatter.ts";
export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts";
export { CheckFlags, CompletionItemKind, DiagnosticCategory, ElementFlags, EmitOnly, IndexKind, JsxEmit, ModifierFlags, ModuleKind, ModuleResolutionKind, NodeBuilderFlags, ObjectFlags, ScriptKind, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypeFormatFlags, TypePredicateKind };
export { CheckFlags, CompletionItemKind, DiagnosticCategory, ElementFlags, EmitFlags, EmitOnly, IndexKind, JsxEmit, ModifierFlags, ModuleKind, ModuleResolutionKind, NodeBuilderFlags, ObjectFlags, ScriptKind, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypeFormatFlags, TypePredicateKind };
export type {
APIImportAdderAction as ImportAdderAction,
APIOptions,
Expand Down Expand Up @@ -6191,6 +6192,36 @@ export interface PrintNodeOptions {
preserveSourceNewlines?: boolean | undefined;
neverAsciiEscape?: boolean | undefined;
terminateUnterminatedLiterals?: boolean | undefined;
emitContext?: EmitContext | undefined;
}
type CommentKind = SyntaxKind.MultiLineCommentTrivia | SyntaxKind.SingleLineCommentTrivia;
export interface SynthesizedComment {
kind: CommentKind;
text: string;
hasTrailingNewLine: boolean;
hasLeadingNewLine: boolean;
}

/** @internal */
interface EmitNode {
emitFlags: EmitFlags;
leadingComments: SynthesizedComment[] | undefined;
trailingComments: SynthesizedComment[] | undefined;
}

export interface EmitContext {
/** @internal */
getEmitNode(node: Node): EmitNode | undefined;
setEmitFlags<T extends Node>(node: T, emitFlags: EmitFlags): T;
getEmitFlags(node: Node): EmitFlags;

addSyntheticLeadingComment(node: Node, kind: CommentKind, text: string, hasTrailingNewLine: boolean, hasLeadingNewLine: boolean): Node;
setSyntheticLeadingComments<T extends Node>(node: T, comments: SynthesizedComment[]): T;
getSyntheticLeadingComments(node: Node): readonly SynthesizedComment[] | undefined;

addSyntheticTrailingComment(node: Node, kind: CommentKind, text: string, hasTrailingNewLine: boolean, hasLeadingNewLine: boolean): Node;
setSyntheticTrailingComments<T extends Node>(node: T, comments: SynthesizedComment[]): T;
getSyntheticTrailingComments(node: Node): readonly SynthesizedComment[] | undefined;
}

export class Printer {
Expand All @@ -6209,23 +6240,35 @@ export class Printer {
owner,
"printNode",
function (node: Node, options: PrintNodeOptions = {}): string {
const encoded = encodeNode(node);
const emitNodes: Record<number, EmitNode> = {};
const encoded = encodeNode(node, (node, index) => {
const emitNode = options.emitContext?.getEmitNode(node);
if (emitNode === undefined) return;
emitNodes[index] = emitNode;
});
const base64 = uint8ArrayToBase64(encoded);
return owner.client.apiRequest("printNode", {
data: base64,
preserveSourceNewlines: options.preserveSourceNewlines,
neverAsciiEscape: options.neverAsciiEscape,
terminateUnterminatedLiterals: options.terminateUnterminatedLiterals,
emitNodes,
});
},
function* (node: Node, options: PrintNodeOptions = {}): Generator<ProtocolRequest, string, ProtocolResponse["result"]> {
const encoded = encodeNode(node);
const emitNodes: Record<number, EmitNode> = {};
const encoded = encodeNode(node, (node, index) => {
const emitNode = options.emitContext?.getEmitNode(node);
if (emitNode === undefined) return;
emitNodes[index] = emitNode;
});
const base64 = uint8ArrayToBase64(encoded);
return yield* apiRequest("printNode", {
data: base64,
preserveSourceNewlines: options.preserveSourceNewlines,
neverAsciiEscape: options.neverAsciiEscape,
terminateUnterminatedLiterals: options.terminateUnterminatedLiterals,
emitNodes,
});
},
);
Expand All @@ -6240,27 +6283,63 @@ export class Printer {
owner,
"printFile",
function (sourceFile: SourceFile, options: PrintNodeOptions = {}): string {
const encoded = encodeNode(sourceFile);
const base64 = uint8ArrayToBase64(encoded);
return owner.client.apiRequest("printNode", {
data: base64,
preserveSourceNewlines: options.preserveSourceNewlines,
neverAsciiEscape: options.neverAsciiEscape,
terminateUnterminatedLiterals: options.terminateUnterminatedLiterals,
});
return owner.printNode(sourceFile, options);
},
function* (sourceFile: SourceFile, options: PrintNodeOptions = {}): Generator<ProtocolRequest, string, ProtocolResponse["result"]> {
const encoded = encodeNode(sourceFile);
const base64 = uint8ArrayToBase64(encoded);
return yield* apiRequest("printNode", {
data: base64,
preserveSourceNewlines: options.preserveSourceNewlines,
neverAsciiEscape: options.neverAsciiEscape,
terminateUnterminatedLiterals: options.terminateUnterminatedLiterals,
});
return yield* owner.printNode.gen(sourceFile, options);
},
);
}

createEmitContext(): EmitContext {
const emitNodes = new WeakMap<Node, EmitNode>();

function getOrCreateEmitNode(node: Node): EmitNode {
let emitNode = emitNodes.get(node);
if (!emitNode) {
emitNode = { emitFlags: 0 as EmitFlags, leadingComments: undefined, trailingComments: undefined };
emitNodes.set(node, emitNode);
}
return emitNode;
}

return {
getEmitNode(node) {
return emitNodes.get(node);
},
setEmitFlags(node, emitFlags) {
getOrCreateEmitNode(node).emitFlags = emitFlags;
return node;
},
getEmitFlags(node) {
return emitNodes.get(node)?.emitFlags ?? 0 as EmitFlags;
},

addSyntheticLeadingComment(node, kind, text, hasTrailingNewLine, hasLeadingNewLine) {
(getOrCreateEmitNode(node).leadingComments ??= []).push({ kind, text, hasTrailingNewLine, hasLeadingNewLine });
return node;
},
setSyntheticLeadingComments(node, comments) {
getOrCreateEmitNode(node).leadingComments = comments;
return node;
},
getSyntheticLeadingComments(node) {
return emitNodes.get(node)?.leadingComments;
},

addSyntheticTrailingComment(node, kind, text, hasTrailingNewLine, hasLeadingNewLine) {
(getOrCreateEmitNode(node).trailingComments ??= []).push({ kind, text, hasTrailingNewLine, hasLeadingNewLine });
return node;
},
setSyntheticTrailingComments(node, comments) {
getOrCreateEmitNode(node).trailingComments = comments;
return node;
},
getSyntheticTrailingComments(node) {
return emitNodes.get(node)?.trailingComments;
},
};
}
}

export class SnapshotInternalAPI {
Expand Down
Loading