• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    AccessExpression, append, appendIfUnique, Debug, EmitFlags, EmitHelper, EmitNode, getParseTreeNode,
3    getSourceFileOfNode, isParseTreeNode, Node, orderedRemoveItem, SnippetElement, some, SourceFile, SourceMapRange,
4    SyntaxKind, SynthesizedComment, TextRange, TypeNode
5} from "../_namespaces/ts";
6
7/**
8 * Associates a node with the current transformation, initializing
9 * various transient transformation properties.
10 * @internal
11 */
12export function getOrCreateEmitNode(node: Node): EmitNode {
13    if (!node.emitNode) {
14        if (isParseTreeNode(node)) {
15            // To avoid holding onto transformation artifacts, we keep track of any
16            // parse tree node we are annotating. This allows us to clean them up after
17            // all transformations have completed.
18            if (node.kind === SyntaxKind.SourceFile) {
19                return node.emitNode = { annotatedNodes: [node] } as EmitNode;
20            }
21
22            const sourceFile = getSourceFileOfNode(getParseTreeNode(getSourceFileOfNode(node))) ?? Debug.fail("Could not determine parsed source file.");
23            getOrCreateEmitNode(sourceFile).annotatedNodes!.push(node);
24        }
25
26        node.emitNode = {} as EmitNode;
27    }
28    else {
29        Debug.assert(!(node.emitNode.flags & EmitFlags.Immutable), "Invalid attempt to mutate an immutable node.");
30    }
31    return node.emitNode;
32}
33
34/**
35 * Clears any `EmitNode` entries from parse-tree nodes.
36 * @param sourceFile A source file.
37 */
38export function disposeEmitNodes(sourceFile: SourceFile | undefined) {
39    // During transformation we may need to annotate a parse tree node with transient
40    // transformation properties. As parse tree nodes live longer than transformation
41    // nodes, we need to make sure we reclaim any memory allocated for custom ranges
42    // from these nodes to ensure we do not hold onto entire subtrees just for position
43    // information. We also need to reset these nodes to a pre-transformation state
44    // for incremental parsing scenarios so that we do not impact later emit.
45    const annotatedNodes = getSourceFileOfNode(getParseTreeNode(sourceFile))?.emitNode?.annotatedNodes;
46    if (annotatedNodes) {
47        for (const node of annotatedNodes) {
48            node.emitNode = undefined;
49        }
50    }
51}
52
53/**
54 * Sets `EmitFlags.NoComments` on a node and removes any leading and trailing synthetic comments.
55 * @internal
56 */
57export function removeAllComments<T extends Node>(node: T): T {
58    const emitNode = getOrCreateEmitNode(node);
59    emitNode.flags |= EmitFlags.NoComments;
60    emitNode.leadingComments = undefined;
61    emitNode.trailingComments = undefined;
62    return node;
63}
64
65/**
66 * Sets flags that control emit behavior of a node.
67 */
68export function setEmitFlags<T extends Node>(node: T, emitFlags: EmitFlags) {
69    getOrCreateEmitNode(node).flags = emitFlags;
70    return node;
71}
72
73/**
74 * Sets flags that control emit behavior of a node.
75 * @internal
76 */
77export function addEmitFlags<T extends Node>(node: T, emitFlags: EmitFlags) {
78    const emitNode = getOrCreateEmitNode(node);
79    emitNode.flags = emitNode.flags | emitFlags;
80    return node;
81}
82
83/**
84 * Gets a custom text range to use when emitting source maps.
85 */
86export function getSourceMapRange(node: Node): SourceMapRange {
87    return node.emitNode?.sourceMapRange ?? node;
88}
89
90/**
91 * Sets a custom text range to use when emitting source maps.
92 */
93export function setSourceMapRange<T extends Node>(node: T, range: SourceMapRange | undefined) {
94    getOrCreateEmitNode(node).sourceMapRange = range;
95    return node;
96}
97
98/**
99 * Gets the TextRange to use for source maps for a token of a node.
100 */
101export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined {
102    return node.emitNode?.tokenSourceMapRanges?.[token];
103}
104
105/**
106 * Sets the TextRange to use for source maps for a token of a node.
107 */
108export function setTokenSourceMapRange<T extends Node>(node: T, token: SyntaxKind, range: SourceMapRange | undefined) {
109    const emitNode = getOrCreateEmitNode(node);
110    const tokenSourceMapRanges = emitNode.tokenSourceMapRanges ?? (emitNode.tokenSourceMapRanges = []);
111    tokenSourceMapRanges[token] = range;
112    return node;
113}
114
115/**
116 * Gets a custom text range to use when emitting comments.
117 *
118 * @internal
119 */
120export function getStartsOnNewLine(node: Node) {
121    return node.emitNode?.startsOnNewLine;
122}
123
124/**
125 * Sets a custom text range to use when emitting comments.
126 *
127 * @internal
128 */
129export function setStartsOnNewLine<T extends Node>(node: T, newLine: boolean) {
130    getOrCreateEmitNode(node).startsOnNewLine = newLine;
131    return node;
132}
133
134/**
135 * Gets a custom text range to use when emitting comments.
136 */
137export function getCommentRange(node: Node): TextRange {
138    return node.emitNode?.commentRange ?? node;
139}
140
141/**
142 * Sets a custom text range to use when emitting comments.
143 */
144export function setCommentRange<T extends Node>(node: T, range: TextRange) {
145    getOrCreateEmitNode(node).commentRange = range;
146    return node;
147}
148
149export function getSyntheticLeadingComments(node: Node): SynthesizedComment[] | undefined {
150    return node.emitNode?.leadingComments;
151}
152
153export function setSyntheticLeadingComments<T extends Node>(node: T, comments: SynthesizedComment[] | undefined) {
154    getOrCreateEmitNode(node).leadingComments = comments;
155    return node;
156}
157
158export function addSyntheticLeadingComment<T extends Node>(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) {
159    return setSyntheticLeadingComments(node, append<SynthesizedComment>(getSyntheticLeadingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text }));
160}
161
162export function getSyntheticTrailingComments(node: Node): SynthesizedComment[] | undefined {
163    return node.emitNode?.trailingComments;
164}
165
166export function setSyntheticTrailingComments<T extends Node>(node: T, comments: SynthesizedComment[] | undefined) {
167    getOrCreateEmitNode(node).trailingComments = comments;
168    return node;
169}
170
171export function addSyntheticTrailingComment<T extends Node>(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean) {
172    return setSyntheticTrailingComments(node, append<SynthesizedComment>(getSyntheticTrailingComments(node), { kind, pos: -1, end: -1, hasTrailingNewLine, text }));
173}
174
175export function moveSyntheticComments<T extends Node>(node: T, original: Node): T {
176    setSyntheticLeadingComments(node, getSyntheticLeadingComments(original));
177    setSyntheticTrailingComments(node, getSyntheticTrailingComments(original));
178    const emit = getOrCreateEmitNode(original);
179    emit.leadingComments = undefined;
180    emit.trailingComments = undefined;
181    return node;
182}
183
184/**
185 * Gets the constant value to emit for an expression representing an enum.
186 */
187export function getConstantValue(node: AccessExpression): string | number | undefined {
188    return node.emitNode?.constantValue;
189}
190
191/**
192 * Sets the constant value to emit for an expression.
193 */
194export function setConstantValue(node: AccessExpression, value: string | number): AccessExpression {
195    const emitNode = getOrCreateEmitNode(node);
196    emitNode.constantValue = value;
197    return node;
198}
199
200/**
201 * Adds an EmitHelper to a node.
202 */
203export function addEmitHelper<T extends Node>(node: T, helper: EmitHelper): T {
204    const emitNode = getOrCreateEmitNode(node);
205    emitNode.helpers = append(emitNode.helpers, helper);
206    return node;
207}
208
209/**
210 * Add EmitHelpers to a node.
211 */
212export function addEmitHelpers<T extends Node>(node: T, helpers: EmitHelper[] | undefined): T {
213    if (some(helpers)) {
214        const emitNode = getOrCreateEmitNode(node);
215        for (const helper of helpers) {
216            emitNode.helpers = appendIfUnique(emitNode.helpers, helper);
217        }
218    }
219    return node;
220}
221
222/**
223 * Removes an EmitHelper from a node.
224 */
225export function removeEmitHelper(node: Node, helper: EmitHelper): boolean {
226    const helpers = node.emitNode?.helpers;
227    if (helpers) {
228        return orderedRemoveItem(helpers, helper);
229    }
230    return false;
231}
232
233/**
234 * Gets the EmitHelpers of a node.
235 */
236export function getEmitHelpers(node: Node): EmitHelper[] | undefined {
237    return node.emitNode?.helpers;
238}
239
240/**
241 * Moves matching emit helpers from a source node to a target node.
242 */
243export function moveEmitHelpers(source: Node, target: Node, predicate: (helper: EmitHelper) => boolean) {
244    const sourceEmitNode = source.emitNode;
245    const sourceEmitHelpers = sourceEmitNode && sourceEmitNode.helpers;
246    if (!some(sourceEmitHelpers)) return;
247
248    const targetEmitNode = getOrCreateEmitNode(target);
249    let helpersRemoved = 0;
250    for (let i = 0; i < sourceEmitHelpers.length; i++) {
251        const helper = sourceEmitHelpers[i];
252        if (predicate(helper)) {
253            helpersRemoved++;
254            targetEmitNode.helpers = appendIfUnique(targetEmitNode.helpers, helper);
255        }
256        else if (helpersRemoved > 0) {
257            sourceEmitHelpers[i - helpersRemoved] = helper;
258        }
259    }
260
261    if (helpersRemoved > 0) {
262        sourceEmitHelpers.length -= helpersRemoved;
263    }
264}
265
266/**
267 * Gets the SnippetElement of a node.
268 *
269 * @internal
270 */
271export function getSnippetElement(node: Node): SnippetElement | undefined {
272    return node.emitNode?.snippetElement;
273}
274
275/**
276 * Sets the SnippetElement of a node.
277 *
278 * @internal
279 */
280export function setSnippetElement<T extends Node>(node: T, snippet: SnippetElement): T {
281    const emitNode = getOrCreateEmitNode(node);
282    emitNode.snippetElement = snippet;
283    return node;
284}
285
286/** @internal */
287export function ignoreSourceNewlines<T extends Node>(node: T): T {
288    getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines;
289    return node;
290}
291
292/** @internal */
293export function setTypeNode<T extends Node>(node: T, type: TypeNode): T {
294    const emitNode = getOrCreateEmitNode(node);
295    emitNode.typeNode = type;
296    return node;
297}
298
299/** @internal */
300export function getTypeNode<T extends Node>(node: T): TypeNode | undefined {
301    return node.emitNode?.typeNode;
302}
303