• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts {
3    export function getOriginalNodeId(node: Node) {
4        node = getOriginalNode(node);
5        return node ? getNodeId(node) : 0;
6    }
7
8    export interface ExternalModuleInfo {
9        externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]; // imports of other external modules
10        externalHelpersImportDeclaration: ImportDeclaration | undefined; // import of external helpers
11        exportSpecifiers: ESMap<string, ExportSpecifier[]>; // file-local export specifiers by name (no reexports)
12        exportedBindings: Identifier[][]; // exported names of local declarations
13        exportedNames: Identifier[] | undefined; // all exported names in the module, both local and reexported
14        exportEquals: ExportAssignment | undefined; // an export= declaration if one was present
15        hasExportStarsToExportValues: boolean; // whether this module contains export*
16    }
17
18    function containsDefaultReference(node: NamedImportBindings | undefined) {
19        if (!node) return false;
20        if (!isNamedImports(node)) return false;
21        return some(node.elements, isNamedDefaultReference);
22    }
23
24    function isNamedDefaultReference(e: ImportSpecifier): boolean {
25        return e.propertyName !== undefined && e.propertyName.escapedText === InternalSymbolName.Default;
26    }
27
28    export function chainBundle(context: CoreTransformationContext, transformSourceFile: (x: SourceFile) => SourceFile): (x: SourceFile | Bundle) => SourceFile | Bundle {
29        return transformSourceFileOrBundle;
30
31        function transformSourceFileOrBundle(node: SourceFile | Bundle) {
32            return node.kind === SyntaxKind.SourceFile ? transformSourceFile(node) : transformBundle(node);
33        }
34
35        function transformBundle(node: Bundle) {
36            return context.factory.createBundle(map(node.sourceFiles, transformSourceFile), node.prepends);
37        }
38    }
39
40    export function getExportNeedsImportStarHelper(node: ExportDeclaration): boolean {
41        return !!getNamespaceDeclarationNode(node);
42    }
43
44    export function getImportNeedsImportStarHelper(node: ImportDeclaration): boolean {
45        if (!!getNamespaceDeclarationNode(node)) {
46            return true;
47        }
48        const bindings = node.importClause && node.importClause.namedBindings;
49        if (!bindings) {
50            return false;
51        }
52        if (!isNamedImports(bindings)) return false;
53        let defaultRefCount = 0;
54        for (const binding of bindings.elements) {
55            if (isNamedDefaultReference(binding)) {
56                defaultRefCount++;
57            }
58        }
59        // Import star is required if there's default named refs mixed with non-default refs, or if theres non-default refs and it has a default import
60        return (defaultRefCount > 0 && defaultRefCount !== bindings.elements.length) || (!!(bindings.elements.length - defaultRefCount) && isDefaultImport(node));
61    }
62
63    export function getImportNeedsImportDefaultHelper(node: ImportDeclaration): boolean {
64        // Import default is needed if there's a default import or a default ref and no other refs (meaning an import star helper wasn't requested)
65        return !getImportNeedsImportStarHelper(node) && (isDefaultImport(node) || (!!node.importClause && isNamedImports(node.importClause.namedBindings!) && containsDefaultReference(node.importClause.namedBindings))); // TODO: GH#18217
66    }
67
68    export function collectExternalModuleInfo(context: TransformationContext, sourceFile: SourceFile, resolver: EmitResolver, compilerOptions: CompilerOptions): ExternalModuleInfo {
69        const externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[] = [];
70        const exportSpecifiers = createMultiMap<ExportSpecifier>();
71        const exportedBindings: Identifier[][] = [];
72        const uniqueExports = new Map<string, boolean>();
73        let exportedNames: Identifier[] | undefined;
74        let hasExportDefault = false;
75        let exportEquals: ExportAssignment | undefined;
76        let hasExportStarsToExportValues = false;
77        let hasImportStar = false;
78        let hasImportDefault = false;
79
80        for (const node of sourceFile.statements) {
81            switch (node.kind) {
82                case SyntaxKind.ImportDeclaration:
83                    // import "mod"
84                    // import x from "mod"
85                    // import * as x from "mod"
86                    // import { x, y } from "mod"
87                    externalImports.push(<ImportDeclaration>node);
88                    if (!hasImportStar && getImportNeedsImportStarHelper(<ImportDeclaration>node)) {
89                        hasImportStar = true;
90                    }
91                    if (!hasImportDefault && getImportNeedsImportDefaultHelper(<ImportDeclaration>node)) {
92                        hasImportDefault = true;
93                    }
94                    break;
95
96                case SyntaxKind.ImportEqualsDeclaration:
97                    if ((<ImportEqualsDeclaration>node).moduleReference.kind === SyntaxKind.ExternalModuleReference) {
98                        // import x = require("mod")
99                        externalImports.push(<ImportEqualsDeclaration>node);
100                    }
101
102                    break;
103
104                case SyntaxKind.ExportDeclaration:
105                    if ((<ExportDeclaration>node).moduleSpecifier) {
106                        if (!(<ExportDeclaration>node).exportClause) {
107                            // export * from "mod"
108                            externalImports.push(<ExportDeclaration>node);
109                            hasExportStarsToExportValues = true;
110                        }
111                        else {
112                            // export * as ns from "mod"
113                            // export { x, y } from "mod"
114                            externalImports.push(<ExportDeclaration>node);
115                            if (isNamedExports((node as ExportDeclaration).exportClause!)) {
116                                addExportedNamesForExportDeclaration(node as ExportDeclaration);
117                            }
118                            else {
119                                const name = ((node as ExportDeclaration).exportClause as NamespaceExport).name;
120                                if (!uniqueExports.get(idText(name))) {
121                                    multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name);
122                                    uniqueExports.set(idText(name), true);
123                                    exportedNames = append(exportedNames, name);
124                                }
125                                // we use the same helpers for `export * as ns` as we do for `import * as ns`
126                                hasImportStar = true;
127                            }
128                        }
129                    }
130                    else {
131                        // export { x, y }
132                        addExportedNamesForExportDeclaration(node as ExportDeclaration);
133                    }
134                    break;
135
136                case SyntaxKind.ExportAssignment:
137                    if ((<ExportAssignment>node).isExportEquals && !exportEquals) {
138                        // export = x
139                        exportEquals = <ExportAssignment>node;
140                    }
141                    break;
142
143                case SyntaxKind.VariableStatement:
144                    if (hasSyntacticModifier(node, ModifierFlags.Export)) {
145                        for (const decl of (<VariableStatement>node).declarationList.declarations) {
146                            exportedNames = collectExportedVariableInfo(decl, uniqueExports, exportedNames);
147                        }
148                    }
149                    break;
150
151                case SyntaxKind.FunctionDeclaration:
152                    if (hasSyntacticModifier(node, ModifierFlags.Export)) {
153                        if (hasSyntacticModifier(node, ModifierFlags.Default)) {
154                            // export default function() { }
155                            if (!hasExportDefault) {
156                                multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), context.factory.getDeclarationName(<FunctionDeclaration>node));
157                                hasExportDefault = true;
158                            }
159                        }
160                        else {
161                            // export function x() { }
162                            const name = (<FunctionDeclaration>node).name!;
163                            if (!uniqueExports.get(idText(name))) {
164                                multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name);
165                                uniqueExports.set(idText(name), true);
166                                exportedNames = append(exportedNames, name);
167                            }
168                        }
169                    }
170                    break;
171
172                case SyntaxKind.ClassDeclaration:
173                    if (hasSyntacticModifier(node, ModifierFlags.Export)) {
174                        if (hasSyntacticModifier(node, ModifierFlags.Default)) {
175                            // export default class { }
176                            if (!hasExportDefault) {
177                                multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), context.factory.getDeclarationName(<ClassDeclaration>node));
178                                hasExportDefault = true;
179                            }
180                        }
181                        else {
182                            // export class x { }
183                            const name = (<ClassDeclaration>node).name;
184                            if (name && !uniqueExports.get(idText(name))) {
185                                multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(node), name);
186                                uniqueExports.set(idText(name), true);
187                                exportedNames = append(exportedNames, name);
188                            }
189                        }
190                    }
191                    break;
192            }
193        }
194
195        const externalHelpersImportDeclaration = createExternalHelpersImportDeclarationIfNeeded(context.factory, context.getEmitHelperFactory(), sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar, hasImportDefault);
196        if (externalHelpersImportDeclaration) {
197            externalImports.unshift(externalHelpersImportDeclaration);
198        }
199
200        return { externalImports, exportSpecifiers, exportEquals, hasExportStarsToExportValues, exportedBindings, exportedNames, externalHelpersImportDeclaration };
201
202        function addExportedNamesForExportDeclaration(node: ExportDeclaration) {
203            for (const specifier of cast(node.exportClause, isNamedExports).elements) {
204                if (!uniqueExports.get(idText(specifier.name))) {
205                    const name = specifier.propertyName || specifier.name;
206                    if (!node.moduleSpecifier) {
207                        exportSpecifiers.add(idText(name), specifier);
208                    }
209
210                    const decl = resolver.getReferencedImportDeclaration(name)
211                        || resolver.getReferencedValueDeclaration(name);
212
213                    if (decl) {
214                        multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(decl), specifier.name);
215                    }
216
217                    uniqueExports.set(idText(specifier.name), true);
218                    exportedNames = append(exportedNames, specifier.name);
219                }
220            }
221        }
222    }
223
224    function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: ESMap<string, boolean>, exportedNames: Identifier[] | undefined) {
225        if (isBindingPattern(decl.name)) {
226            for (const element of decl.name.elements) {
227                if (!isOmittedExpression(element)) {
228                    exportedNames = collectExportedVariableInfo(element, uniqueExports, exportedNames);
229                }
230            }
231        }
232        else if (!isGeneratedIdentifier(decl.name)) {
233            const text = idText(decl.name);
234            if (!uniqueExports.get(text)) {
235                uniqueExports.set(text, true);
236                exportedNames = append(exportedNames, decl.name);
237            }
238        }
239        return exportedNames;
240    }
241
242    /** Use a sparse array as a multi-map. */
243    function multiMapSparseArrayAdd<V>(map: V[][], key: number, value: V): V[] {
244        let values = map[key];
245        if (values) {
246            values.push(value);
247        }
248        else {
249            map[key] = values = [value];
250        }
251        return values;
252    }
253
254    /**
255     * Used in the module transformer to check if an expression is reasonably without sideeffect,
256     *  and thus better to copy into multiple places rather than to cache in a temporary variable
257     *  - this is mostly subjective beyond the requirement that the expression not be sideeffecting
258     */
259    export function isSimpleCopiableExpression(expression: Expression) {
260        return isStringLiteralLike(expression) ||
261            expression.kind === SyntaxKind.NumericLiteral ||
262            isKeyword(expression.kind) ||
263            isIdentifier(expression);
264    }
265
266    /**
267     * A simple inlinable expression is an expression which can be copied into multiple locations
268     * without risk of repeating any sideeffects and whose value could not possibly change between
269     * any such locations
270     */
271    export function isSimpleInlineableExpression(expression: Expression) {
272        return !isIdentifier(expression) && isSimpleCopiableExpression(expression) ||
273            isWellKnownSymbolSyntactically(expression);
274    }
275
276    export function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator {
277        return kind >= SyntaxKind.FirstCompoundAssignment
278            && kind <= SyntaxKind.LastCompoundAssignment;
279    }
280
281    export function getNonAssignmentOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): LogicalOperatorOrHigher | SyntaxKind.QuestionQuestionToken {
282        switch (kind) {
283            case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken;
284            case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken;
285            case SyntaxKind.AsteriskEqualsToken: return SyntaxKind.AsteriskToken;
286            case SyntaxKind.AsteriskAsteriskEqualsToken: return SyntaxKind.AsteriskAsteriskToken;
287            case SyntaxKind.SlashEqualsToken: return SyntaxKind.SlashToken;
288            case SyntaxKind.PercentEqualsToken: return SyntaxKind.PercentToken;
289            case SyntaxKind.LessThanLessThanEqualsToken: return SyntaxKind.LessThanLessThanToken;
290            case SyntaxKind.GreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanToken;
291            case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
292            case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken;
293            case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken;
294            case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken;
295            case SyntaxKind.BarBarEqualsToken: return SyntaxKind.BarBarToken;
296            case SyntaxKind.AmpersandAmpersandEqualsToken: return SyntaxKind.AmpersandAmpersandToken;
297            case SyntaxKind.QuestionQuestionEqualsToken: return SyntaxKind.QuestionQuestionToken;
298
299        }
300    }
301
302    /**
303     * Adds super call and preceding prologue directives into the list of statements.
304     *
305     * @param ctor The constructor node.
306     * @param result The list of statements.
307     * @param visitor The visitor to apply to each node added to the result array.
308     * @returns index of the statement that follows super call
309     */
310    export function addPrologueDirectivesAndInitialSuperCall(factory: NodeFactory, ctor: ConstructorDeclaration, result: Statement[], visitor: Visitor): number {
311        if (ctor.body) {
312            const statements = ctor.body.statements;
313            // add prologue directives to the list (if any)
314            const index = factory.copyPrologue(statements, result, /*ensureUseStrict*/ false, visitor);
315            if (index === statements.length) {
316                // list contains nothing but prologue directives (or empty) - exit
317                return index;
318            }
319
320            const superIndex = findIndex(statements, s => isExpressionStatement(s) && isSuperCall(s.expression), index);
321            if (superIndex > -1) {
322                for (let i = index; i <= superIndex; i++) {
323                    result.push(visitNode(statements[i], visitor, isStatement));
324                }
325                return superIndex + 1;
326            }
327
328            return index;
329        }
330
331        return 0;
332    }
333
334    /**
335     * Gets all the static or all the instance property declarations of a class
336     *
337     * @param node The class node.
338     * @param isStatic A value indicating whether to get properties from the static or instance side of the class.
339     */
340    export function getProperties(node: ClassExpression | ClassDeclaration, requireInitializer: true, isStatic: boolean): readonly InitializedPropertyDeclaration[];
341    export function getProperties(node: ClassExpression | ClassDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[];
342    export function getProperties(node: ClassExpression | ClassDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[] {
343        return filter(node.members, m => isInitializedOrStaticProperty(m, requireInitializer, isStatic)) as PropertyDeclaration[];
344    }
345
346    /**
347     * Is a class element either a static or an instance property declaration with an initializer?
348     *
349     * @param member The class element node.
350     * @param isStatic A value indicating whether the member should be a static or instance member.
351     */
352    function isInitializedOrStaticProperty(member: ClassElement, requireInitializer: boolean, isStatic: boolean) {
353        return isPropertyDeclaration(member)
354            && (!!member.initializer || !requireInitializer)
355            && hasStaticModifier(member) === isStatic;
356    }
357
358    /**
359     * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer.
360     *
361     * @param member The class element node.
362     * @param isStatic A value indicating whether the member should be a static or instance member.
363     */
364    export function isInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } {
365        return member.kind === SyntaxKind.PropertyDeclaration
366            && (<PropertyDeclaration>member).initializer !== undefined;
367    }
368}
369