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