• 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(node as ImportDeclaration);
88                    if (!hasImportStar && getImportNeedsImportStarHelper(node as ImportDeclaration)) {
89                        hasImportStar = true;
90                    }
91                    if (!hasImportDefault && getImportNeedsImportDefaultHelper(node as ImportDeclaration)) {
92                        hasImportDefault = true;
93                    }
94                    break;
95
96                case SyntaxKind.ImportEqualsDeclaration:
97                    if ((node as ImportEqualsDeclaration).moduleReference.kind === SyntaxKind.ExternalModuleReference) {
98                        // import x = require("mod")
99                        externalImports.push(node as ImportEqualsDeclaration);
100                    }
101
102                    break;
103
104                case SyntaxKind.ExportDeclaration:
105                    if ((node as ExportDeclaration).moduleSpecifier) {
106                        if (!(node as ExportDeclaration).exportClause) {
107                            // export * from "mod"
108                            externalImports.push(node as ExportDeclaration);
109                            hasExportStarsToExportValues = true;
110                        }
111                        else {
112                            // export * as ns from "mod"
113                            // export { x, y } from "mod"
114                            externalImports.push(node as ExportDeclaration);
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 ((node as ExportAssignment).isExportEquals && !exportEquals) {
138                        // export = x
139                        exportEquals = node as ExportAssignment;
140                    }
141                    break;
142
143                case SyntaxKind.VariableStatement:
144                    if (hasSyntacticModifier(node, ModifierFlags.Export)) {
145                        for (const decl of (node as VariableStatement).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(node as FunctionDeclaration));
157                                hasExportDefault = true;
158                            }
159                        }
160                        else {
161                            // export function x() { }
162                            const name = (node as FunctionDeclaration).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(node as ClassDeclaration));
178                                hasExportDefault = true;
179                            }
180                        }
181                        else {
182                            // export class x { }
183                            const name = (node as ClassDeclaration).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    }
274
275    export function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator {
276        return kind >= SyntaxKind.FirstCompoundAssignment
277            && kind <= SyntaxKind.LastCompoundAssignment;
278    }
279
280    export function getNonAssignmentOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): LogicalOperatorOrHigher | SyntaxKind.QuestionQuestionToken {
281        switch (kind) {
282            case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken;
283            case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken;
284            case SyntaxKind.AsteriskEqualsToken: return SyntaxKind.AsteriskToken;
285            case SyntaxKind.AsteriskAsteriskEqualsToken: return SyntaxKind.AsteriskAsteriskToken;
286            case SyntaxKind.SlashEqualsToken: return SyntaxKind.SlashToken;
287            case SyntaxKind.PercentEqualsToken: return SyntaxKind.PercentToken;
288            case SyntaxKind.LessThanLessThanEqualsToken: return SyntaxKind.LessThanLessThanToken;
289            case SyntaxKind.GreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanToken;
290            case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
291            case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken;
292            case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken;
293            case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken;
294            case SyntaxKind.BarBarEqualsToken: return SyntaxKind.BarBarToken;
295            case SyntaxKind.AmpersandAmpersandEqualsToken: return SyntaxKind.AmpersandAmpersandToken;
296            case SyntaxKind.QuestionQuestionEqualsToken: return SyntaxKind.QuestionQuestionToken;
297
298        }
299    }
300
301    /**
302     * @returns Contained super() call from descending into the statement ignoring parentheses, if that call exists.
303     */
304    export function getSuperCallFromStatement(statement: Statement) {
305        if (!isExpressionStatement(statement)) {
306            return undefined;
307        }
308
309        const expression = skipParentheses(statement.expression);
310        return isSuperCall(expression)
311            ? expression
312            : undefined;
313    }
314
315    /**
316     * @returns The index (after prologue statements) of a super call, or -1 if not found.
317     */
318    export function findSuperStatementIndex(statements: NodeArray<Statement>, indexAfterLastPrologueStatement: number) {
319        for (let i = indexAfterLastPrologueStatement; i < statements.length; i += 1) {
320            const statement = statements[i];
321
322            if (getSuperCallFromStatement(statement)) {
323                return i;
324            }
325        }
326
327        return -1;
328    }
329
330    /**
331     * Gets all the static or all the instance property declarations of a class
332     *
333     * @param node The class node.
334     * @param isStatic A value indicating whether to get properties from the static or instance side of the class.
335     */
336    export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: true, isStatic: boolean): readonly InitializedPropertyDeclaration[];
337    export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[];
338    export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[] {
339        return filter(node.members, m => isInitializedOrStaticProperty(m, requireInitializer, isStatic)) as PropertyDeclaration[];
340    }
341
342    function isStaticPropertyDeclarationOrClassStaticBlockDeclaration(element: ClassElement): element is PropertyDeclaration | ClassStaticBlockDeclaration {
343        return isStaticPropertyDeclaration(element) || isClassStaticBlockDeclaration(element);
344    }
345
346    export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[];
347    export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[];
348    export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[] {
349        return filter(node.members, isStaticPropertyDeclarationOrClassStaticBlockDeclaration);
350    }
351
352    /**
353     * Is a class element either a static or an instance property declaration with an initializer?
354     *
355     * @param member The class element node.
356     * @param isStatic A value indicating whether the member should be a static or instance member.
357     */
358    function isInitializedOrStaticProperty(member: ClassElement, requireInitializer: boolean, isStatic: boolean) {
359        return isPropertyDeclaration(member)
360            && (!!member.initializer || !requireInitializer)
361            && hasStaticModifier(member) === isStatic;
362    }
363
364    function isStaticPropertyDeclaration(member: ClassElement) {
365        return isPropertyDeclaration(member) && hasStaticModifier(member);
366    }
367
368    /**
369     * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer.
370     *
371     * @param member The class element node.
372     * @param isStatic A value indicating whether the member should be a static or instance member.
373     */
374    export function isInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } {
375        return member.kind === SyntaxKind.PropertyDeclaration
376            && (member as PropertyDeclaration).initializer !== undefined;
377    }
378
379    /**
380     * Gets a value indicating whether a class element is a private instance method or accessor.
381     *
382     * @param member The class element node.
383     */
384    export function isNonStaticMethodOrAccessorWithPrivateName(member: ClassElement): member is PrivateIdentifierMethodDeclaration | PrivateIdentifierAccessorDeclaration | PrivateIdentifierAutoAccessorPropertyDeclaration {
385        return !isStatic(member) && (isMethodOrAccessor(member) || isAutoAccessorPropertyDeclaration(member)) && isPrivateIdentifier(member.name);
386    }
387
388    /**
389     * Gets an array of arrays of decorators for the parameters of a function-like node.
390     * The offset into the result array should correspond to the offset of the parameter.
391     *
392     * @param node The function-like node.
393     */
394    function getDecoratorsOfParameters(node: FunctionLikeDeclaration | undefined) {
395        let decorators: (readonly Decorator[] | undefined)[] | undefined;
396        if (node) {
397            const parameters = node.parameters;
398            const firstParameterIsThis = parameters.length > 0 && parameterIsThisKeyword(parameters[0]);
399            const firstParameterOffset = firstParameterIsThis ? 1 : 0;
400            const numParameters = firstParameterIsThis ? parameters.length - 1 : parameters.length;
401            for (let i = 0; i < numParameters; i++) {
402                const parameter = parameters[i + firstParameterOffset];
403                if (decorators || hasDecorators(parameter)) {
404                    if (!decorators) {
405                        decorators = new Array(numParameters);
406                    }
407
408                    decorators[i] = getDecorators(parameter);
409                }
410            }
411        }
412
413        return decorators;
414    }
415
416    /**
417     * Gets an AllDecorators object containing the decorators for the class and the decorators for the
418     * parameters of the constructor of the class.
419     *
420     * @param node The class node.
421     */
422    export function getAllDecoratorsOfClass(node: ClassLikeDeclaration): AllDecorators | undefined {
423        const decorators = getDecorators(node);
424        const parameters = getDecoratorsOfParameters(getFirstConstructorWithBody(node));
425        if (!some(decorators) && !some(parameters)) {
426            return undefined;
427        }
428
429        return {
430            decorators,
431            parameters
432        };
433    }
434
435    /**
436     * Gets an AllDecorators object containing the decorators for the member and its parameters.
437     *
438     * @param parent The class node that contains the member.
439     * @param member The class member.
440     */
441    export function getAllDecoratorsOfClassElement(member: ClassElement, parent: ClassLikeDeclaration): AllDecorators | undefined {
442        switch (member.kind) {
443            case SyntaxKind.GetAccessor:
444            case SyntaxKind.SetAccessor:
445                return getAllDecoratorsOfAccessors(member as AccessorDeclaration, parent);
446
447            case SyntaxKind.MethodDeclaration:
448                return getAllDecoratorsOfMethod(member as MethodDeclaration);
449
450            case SyntaxKind.PropertyDeclaration:
451                return getAllDecoratorsOfProperty(member as PropertyDeclaration);
452
453            default:
454                return undefined;
455        }
456    }
457
458    /**
459     * Gets an AllDecorators object containing the decorators for the accessor and its parameters.
460     *
461     * @param parent The class node that contains the accessor.
462     * @param accessor The class accessor member.
463     */
464    function getAllDecoratorsOfAccessors(accessor: AccessorDeclaration, parent: ClassExpression | ClassDeclaration | StructDeclaration): AllDecorators | undefined {
465        if (!accessor.body) {
466            return undefined;
467        }
468
469        const { firstAccessor, secondAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(parent.members, accessor);
470        const firstAccessorWithDecorators =
471            hasDecorators(firstAccessor) ? firstAccessor :
472            secondAccessor && hasDecorators(secondAccessor) ? secondAccessor :
473            undefined;
474
475        if (!firstAccessorWithDecorators || accessor !== firstAccessorWithDecorators) {
476            return undefined;
477        }
478
479        const decorators = getDecorators(firstAccessorWithDecorators);
480        const parameters = getDecoratorsOfParameters(setAccessor);
481        if (!some(decorators) && !some(parameters)) {
482            return undefined;
483        }
484
485        return {
486            decorators,
487            parameters,
488            getDecorators: getAccessor && getDecorators(getAccessor),
489            setDecorators: setAccessor && getDecorators(setAccessor)
490        };
491    }
492
493    /**
494     * Gets an AllDecorators object containing the decorators for the method and its parameters.
495     *
496     * @param method The class method member.
497     */
498    function getAllDecoratorsOfMethod(method: MethodDeclaration): AllDecorators | undefined {
499        if (!method.body) {
500            return undefined;
501        }
502
503        const decorators = getDecorators(method);
504        const parameters = getDecoratorsOfParameters(method);
505        if (!some(decorators) && !some(parameters)) {
506            return undefined;
507        }
508
509        return { decorators, parameters };
510    }
511
512    /**
513     * Gets an AllDecorators object containing the decorators for the property.
514     *
515     * @param property The class property member.
516     */
517    function getAllDecoratorsOfProperty(property: PropertyDeclaration): AllDecorators | undefined {
518        const decorators = getDecorators(property);
519        if (!some(decorators)) {
520            return undefined;
521
522        }
523
524        return { decorators };
525    }
526
527}
528