• 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 (resolver.isReferredToAnnotation(specifier) === true) {
205                    continue;
206                }
207                if (!uniqueExports.get(idText(specifier.name))) {
208                    const name = specifier.propertyName || specifier.name;
209                    if (!node.moduleSpecifier) {
210                        exportSpecifiers.add(idText(name), specifier);
211                    }
212
213                    const decl = resolver.getReferencedImportDeclaration(name)
214                        || resolver.getReferencedValueDeclaration(name);
215
216                    if (decl) {
217                        multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(decl), specifier.name);
218                    }
219
220                    uniqueExports.set(idText(specifier.name), true);
221                    exportedNames = append(exportedNames, specifier.name);
222                }
223            }
224        }
225    }
226
227    function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: ESMap<string, boolean>, exportedNames: Identifier[] | undefined) {
228        if (isBindingPattern(decl.name)) {
229            for (const element of decl.name.elements) {
230                if (!isOmittedExpression(element)) {
231                    exportedNames = collectExportedVariableInfo(element, uniqueExports, exportedNames);
232                }
233            }
234        }
235        else if (!isGeneratedIdentifier(decl.name)) {
236            const text = idText(decl.name);
237            if (!uniqueExports.get(text)) {
238                uniqueExports.set(text, true);
239                exportedNames = append(exportedNames, decl.name);
240            }
241        }
242        return exportedNames;
243    }
244
245    /** Use a sparse array as a multi-map. */
246    function multiMapSparseArrayAdd<V>(map: V[][], key: number, value: V): V[] {
247        let values = map[key];
248        if (values) {
249            values.push(value);
250        }
251        else {
252            map[key] = values = [value];
253        }
254        return values;
255    }
256
257    /**
258     * Used in the module transformer to check if an expression is reasonably without sideeffect,
259     *  and thus better to copy into multiple places rather than to cache in a temporary variable
260     *  - this is mostly subjective beyond the requirement that the expression not be sideeffecting
261     */
262    export function isSimpleCopiableExpression(expression: Expression) {
263        return isStringLiteralLike(expression) ||
264            expression.kind === SyntaxKind.NumericLiteral ||
265            isKeyword(expression.kind) ||
266            isIdentifier(expression);
267    }
268
269    /**
270     * A simple inlinable expression is an expression which can be copied into multiple locations
271     * without risk of repeating any sideeffects and whose value could not possibly change between
272     * any such locations
273     */
274    export function isSimpleInlineableExpression(expression: Expression) {
275        return !isIdentifier(expression) && isSimpleCopiableExpression(expression);
276    }
277
278    export function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator {
279        return kind >= SyntaxKind.FirstCompoundAssignment
280            && kind <= SyntaxKind.LastCompoundAssignment;
281    }
282
283    export function getNonAssignmentOperatorForCompoundAssignment(kind: CompoundAssignmentOperator): LogicalOperatorOrHigher | SyntaxKind.QuestionQuestionToken {
284        switch (kind) {
285            case SyntaxKind.PlusEqualsToken: return SyntaxKind.PlusToken;
286            case SyntaxKind.MinusEqualsToken: return SyntaxKind.MinusToken;
287            case SyntaxKind.AsteriskEqualsToken: return SyntaxKind.AsteriskToken;
288            case SyntaxKind.AsteriskAsteriskEqualsToken: return SyntaxKind.AsteriskAsteriskToken;
289            case SyntaxKind.SlashEqualsToken: return SyntaxKind.SlashToken;
290            case SyntaxKind.PercentEqualsToken: return SyntaxKind.PercentToken;
291            case SyntaxKind.LessThanLessThanEqualsToken: return SyntaxKind.LessThanLessThanToken;
292            case SyntaxKind.GreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanToken;
293            case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
294            case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandToken;
295            case SyntaxKind.BarEqualsToken: return SyntaxKind.BarToken;
296            case SyntaxKind.CaretEqualsToken: return SyntaxKind.CaretToken;
297            case SyntaxKind.BarBarEqualsToken: return SyntaxKind.BarBarToken;
298            case SyntaxKind.AmpersandAmpersandEqualsToken: return SyntaxKind.AmpersandAmpersandToken;
299            case SyntaxKind.QuestionQuestionEqualsToken: return SyntaxKind.QuestionQuestionToken;
300
301        }
302    }
303
304    /**
305     * @returns Contained super() call from descending into the statement ignoring parentheses, if that call exists.
306     */
307    export function getSuperCallFromStatement(statement: Statement) {
308        if (!isExpressionStatement(statement)) {
309            return undefined;
310        }
311
312        const expression = skipParentheses(statement.expression);
313        return isSuperCall(expression)
314            ? expression
315            : undefined;
316    }
317
318    /**
319     * @returns The index (after prologue statements) of a super call, or -1 if not found.
320     */
321    export function findSuperStatementIndex(statements: NodeArray<Statement>, indexAfterLastPrologueStatement: number) {
322        for (let i = indexAfterLastPrologueStatement; i < statements.length; i += 1) {
323            const statement = statements[i];
324
325            if (getSuperCallFromStatement(statement)) {
326                return i;
327            }
328        }
329
330        return -1;
331    }
332
333    /**
334     * Gets all the static or all the instance property declarations of a class
335     *
336     * @param node The class node.
337     * @param isStatic A value indicating whether to get properties from the static or instance side of the class.
338     */
339    export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: true, isStatic: boolean): readonly InitializedPropertyDeclaration[];
340    export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[];
341    export function getProperties(node: ClassExpression | ClassDeclaration | StructDeclaration, requireInitializer: boolean, isStatic: boolean): readonly PropertyDeclaration[] {
342        return filter(node.members, m => isInitializedOrStaticProperty(m, requireInitializer, isStatic)) as PropertyDeclaration[];
343    }
344
345    function isStaticPropertyDeclarationOrClassStaticBlockDeclaration(element: ClassElement): element is PropertyDeclaration | ClassStaticBlockDeclaration {
346        return isStaticPropertyDeclaration(element) || isClassStaticBlockDeclaration(element);
347    }
348
349    export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[];
350    export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[];
351    export function getStaticPropertiesAndClassStaticBlock(node: ClassExpression | ClassDeclaration): readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[] {
352        return filter(node.members, isStaticPropertyDeclarationOrClassStaticBlockDeclaration);
353    }
354
355    /**
356     * Is a class element either a static or an instance property declaration with an initializer?
357     *
358     * @param member The class element node.
359     * @param isStatic A value indicating whether the member should be a static or instance member.
360     */
361    function isInitializedOrStaticProperty(member: ClassElement, requireInitializer: boolean, isStatic: boolean) {
362        return isPropertyDeclaration(member)
363            && (!!member.initializer || !requireInitializer)
364            && hasStaticModifier(member) === isStatic;
365    }
366
367    function isStaticPropertyDeclaration(member: ClassElement) {
368        return isPropertyDeclaration(member) && hasStaticModifier(member);
369    }
370
371    /**
372     * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer.
373     *
374     * @param member The class element node.
375     * @param isStatic A value indicating whether the member should be a static or instance member.
376     */
377    export function isInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } {
378        return member.kind === SyntaxKind.PropertyDeclaration
379            && (member as PropertyDeclaration).initializer !== undefined;
380    }
381
382    /**
383     * Gets a value indicating whether a class element is a private instance method or accessor.
384     *
385     * @param member The class element node.
386     */
387    export function isNonStaticMethodOrAccessorWithPrivateName(member: ClassElement): member is PrivateIdentifierMethodDeclaration | PrivateIdentifierAccessorDeclaration | PrivateIdentifierAutoAccessorPropertyDeclaration {
388        return !isStatic(member) && (isMethodOrAccessor(member) || isAutoAccessorPropertyDeclaration(member)) && isPrivateIdentifier(member.name);
389    }
390
391    /**
392     * Gets an array of arrays of decorators for the parameters of a function-like node.
393     * The offset into the result array should correspond to the offset of the parameter.
394     *
395     * @param node The function-like node.
396     */
397    function getDecoratorsOfParameters(node: FunctionLikeDeclaration | undefined) {
398        let decorators: (readonly Decorator[] | undefined)[] | undefined;
399        if (node) {
400            const parameters = node.parameters;
401            const firstParameterIsThis = parameters.length > 0 && parameterIsThisKeyword(parameters[0]);
402            const firstParameterOffset = firstParameterIsThis ? 1 : 0;
403            const numParameters = firstParameterIsThis ? parameters.length - 1 : parameters.length;
404            for (let i = 0; i < numParameters; i++) {
405                const parameter = parameters[i + firstParameterOffset];
406                if (decorators || hasDecorators(parameter)) {
407                    if (!decorators) {
408                        decorators = new Array(numParameters);
409                    }
410
411                    decorators[i] = getDecorators(parameter);
412                }
413            }
414        }
415
416        return decorators;
417    }
418
419    /**
420     * Gets an AllDecorators object containing the decorators for the class and the decorators for the
421     * parameters of the constructor of the class.
422     *
423     * @param node The class node.
424     */
425    export function getAllDecoratorsOfClass(node: ClassLikeDeclaration): AllDecorators | undefined {
426        const decorators = getDecorators(node);
427        const parameters = getDecoratorsOfParameters(getFirstConstructorWithBody(node));
428        if (!some(decorators) && !some(parameters)) {
429            return undefined;
430        }
431
432        return {
433            decorators,
434            parameters
435        };
436    }
437
438    /**
439     * Gets an AllDecorators object containing the decorators for the member and its parameters.
440     *
441     * @param parent The class node that contains the member.
442     * @param member The class member.
443     */
444    export function getAllDecoratorsOfClassElement(member: ClassElement, parent: ClassLikeDeclaration): AllDecorators | undefined {
445        switch (member.kind) {
446            case SyntaxKind.GetAccessor:
447            case SyntaxKind.SetAccessor:
448                return getAllDecoratorsOfAccessors(member as AccessorDeclaration, parent);
449
450            case SyntaxKind.MethodDeclaration:
451                return getAllDecoratorsOfMethod(member as MethodDeclaration);
452
453            case SyntaxKind.PropertyDeclaration:
454                return getAllDecoratorsOfProperty(member as PropertyDeclaration);
455
456            default:
457                return undefined;
458        }
459    }
460
461    /**
462     * Gets an AllDecorators object containing the decorators for the accessor and its parameters.
463     *
464     * @param parent The class node that contains the accessor.
465     * @param accessor The class accessor member.
466     */
467    function getAllDecoratorsOfAccessors(accessor: AccessorDeclaration, parent: ClassExpression | ClassDeclaration | StructDeclaration): AllDecorators | undefined {
468        if (!accessor.body) {
469            return undefined;
470        }
471
472        const { firstAccessor, secondAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(parent.members, accessor);
473        const firstAccessorWithDecorators =
474            hasDecorators(firstAccessor) ? firstAccessor :
475            secondAccessor && hasDecorators(secondAccessor) ? secondAccessor :
476            undefined;
477
478        if (!firstAccessorWithDecorators || accessor !== firstAccessorWithDecorators) {
479            return undefined;
480        }
481
482        const decorators = getDecorators(firstAccessorWithDecorators);
483        const parameters = getDecoratorsOfParameters(setAccessor);
484        if (!some(decorators) && !some(parameters)) {
485            return undefined;
486        }
487
488        return {
489            decorators,
490            parameters,
491            getDecorators: getAccessor && getDecorators(getAccessor),
492            setDecorators: setAccessor && getDecorators(setAccessor)
493        };
494    }
495
496    /**
497     * Gets an AllDecorators object containing the decorators for the method and its parameters.
498     *
499     * @param method The class method member.
500     */
501    function getAllDecoratorsOfMethod(method: MethodDeclaration): AllDecorators | undefined {
502        if (!method.body) {
503            return undefined;
504        }
505
506        const decorators = getDecorators(method);
507        const parameters = getDecoratorsOfParameters(method);
508        if (!some(decorators) && !some(parameters)) {
509            return undefined;
510        }
511
512        return { decorators, parameters };
513    }
514
515    /**
516     * Gets an AllDecorators object containing the decorators for the property.
517     *
518     * @param property The class property member.
519     */
520    function getAllDecoratorsOfProperty(property: PropertyDeclaration): AllDecorators | undefined {
521        const decorators = getDecorators(property);
522        if (!some(decorators)) {
523            return undefined;
524
525        }
526
527        return { decorators };
528    }
529
530}
531