diff --git a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts index ddf9954803..c4d7c89cbe 100644 --- a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts +++ b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts @@ -419,10 +419,10 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N * @returns An array of class symbols. */ findClassSymbols(sourceFile: ts.SourceFile): NgccClassSymbol[] { - const classes: NgccClassSymbol[] = []; + const classes = new Map(); this.getModuleStatements(sourceFile) .forEach(statement => this.addClassSymbolsFromStatement(classes, statement)); - return classes; + return Array.from(classes.values()); } /** @@ -462,20 +462,27 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N declaration.getText()} in ${declaration.getSourceFile().fileName}`); } + const decl = this.getDeclarationOfIdentifier(declaration.name); + if (decl === null) { + throw new Error( + `Cannot get the dts file for a node that cannot be associated with a declaration ${ + declaration.getText()} in ${declaration.getSourceFile().fileName}`); + } + // Try to retrieve the dts declaration from the public map if (this.publicDtsDeclarationMap === null) { this.publicDtsDeclarationMap = this.computePublicDtsDeclarationMap(this.src, this.dts); } - if (this.publicDtsDeclarationMap.has(declaration)) { - return this.publicDtsDeclarationMap.get(declaration)!; + if (this.publicDtsDeclarationMap.has(decl.node)) { + return this.publicDtsDeclarationMap.get(decl.node)!; } // No public export, try the private map if (this.privateDtsDeclarationMap === null) { this.privateDtsDeclarationMap = this.computePrivateDtsDeclarationMap(this.src, this.dts); } - if (this.privateDtsDeclarationMap.has(declaration)) { - return this.privateDtsDeclarationMap.get(declaration)!; + if (this.privateDtsDeclarationMap.has(decl.node)) { + return this.privateDtsDeclarationMap.get(decl.node)!; } // No declaration found at all @@ -547,21 +554,21 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N ///////////// Protected Helpers ///////////// /** - * Extract all the "classes" from the `statement` and add them to the `classes` array. + * Extract all the "classes" from the `statement` and add them to the `classes` map. */ - protected addClassSymbolsFromStatement(classes: NgccClassSymbol[], statement: ts.Statement): - void { + protected addClassSymbolsFromStatement( + classes: Map, statement: ts.Statement): void { if (ts.isVariableStatement(statement)) { statement.declarationList.declarations.forEach(declaration => { const classSymbol = this.getClassSymbol(declaration); if (classSymbol) { - classes.push(classSymbol); + classes.set(classSymbol.implementation, classSymbol); } }); } else if (ts.isClassDeclaration(statement)) { const classSymbol = this.getClassSymbol(statement); if (classSymbol) { - classes.push(classSymbol); + classes.set(classSymbol.implementation, classSymbol); } } } diff --git a/packages/compiler-cli/ngcc/src/host/umd_host.ts b/packages/compiler-cli/ngcc/src/host/umd_host.ts index ec934ffaff..4b49a2e66d 100644 --- a/packages/compiler-cli/ngcc/src/host/umd_host.ts +++ b/packages/compiler-cli/ngcc/src/host/umd_host.ts @@ -10,13 +10,14 @@ import * as ts from 'typescript'; import {absoluteFrom} from '../../../src/ngtsc/file_system'; import {Logger} from '../../../src/ngtsc/logging'; -import {Declaration, DeclarationKind, Import} from '../../../src/ngtsc/reflection'; +import {Declaration, DeclarationKind, Import, isNamedFunctionDeclaration} from '../../../src/ngtsc/reflection'; import {BundleProgram} from '../packages/bundle_program'; import {FactoryMap, getTsHelperFnFromIdentifier, stripExtension} from '../utils'; -import {DefinePropertyReexportStatement, ExportDeclaration, ExportsStatement, extractGetterFnExpression, findNamespaceOfIdentifier, findRequireCallReference, isDefinePropertyReexportStatement, isExportsStatement, isExternalImport, isRequireCall, isWildcardReexportStatement, WildcardReexportStatement} from './commonjs_umd_utils'; -import {isAssignment} from './esm2015_host'; +import {DefinePropertyReexportStatement, ExportDeclaration, ExportsStatement, extractGetterFnExpression, findNamespaceOfIdentifier, findRequireCallReference, isDefinePropertyReexportStatement, isExportsAssignment, isExportsDeclaration, isExportsStatement, isExternalImport, isRequireCall, isWildcardReexportStatement, WildcardReexportStatement} from './commonjs_umd_utils'; +import {getInnerClassDeclaration, getOuterNodeFromInnerDeclaration, isAssignment} from './esm2015_host'; import {Esm5ReflectionHost} from './esm5_host'; +import {NgccClassSymbol} from './ngcc_host'; import {stripParentheses} from './utils'; export class UmdReflectionHost extends Esm5ReflectionHost { @@ -45,8 +46,40 @@ export class UmdReflectionHost extends Esm5ReflectionHost { } getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null { - return this.getExportsDeclaration(id) || this.getUmdModuleDeclaration(id) || - this.getUmdDeclaration(id) || super.getDeclarationOfIdentifier(id); + // First we try one of the the following: + // 1. The `exports` identifier - referring to the current file/module. + // 2. An identifier (e.g. `foo`) that refers to an imported UMD module. + // 3. A UMD style export identifier (e.g. the `foo` of `exports.foo`). + const declaration = this.getExportsDeclaration(id) || this.getUmdModuleDeclaration(id) || + this.getUmdDeclaration(id); + if (declaration !== null) { + return declaration; + } + + // Try to get the declaration using the super class. + const superDeclaration = super.getDeclarationOfIdentifier(id); + if (superDeclaration === null) { + return null; + } + + // Check to see if the declaration is the inner node of a declaration IIFE. + const outerNode = getOuterNodeFromInnerDeclaration(superDeclaration.node); + if (outerNode === null) { + return superDeclaration; + } + + // We are only interested if the outer declaration is of the form + // `exports. = `. + if (!isExportsAssignment(outerNode)) { + return superDeclaration; + } + + return { + kind: DeclarationKind.Inline, + node: outerNode.left, + known: null, + viaModule: null, + }; } getExportsOfModule(module: ts.Node): Map|null { @@ -78,6 +111,102 @@ export class UmdReflectionHost extends Esm5ReflectionHost { return umdModule !== null ? Array.from(umdModule.factoryFn.body.statements) : []; } + protected getClassSymbolFromOuterDeclaration(declaration: ts.Node): NgccClassSymbol|undefined { + const superSymbol = super.getClassSymbolFromOuterDeclaration(declaration); + if (superSymbol) { + return superSymbol; + } + + if (!isExportsDeclaration(declaration)) { + return undefined; + } + + let initializer = skipAliases(declaration.parent.right); + + if (ts.isIdentifier(initializer)) { + const implementation = this.getDeclarationOfIdentifier(initializer); + if (implementation !== null) { + const implementationSymbol = this.getClassSymbol(implementation.node); + if (implementationSymbol !== null) { + return implementationSymbol; + } + } + } + + const innerDeclaration = getInnerClassDeclaration(initializer); + if (innerDeclaration !== null) { + return this.createClassSymbol(declaration.name, innerDeclaration); + } + + return undefined; + } + + + protected getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol|undefined { + const superClassSymbol = super.getClassSymbolFromInnerDeclaration(declaration); + if (superClassSymbol !== undefined) { + return superClassSymbol; + } + + if (!isNamedFunctionDeclaration(declaration)) { + return undefined; + } + + const outerNode = getOuterNodeFromInnerDeclaration(declaration); + if (outerNode === null || !isExportsAssignment(outerNode)) { + return undefined; + } + + return this.createClassSymbol(outerNode.left.name, declaration); + } + + /** + * Extract all "classes" from the `statement` and add them to the `classes` map. + */ + protected addClassSymbolsFromStatement( + classes: Map, statement: ts.Statement): void { + super.addClassSymbolsFromStatement(classes, statement); + + // Also check for exports of the form: `exports. = ;` + if (isExportsStatement(statement)) { + const classSymbol = this.getClassSymbol(statement.expression.left); + if (classSymbol) { + classes.set(classSymbol.implementation, classSymbol); + } + } + } + + /** + * Analyze the given statement to see if it corresponds with an exports declaration like + * `exports.MyClass = MyClass_1 = ;`. If so, the declaration of `MyClass_1` + * is associated with the `MyClass` identifier. + * + * @param statement The statement that needs to be preprocessed. + */ + protected preprocessStatement(statement: ts.Statement): void { + super.preprocessStatement(statement); + + if (!isExportsStatement(statement)) { + return; + } + + const declaration = statement.expression.left; + const initializer = statement.expression.right; + if (!isAssignment(initializer) || !ts.isIdentifier(initializer.left) || + !this.isClass(declaration)) { + return; + } + + const aliasedIdentifier = initializer.left; + + const aliasedDeclaration = this.getDeclarationOfIdentifier(aliasedIdentifier); + if (aliasedDeclaration === null || aliasedDeclaration.node === null) { + throw new Error( + `Unable to locate declaration of ${aliasedIdentifier.text} in "${statement.getText()}"`); + } + this.aliasedClassDeclarations.set(aliasedDeclaration.node, declaration.name); + } + private computeUmdModule(sourceFile: ts.SourceFile): UmdModule|null { if (sourceFile.statements.length !== 1) { throw new Error( @@ -93,7 +222,17 @@ export class UmdReflectionHost extends Esm5ReflectionHost { for (const statement of this.getModuleStatements(sourceFile)) { if (isExportsStatement(statement)) { const exportDeclaration = this.extractBasicUmdExportDeclaration(statement); - moduleMap.set(exportDeclaration.name, exportDeclaration.declaration); + if (!moduleMap.has(exportDeclaration.name)) { + // We assume that the first `exports.` is the actual declaration, and that any + // subsequent statements that match are decorating the original declaration. + // For example: + // ``` + // exports.foo = ; + // exports.foo = __decorate(, exports.foo); + // ``` + // The declaration is the first line not the second. + moduleMap.set(exportDeclaration.name, exportDeclaration.declaration); + } } else if (isWildcardReexportStatement(statement)) { const reexports = this.extractUmdWildcardReexports(statement, sourceFile); for (const reexport of reexports) { @@ -136,7 +275,13 @@ export class UmdReflectionHost extends Esm5ReflectionHost { private extractBasicUmdExportDeclaration(statement: ExportsStatement): ExportDeclaration { const name = statement.expression.left.name.text; const exportExpression = skipAliases(statement.expression.right); - return this.extractUmdExportDeclaration(name, exportExpression); + const declaration = this.getDeclarationOfExpression(exportExpression) ?? { + kind: DeclarationKind.Inline, + node: statement.expression.left, + known: null, + viaModule: null, + }; + return {name, declaration}; } private extractUmdWildcardReexports( @@ -185,17 +330,20 @@ export class UmdReflectionHost extends Esm5ReflectionHost { if (getterFnExpression === null) { return null; } - return this.extractUmdExportDeclaration(name, getterFnExpression); - } - private extractUmdExportDeclaration(name: string, expression: ts.Expression): ExportDeclaration { - const declaration = this.getDeclarationOfExpression(expression); + const declaration = this.getDeclarationOfExpression(getterFnExpression); if (declaration !== null) { return {name, declaration}; } + return { name, - declaration: {kind: DeclarationKind.Inline, node: expression, known: null, viaModule: null}, + declaration: { + kind: DeclarationKind.Inline, + node: getterFnExpression, + known: null, + viaModule: null, + }, }; } @@ -214,6 +362,21 @@ export class UmdReflectionHost extends Esm5ReflectionHost { if (nsIdentifier === null) { return null; } + + if (nsIdentifier.parent.parent && isExportsAssignment(nsIdentifier.parent.parent)) { + const initializer = nsIdentifier.parent.parent.right; + if (ts.isIdentifier(initializer)) { + return this.getDeclarationOfIdentifier(initializer); + } + return this.detectKnownDeclaration({ + kind: DeclarationKind.Inline, + node: nsIdentifier.parent.parent.left, + implementation: skipAliases(nsIdentifier.parent.parent.right), + viaModule: null, + known: null, + }); + } + const moduleDeclaration = this.getUmdModuleDeclaration(nsIdentifier); if (moduleDeclaration === null || moduleDeclaration.node === null || !ts.isSourceFile(moduleDeclaration.node)) { @@ -242,7 +405,7 @@ export class UmdReflectionHost extends Esm5ReflectionHost { } private getExportsDeclaration(id: ts.Identifier): Declaration|null { - if (!isExportIdentifier(id)) { + if (!isExportsIdentifier(id)) { return null; } @@ -398,7 +561,7 @@ function getRequiredModulePath(wrapperFn: ts.FunctionExpression, paramIndex: num /** * Is the `node` an identifier with the name "exports"? */ -export function isExportIdentifier(node: ts.Node): node is ts.Identifier { +function isExportsIdentifier(node: ts.Node): node is ts.Identifier { return ts.isIdentifier(node) && node.text === 'exports'; } @@ -412,7 +575,7 @@ export function isExportIdentifier(node: ts.Node): node is ts.Identifier { * @param node the expression to parse * @returns the original `node` or the far right expression of a series of assignments. */ -export function skipAliases(node: ts.Expression): ts.Expression { +function skipAliases(node: ts.Expression): ts.Expression { while (isAssignment(node)) { node = node.right; } diff --git a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts index fe06ec14b5..d208650d3e 100644 --- a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts @@ -14,53 +14,104 @@ import {MockLogger} from '../../../src/ngtsc/logging/testing'; import {ClassMemberKind, ConcreteDeclaration, CtorParameter, DeclarationKind, DownleveledEnum, Import, InlineDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost, TypeValueReferenceKind} from '../../../src/ngtsc/reflection'; import {getDeclaration} from '../../../src/ngtsc/testing'; import {loadFakeCore, loadTestFiles} from '../../../test/helpers'; -import {isExportsStatement} from '../../src/host/commonjs_umd_utils'; +import {isExportsDeclaration, isExportsStatement} from '../../src/host/commonjs_umd_utils'; import {DelegatingReflectionHost} from '../../src/host/delegating_host'; -import {getIifeBody} from '../../src/host/esm2015_host'; import {NgccReflectionHost} from '../../src/host/ngcc_host'; import {parseStatementForUmdModule, UmdReflectionHost} from '../../src/host/umd_host'; import {BundleProgram} from '../../src/packages/bundle_program'; +import {hasNameIdentifier} from '../../src/utils'; import {getRootFiles, makeTestBundleProgram} from '../helpers/utils'; import {expectTypeValueReferencesForParameters} from './util'; runInEachFileSystem(() => { - describe('UmdReflectionHost', () => { - let _: typeof absoluteFrom; + for (const mode of ['inline exports', 'exported vars']) { + describe(`UmdReflectionHost [with ${mode}]`, () => { + let _: typeof absoluteFrom; - let SOME_DIRECTIVE_FILE: TestFile; - let TOPLEVEL_DECORATORS_FILE: TestFile; - let CTOR_DECORATORS_ARRAY_FILE: TestFile; - let SIMPLE_ES2015_CLASS_FILE: TestFile; - let SIMPLE_CLASS_FILE: TestFile; - let FOO_FUNCTION_FILE: TestFile; - let INLINE_EXPORT_FILE: TestFile; - let EXPORTS_IDENTIFIERS_FILE: TestFile; - let INVALID_DECORATORS_FILE: TestFile; - let INVALID_DECORATOR_ARGS_FILE: TestFile; - let INVALID_PROP_DECORATORS_FILE: TestFile; - let INVALID_PROP_DECORATOR_ARGS_FILE: TestFile; - let INVALID_CTOR_DECORATORS_FILE: TestFile; - let INVALID_CTOR_DECORATOR_ARGS_FILE: TestFile; - let IMPORTS_FILES: TestFile[]; - let EXPORTS_FILES: TestFile[]; - let FUNCTION_BODY_FILE: TestFile; - let DECORATED_FILES: TestFile[]; - let TYPINGS_SRC_FILES: TestFile[]; - let TYPINGS_DTS_FILES: TestFile[]; + let SOME_DIRECTIVE_FILE: TestFile; + let TOPLEVEL_DECORATORS_FILE: TestFile; + let CTOR_DECORATORS_ARRAY_FILE: TestFile; + let SIMPLE_ES2015_CLASS_FILE: TestFile; + let SIMPLE_CLASS_FILE: TestFile; + let FOO_FUNCTION_FILE: TestFile; + let INLINE_EXPORT_FILE: TestFile; + let EXPORTS_IDENTIFIERS_FILE: TestFile; + let INVALID_DECORATORS_FILE: TestFile; + let INVALID_DECORATOR_ARGS_FILE: TestFile; + let INVALID_PROP_DECORATORS_FILE: TestFile; + let INVALID_PROP_DECORATOR_ARGS_FILE: TestFile; + let INVALID_CTOR_DECORATORS_FILE: TestFile; + let INVALID_CTOR_DECORATOR_ARGS_FILE: TestFile; + let IMPORTS_FILES: TestFile[]; + let EXPORTS_FILES: TestFile[]; + let FUNCTION_BODY_FILE: TestFile; + let DECORATED_FILES: TestFile[]; + let TYPINGS_SRC_FILES: TestFile[]; + let TYPINGS_DTS_FILES: TestFile[]; - // Helpers - const createHost = (bundle: BundleProgram, ngccHost: UmdReflectionHost) => { - const tsHost = new TypeScriptReflectionHost(bundle.program.getTypeChecker()); - return new DelegatingReflectionHost(tsHost, ngccHost); - }; + // Helpers + const createHost = (bundle: BundleProgram, ngccHost: UmdReflectionHost) => { + const tsHost = new TypeScriptReflectionHost(bundle.program.getTypeChecker()); + return new DelegatingReflectionHost(tsHost, ngccHost); + }; - beforeEach(() => { - _ = absoluteFrom; + // There are two different forms of UMD export declaration. + // + // - "exported vars" where there is a variable declaration that is then assigned to the + // `exports` object. For example: + // ``` + // var MyClass = <...>; + // exports.MyClass = MyClass; + // ``` + // + // - "inline exports" where there is no intemediate variable declaration. For example: + // ``` + // exports.MyClass = <...>; + // ``` - SOME_DIRECTIVE_FILE = { - name: _('/some_directive.umd.js'), - contents: ` + // The following helpers allow us to setup code examples to use these two different + // approaches. + + /** + * Create an export declaration: e.g. + * + * ``` + * exports. = ; + * ``` + * + * or + * + * ``` + * var = ; + * ``` + */ + const expDecl = (name: string, noVar = false) => + mode === 'exported vars' ? `${noVar ? '' : 'var '}${name}` : `exports.${name}`; + + /** + * Export a variable that references a declaration (only for when the export was defined as a + * variable). For example: + * + * ``` + * exports. = ; + * ``` + */ + const varExp = (name: string) => mode === 'exported vars' ? `exports.${name} = ${name};` : ''; + + /** + * Select a predicate for matching an exported declaration based on whether the code contains + * "exported vars" or "inline exports". + */ + const isDesiredDeclaration: typeof hasNameIdentifier = + mode === 'exported vars' ? isNamedVariableDeclaration : isExportsDeclaration; + + beforeEach(() => { + _ = absoluteFrom; + + SOME_DIRECTIVE_FILE = { + name: _('/some_directive.umd.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) : @@ -71,7 +122,7 @@ runInEachFileSystem(() => { var ViewContainerRef = {}; var TemplateRef = {}; - var SomeDirective = (function() { + ${expDecl('SomeDirective')} = (function() { function SomeDirective(_viewContainer, _template, injected) { this.instanceProperty = 'instance'; } @@ -94,13 +145,13 @@ runInEachFileSystem(() => { }; return SomeDirective; }()); - exports.SomeDirective = SomeDirective; + ${varExp('SomeDirective')} })));`, - }; + }; - TOPLEVEL_DECORATORS_FILE = { - name: _('/toplevel_decorators.umd.js'), - contents: ` + TOPLEVEL_DECORATORS_FILE = { + name: _('/toplevel_decorators.umd.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) : @@ -111,105 +162,105 @@ runInEachFileSystem(() => { var ViewContainerRef = {}; var TemplateRef = {}; - var SomeDirective = (function() { + ${expDecl('SomeDirective')} = (function() { function SomeDirective(_viewContainer, _template, injected) {} return SomeDirective; }()); - SomeDirective.decorators = [ + ${expDecl('SomeDirective', true)}.decorators = [ { type: core.Directive, args: [{ selector: '[someDirective]' },] } ]; - SomeDirective.ctorParameters = function() { return [ + ${expDecl('SomeDirective', true)}.ctorParameters = function() { return [ { type: ViewContainerRef, }, { type: TemplateRef, }, { type: undefined, decorators: [{ type: core.Inject, args: [INJECTED_TOKEN,] },] }, ]; }; - SomeDirective.propDecorators = { + ${expDecl('SomeDirective', true)}.propDecorators = { "input1": [{ type: core.Input },], "input2": [{ type: core.Input },], }; - exports.SomeDirective = SomeDirective; + ${varExp('SomeDirective')} })));`, - }; + }; - CTOR_DECORATORS_ARRAY_FILE = { - name: _('/ctor_decorated_as_array.js'), - contents: ` + CTOR_DECORATORS_ARRAY_FILE = { + name: _('/ctor_decorated_as_array.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('ctor_decorated_as_array', ['exports', '@angular/core'], factory) : (factory(global.ctor_decorated_as_array,global.ng.core)); }(this, (function (exports,core) { 'use strict'; - var CtorDecoratedAsArray = (function() { + ${expDecl('CtorDecoratedAsArray')} = (function() { function CtorDecoratedAsArray(arg1) { } CtorDecoratedAsArray.ctorParameters = [{ type: ParamType, decorators: [{ type: Inject },] }]; return CtorDecoratedAsArray; }()); - exports.CtorDecoratedAsArray = CtorDecoratedAsArray; + ${varExp('CtorDecoratedAsArray')} })));`, - }; + }; - SIMPLE_ES2015_CLASS_FILE = { - name: _('/simple_es2015_class.d.ts'), - contents: ` + SIMPLE_ES2015_CLASS_FILE = { + name: _('/simple_es2015_class.d.ts'), + contents: ` export class EmptyClass {} `, - }; + }; - SIMPLE_CLASS_FILE = { - name: _('/simple_class.js'), - contents: ` + SIMPLE_CLASS_FILE = { + name: _('/simple_class.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('simple_class', ['exports'], factory) : (factory(global.simple_class)); }(this, (function (exports) { 'use strict'; - var EmptyClass = (function() { + ${expDecl('EmptyClass')} = (function() { function EmptyClass() { } return EmptyClass; }()); - var NoParensClass = function() { - function EmptyClass() { + ${expDecl('NoParensClass')} = function() { + function NoParensClass() { } - return EmptyClass; + return NoParensClass; }(); - var InnerParensClass = (function() { - function EmptyClass() { + ${expDecl('InnerParensClass')} = (function() { + function InnerParensClass() { } - return EmptyClass; + return InnerParensClass; })(); - var NoDecoratorConstructorClass = (function() { + ${expDecl('NoDecoratorConstructorClass')} = (function() { function NoDecoratorConstructorClass(foo) { } return NoDecoratorConstructorClass; }()); - var OuterClass1 = (function() { + ${expDecl('OuterClass1')} = (function() { function InnerClass1() { } return InnerClass1; }()); - var OuterClass2 = (function() { + ${expDecl('OuterClass2')} = (function() { function InnerClass2() { } InnerClass2_1 = InnerClass12 var InnerClass2_1; return InnerClass2; }()); - var SuperClass = (function() { function SuperClass() {} return SuperClass; }()); - var ChildClass = /** @class */ (function (_super) { - __extends(ChildClass, _super); + ${expDecl('SuperClass')} = (function() { function SuperClass() {} return SuperClass; }()); + ${expDecl('ChildClass')} = /** @class */ (function (_super) { + __extends(InnerChildClass, _super); function InnerChildClass() {} return InnerChildClass; }(SuperClass); - exports.EmptyClass = EmptyClass; - exports.NoDecoratorConstructorClass = NoDecoratorConstructorClass; + ${varExp('EmptyClass')} + ${varExp('NoDecoratorConstructorClass')} })));`, - }; + }; - FOO_FUNCTION_FILE = { - name: _('/foo_function.js'), - contents: ` + FOO_FUNCTION_FILE = { + name: _('/foo_function.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('foo_function', ['exports', '@angular/core'], factory) : @@ -219,13 +270,13 @@ runInEachFileSystem(() => { foo.decorators = [ { type: core.Directive, args: [{ selector: '[ignored]' },] } ]; - exports.foo = foo; + ${varExp('foo')} })));`, - }; + }; - INLINE_EXPORT_FILE = { - name: _('/inline_export.js'), - contents: ` + INLINE_EXPORT_FILE = { + name: _('/inline_export.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('foo_function', ['exports', '@angular/core'], factory) : @@ -238,11 +289,11 @@ runInEachFileSystem(() => { exports.directives = [foo]; }))); `, - }; + }; - EXPORTS_IDENTIFIERS_FILE = { - name: _('/exports_identifiers.js'), - contents: ` + EXPORTS_IDENTIFIERS_FILE = { + name: _('/exports_identifiers.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('foo_function', ['exports', '@angular/core'], factory) : @@ -265,17 +316,17 @@ runInEachFileSystem(() => { } }))); `, - }; + }; - INVALID_DECORATORS_FILE = { - name: _('/invalid_decorators.js'), - contents: ` + INVALID_DECORATORS_FILE = { + name: _('/invalid_decorators.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('invalid_decorators', ['exports', '@angular/core'], factory) : (factory(global.invalid_decorators, global.ng.core)); }(this, (function (exports,core) { 'use strict'; - var NotArrayLiteral = (function() { + ${expDecl('NotArrayLiteral')} = (function() { function NotArrayLiteral() { } NotArrayLiteral.decorators = () => [ @@ -284,7 +335,7 @@ runInEachFileSystem(() => { return NotArrayLiteral; }()); - var NotObjectLiteral = (function() { + ${expDecl('NotObjectLiteral')} = (function() { function NotObjectLiteral() { } NotObjectLiteral.decorators = [ @@ -294,7 +345,7 @@ runInEachFileSystem(() => { return NotObjectLiteral; }()); - var NoTypeProperty = (function() { + ${expDecl('NoTypeProperty')} = (function() { function NoTypeProperty() { } NoTypeProperty.decorators = [ @@ -304,7 +355,7 @@ runInEachFileSystem(() => { return NoTypeProperty; }()); - var NotIdentifier = (function() { + ${expDecl('NotIdentifier')} = (function() { function NotIdentifier() { } NotIdentifier.decorators = [ @@ -314,17 +365,17 @@ runInEachFileSystem(() => { return NotIdentifier; }()); })));`, - }; + }; - INVALID_DECORATOR_ARGS_FILE = { - name: _('/invalid_decorator_args.js'), - contents: ` + INVALID_DECORATOR_ARGS_FILE = { + name: _('/invalid_decorator_args.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('invalid_decorator_args', ['exports', '@angular/core'], factory) : (factory(global.invalid_decorator_args, global.ng.core)); }(this, (function (exports,core) { 'use strict'; - var NoArgsProperty = (function() { + ${expDecl('NoArgsProperty')} = (function() { function NoArgsProperty() { } NoArgsProperty.decorators = [ @@ -334,7 +385,7 @@ runInEachFileSystem(() => { }()); var args = [{ selector: '[ignored]' },]; - var NoPropertyAssignment = (function() { + ${expDecl('NoPropertyAssignment')} = (function() { function NoPropertyAssignment() { } NoPropertyAssignment.decorators = [ @@ -343,7 +394,7 @@ runInEachFileSystem(() => { return NoPropertyAssignment; }()); - var NotArrayLiteral = (function() { + ${expDecl('NotArrayLiteral')} = (function() { function NotArrayLiteral() { } NotArrayLiteral.decorators = [ @@ -352,17 +403,17 @@ runInEachFileSystem(() => { return NotArrayLiteral; }()); })));`, - }; + }; - INVALID_PROP_DECORATORS_FILE = { - name: _('/invalid_prop_decorators.js'), - contents: ` + INVALID_PROP_DECORATORS_FILE = { + name: _('/invalid_prop_decorators.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('invalid_prop_decorators', ['exports', '@angular/core'], factory) : (factory(global.invalid_prop_decorators, global.ng.core)); }(this, (function (exports,core) { 'use strict'; - var NotObjectLiteral = (function() { + ${expDecl('NotObjectLiteral')} = (function() { function NotObjectLiteral() { } NotObjectLiteral.propDecorators = () => ({ @@ -371,7 +422,7 @@ runInEachFileSystem(() => { return NotObjectLiteral; }()); - var NotObjectLiteralProp = (function() { + ${expDecl('NotObjectLiteralProp')} = (function() { function NotObjectLiteralProp() { } NotObjectLiteralProp.propDecorators = { @@ -383,7 +434,7 @@ runInEachFileSystem(() => { return NotObjectLiteralProp; }()); - var NoTypeProperty = (function() { + ${expDecl('NoTypeProperty')} = (function() { function NoTypeProperty() { } NoTypeProperty.propDecorators = { @@ -395,7 +446,7 @@ runInEachFileSystem(() => { return NoTypeProperty; }()); - var NotIdentifier = (function() { + ${expDecl('NotIdentifier')} = (function() { function NotIdentifier() { } NotIdentifier.propDecorators = { @@ -407,17 +458,17 @@ runInEachFileSystem(() => { return NotIdentifier; }()); })));`, - }; + }; - INVALID_PROP_DECORATOR_ARGS_FILE = { - name: _('/invalid_prop_decorator_args.js'), - contents: ` + INVALID_PROP_DECORATOR_ARGS_FILE = { + name: _('/invalid_prop_decorator_args.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('invalid_prop_decorator_args', ['exports', '@angular/core'], factory) : (factory(global.invalid_prop_decorator_args, global.ng.core)); }(this, (function (exports,core) { 'use strict'; - var NoArgsProperty = (function() { + ${expDecl('NoArgsProperty')} = (function() { function NoArgsProperty() { } NoArgsProperty.propDecorators = { @@ -427,7 +478,7 @@ runInEachFileSystem(() => { }()); var args = [{ selector: '[ignored]' },]; - var NoPropertyAssignment = (function() { + ${expDecl('NoPropertyAssignment')} = (function() { function NoPropertyAssignment() { } NoPropertyAssignment.propDecorators = { @@ -436,7 +487,7 @@ runInEachFileSystem(() => { return NoPropertyAssignment; }()); - var NotArrayLiteral = (function() { + ${expDecl('NotArrayLiteral')} = (function() { function NotArrayLiteral() { } NotArrayLiteral.propDecorators = { @@ -445,29 +496,29 @@ runInEachFileSystem(() => { return NotArrayLiteral; }()); })));`, - }; + }; - INVALID_CTOR_DECORATORS_FILE = { - name: _('/invalid_ctor_decorators.js'), - contents: ` + INVALID_CTOR_DECORATORS_FILE = { + name: _('/invalid_ctor_decorators.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('invalid_ctor_decorators', ['exports', '@angular/core'], factory) : (factory(global.invalid_ctor_decorators,global.ng.core)); }(this, (function (exports,core) { 'use strict'; - var NoParameters = (function() { + ${expDecl('NoParameters')} = (function() { function NoParameters() {} return NoParameters; }()); - var NotArrayLiteral = (function() { + ${expDecl('NotArrayLiteral')} = (function() { function NotArrayLiteral(arg1) { } NotArrayLiteral.ctorParameters = function() { return 'StringsAreNotArrayLiterals'; }; return NotArrayLiteral; }()); - var NotObjectLiteral = (function() { + ${expDecl('NotObjectLiteral')} = (function() { function NotObjectLiteral(arg1, arg2) { } NotObjectLiteral.ctorParameters = function() { return [ @@ -477,7 +528,7 @@ runInEachFileSystem(() => { return NotObjectLiteral; }()); - var NoTypeProperty = (function() { + ${expDecl('NoTypeProperty')} = (function() { function NoTypeProperty(arg1, arg2) { } NoTypeProperty.ctorParameters = function() { return [ @@ -492,7 +543,7 @@ runInEachFileSystem(() => { return NoTypeProperty; }()); - var NotIdentifier = (function() { + ${expDecl('NotIdentifier')} = (function() { function NotIdentifier(arg1, arg2) { } NotIdentifier.ctorParameters = function() { return [ @@ -507,17 +558,17 @@ runInEachFileSystem(() => { return NotIdentifier; }()); })));`, - }; + }; - INVALID_CTOR_DECORATOR_ARGS_FILE = { - name: _('/invalid_ctor_decorator_args.js'), - contents: ` + INVALID_CTOR_DECORATOR_ARGS_FILE = { + name: _('/invalid_ctor_decorator_args.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('invalid_ctor_decorator_args', ['exports', '@angular/core'], factory) : (factory(global.invalid_ctor_decorator_args,global.ng.core)); }(this, (function (exports,core) { 'use strict'; - var NoArgsProperty = (function() { + ${expDecl('NoArgsProperty')} = (function() { function NoArgsProperty(arg1) { } NoArgsProperty.ctorParameters = function() { return [ @@ -527,7 +578,7 @@ runInEachFileSystem(() => { }()); var args = [{ selector: '[ignored]' },]; - var NoPropertyAssignment = (function() { + ${expDecl('NoPropertyAssignment')} = (function() { function NoPropertyAssignment(arg1) { } NoPropertyAssignment.ctorParameters = function() { return [ @@ -536,7 +587,7 @@ runInEachFileSystem(() => { return NoPropertyAssignment; }()); - var NotArrayLiteral = (function() { + ${expDecl('NotArrayLiteral')} = (function() { function NotArrayLiteral(arg1) { } NotArrayLiteral.ctorParameters = function() { return [ @@ -545,34 +596,34 @@ runInEachFileSystem(() => { return NotArrayLiteral; }()); })));`, - }; + }; - IMPORTS_FILES = [ - { - name: _('/index.js'), - contents: ` + IMPORTS_FILES = [ + { + name: _('/index.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./file_a'), require('./file_b'), require('./file_c')) : typeof define === 'function' && define.amd ? define('index', ['exports', './file_a', './file_b', './file_c'], factory) : (factory(global.index, global.file_a, global.file_b, global.file_c)); }(this, (function (exports, file_a, file_b, file_c) { 'use strict'; })));`, - }, - { - name: _('/file_a.js'), - contents: ` + }, + { + name: _('/file_a.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('file_a', ['exports'], factory) : (factory(global.file_a)); }(this, (function (exports) { 'use strict'; var a = 'a'; - exports.a = a; + ${varExp('a')} })));`, - }, - { - name: _('/file_b.js'), - contents: ` + }, + { + name: _('/file_b.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./file_a')) : typeof define === 'function' && define.amd ? define('file_b', ['exports', './file_a'], factory) : @@ -582,10 +633,10 @@ runInEachFileSystem(() => { var c = 'c'; var d = c; })));`, - }, - { - name: _('/file_c.js'), - contents: ` + }, + { + name: _('/file_c.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./file_a')) : typeof define === 'function' && define.amd ? define('file_c', ['exports', 'file_a'], factory) : @@ -593,122 +644,146 @@ runInEachFileSystem(() => { }(this, function (exports, file_a) { 'use strict'; var c = file_a.a; }));`, - }, - ]; + }, + ]; - EXPORTS_FILES = [ - { - name: _('/index.js'), - contents: `(function (global, factory) {\n` + - ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./a_module'), require('./b_module'), require('./wildcard_reexports'), require('./wildcard_reexports_imported_helpers'), require('./wildcard_reexports_with_require'), require('./define_property_reexports')) :\n` + - ` typeof define === 'function' && define.amd ? define('index', ['exports', './a_module', './b_module', './wildcard_reexports', './wildcard_reexports_imported_helpers', './wildcard_reexports_with_require', './define_property_reexports'], factory) :\n` + - ` (factory(global.index, global.a_module, global.b_module, global.wildcard_reexports, global.wildcard_reexports_imported_helpers, global.wildcard_reexports_with_require, global.define_property_reexports));\n` + - `}(this, (function (exports, a_module, b_module, wildcard_reexports, wildcard_reexports_imported_helpers, wildcard_reexports_with_require, define_property_reexports) { 'use strict';\n` + - `})));\n` - }, - { - name: _('/a_module.js'), - contents: `(function (global, factory) {\n` + - ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n` + - ` typeof define === 'function' && define.amd ? define('a_module', ['exports'], factory) :\n` + - ` (factory(global.a_module));\n` + - `}(this, (function (exports) { 'use strict';\n` + - ` var a = 'a';\n` + - ` exports.a = a;\n` + - `})));\n`, - }, - { - name: _('/b_module.js'), - contents: `(function (global, factory) {\n` + - ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('./a_module')) :\n` + - ` typeof define === 'function' && define.amd ? define('b_module', ['exports', '@angular/core', './a_module'], factory) :\n` + - ` (factory(global.b_module));\n` + - `}(this, (function (exports, core, a_module) { 'use strict';\n` + - ` var b = a_module.a;\n` + - ` var e = 'e';\n` + - ` var SomeClass = (function() {\n` + - ` function SomeClass() {}\n` + - ` return SomeClass;\n` + - ` }());\n` + - `\n` + - ` exports.Directive = core.Directive;\n` + - ` exports.a = a_module.a;\n` + - ` exports.b = b;\n` + - ` exports.c = a_module.a;\n` + - ` exports.d = b;\n` + - ` exports.e = e;\n` + - ` exports.DirectiveX = core.Directive;\n` + - ` var SomeClass_1;\n` + - ` exports.SomeClass = SomeClass_1 = SomeClass;\n` + - `})));\n`, - }, - { - name: _('/xtra_module.js'), - contents: `(function (global, factory) {\n` + - ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n` + - ` typeof define === 'function' && define.amd ? define('xtra_module', ['exports'], factory) :\n` + - ` (factory(global.xtra_module));\n` + - `}(this, (function (exports) { 'use strict';\n` + - ` var xtra1 = 'xtra1';\n` + - ` var xtra2 = 'xtra2';\n` + - ` exports.xtra1 = xtra1;\n` + - ` exports.xtra2 = xtra2;\n` + - `})));\n`, - }, - { - name: _('/wildcard_reexports.js'), - contents: `(function (global, factory) {\n` + - ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./b_module'), require('./xtra_module')) :\n` + - ` typeof define === 'function' && define.amd ? define('wildcard_reexports', ['exports', './b_module', './xtra_module'], factory) :\n` + - ` (factory(global.wildcard_reexports, b_module, xtra_module));\n` + - `}(this, (function (exports, b_module, xtra_module) { 'use strict';\n` + - ` function __export(m) {\n` + - ` for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];\n` + - ` }\n` + - ` __export(b_module);\n` + - ` __export(xtra_module);\n` + - `})));\n`, - }, - { - name: _('/wildcard_reexports_imported_helpers.js'), - contents: `(function (global, factory) {\n` + - ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib'), require('./b_module'), require('./xtra_module')) :\n` + - ` typeof define === 'function' && define.amd ? define('wildcard_reexports', ['exports', 'tslib', './b_module', './xtra_module'], factory) :\n` + - ` (factory(global.wildcard_reexports_imported_helpers, tslib, b_module, xtra_module));\n` + - `}(this, (function (exports, tslib, b_module, xtra_module) { 'use strict';\n` + - ` tslib.__exportStar(b_module, exports);\n` + - ` tslib.__exportStar(xtra_module, exports);\n` + - `})));\n`, - }, - { - name: _('/wildcard_reexports_with_require.js'), - contents: `(function (global, factory) {\n` + - ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(require, exports) :\n` + - ` typeof define === 'function' && define.amd ? define('wildcard_reexports_with_require', ['require', 'exports'], factory);\n` + - `}(this, (function (require, exports) { 'use strict';\n` + - ` function __export(m) {\n` + - ` for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];\n` + - ` }\n` + - ` var b_module = require('./b_module');\n` + - ` __export(b_module);\n` + - ` __export(require('./xtra_module'));\n` + - `})));\n`, - }, - { - name: _('/define_property_reexports.js'), - contents: `(function (global, factory) {\n` + - ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(require, exports) :\n` + - ` typeof define === 'function' && define.amd ? define('define_property_reexports', ['require', 'exports'], factory);\n` + - `}(this, (function (require, exports) { 'use strict';\n` + - `var moduleA = require("./a_module");\n` + - `Object.defineProperty(exports, "newA", { enumerable: true, get: function () { return moduleA.a; } });\n` + - `})));`, - }, - ]; + EXPORTS_FILES = [ + { + name: _('/index.js'), + contents: `(function (global, factory) {\n` + + ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./a_module'), require('./b_module'), require('./wildcard_reexports'), require('./wildcard_reexports_imported_helpers'), require('./wildcard_reexports_with_require'), require('./define_property_reexports')) :\n` + + ` typeof define === 'function' && define.amd ? define('index', ['exports', './a_module', './b_module', './wildcard_reexports', './wildcard_reexports_imported_helpers', './wildcard_reexports_with_require', './define_property_reexports'], factory) :\n` + + ` (factory(global.index, global.a_module, global.b_module, global.wildcard_reexports, global.wildcard_reexports_imported_helpers, global.wildcard_reexports_with_require, global.define_property_reexports));\n` + + `}(this, (function (exports, a_module, b_module, wildcard_reexports, wildcard_reexports_imported_helpers, wildcard_reexports_with_require, define_property_reexports) { 'use strict';\n` + + `})));\n` + }, + { + name: _('/a_module.js'), + contents: `(function (global, factory) {\n` + + ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n` + + ` typeof define === 'function' && define.amd ? define('a_module', ['exports'], factory) :\n` + + ` (factory(global.a_module));\n` + + `}(this, (function (exports) { 'use strict';\n` + + ` var a = 'a';\n` + + ` exports.a = a;\n` + + `})));\n`, + }, + { + name: _('/b_module.js'), + contents: `(function (global, factory) {\n` + + ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('./a_module')) :\n` + + ` typeof define === 'function' && define.amd ? define('b_module', ['exports', '@angular/core', './a_module'], factory) :\n` + + ` (factory(global.b_module));\n` + + `}(this, (function (exports, core, a_module) { 'use strict';\n` + + ` var b = a_module.a;\n` + + ` var e = 'e';\n` + + ` var SomeClass = (function() {\n` + + ` function SomeClass() {}\n` + + ` return SomeClass;\n` + + ` }());\n` + + `\n` + + ` exports.Directive = core.Directive;\n` + + ` exports.a = a_module.a;\n` + + ` exports.b = b;\n` + + ` exports.c = a_module.a;\n` + + ` exports.d = b;\n` + + ` exports.e = e;\n` + + ` exports.DirectiveX = core.Directive;\n` + + ` var SomeClass_1;\n` + + ` exports.SomeClass = SomeClass_1 = SomeClass;\n` + + `})));\n`, + }, + { + name: _('/xtra_module.js'), + contents: `(function (global, factory) {\n` + + ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n` + + ` typeof define === 'function' && define.amd ? define('xtra_module', ['exports'], factory) :\n` + + ` (factory(global.xtra_module));\n` + + `}(this, (function (exports) { 'use strict';\n` + + ` var xtra1 = 'xtra1';\n` + + ` var xtra2 = 'xtra2';\n` + + ` exports.xtra1 = xtra1;\n` + + ` exports.xtra2 = xtra2;\n` + + `})));\n`, + }, + { + name: _('/wildcard_reexports.js'), + contents: `(function (global, factory) {\n` + + ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./b_module'), require('./xtra_module')) :\n` + + ` typeof define === 'function' && define.amd ? define('wildcard_reexports', ['exports', './b_module', './xtra_module'], factory) :\n` + + ` (factory(global.wildcard_reexports, b_module, xtra_module));\n` + + `}(this, (function (exports, b_module, xtra_module) { 'use strict';\n` + + ` function __export(m) {\n` + + ` for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];\n` + + ` }\n` + + ` __export(b_module);\n` + + ` __export(xtra_module);\n` + + `})));\n`, + }, + { + name: _('/wildcard_reexports_imported_helpers.js'), + contents: `(function (global, factory) {\n` + + ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib'), require('./b_module'), require('./xtra_module')) :\n` + + ` typeof define === 'function' && define.amd ? define('wildcard_reexports', ['exports', 'tslib', './b_module', './xtra_module'], factory) :\n` + + ` (factory(global.wildcard_reexports_imported_helpers, tslib, b_module, xtra_module));\n` + + `}(this, (function (exports, tslib, b_module, xtra_module) { 'use strict';\n` + + ` tslib.__exportStar(b_module, exports);\n` + + ` tslib.__exportStar(xtra_module, exports);\n` + + `})));\n`, + }, + { + name: _('/wildcard_reexports_with_require.js'), + contents: `(function (global, factory) {\n` + + ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(require, exports) :\n` + + ` typeof define === 'function' && define.amd ? define('wildcard_reexports_with_require', ['require', 'exports'], factory);\n` + + `}(this, (function (require, exports) { 'use strict';\n` + + ` function __export(m) {\n` + + ` for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];\n` + + ` }\n` + + ` var b_module = require('./b_module');\n` + + ` __export(b_module);\n` + + ` __export(require('./xtra_module'));\n` + + `})));\n`, + }, + { + name: _('/define_property_reexports.js'), + contents: `(function (global, factory) {\n` + + ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(require, exports) :\n` + + ` typeof define === 'function' && define.amd ? define('define_property_reexports', ['require', 'exports'], factory);\n` + + `}(this, (function (require, exports) { 'use strict';\n` + + `var moduleA = require("./a_module");\n` + + `Object.defineProperty(exports, "newA", { enumerable: true, get: function () { return moduleA.a; } });\n` + + `})));`, + }, + { + name: _('/decorated_class.js'), + contents: `(function (global, factory) {\n` + + ` typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n` + + ` typeof define === 'function' && define.amd ? define('decorated_class', ['exports'], factory) :\n` + + ` (factory(global.decorated_class));\n` + + `}(this, (function (exports) { 'use strict';\n` + + `var DecoratedClass_1;\n` + + `\n` + + `exports.DecoratedClass = DecoratedClass_1 = (function() {\n` + + ` function DecoratedClass() {\n` + + ` }\n` + + ` return DecoratedClass;\n` + + `}());\n` + + `\n` + + `exports.DecoratedClass = DecoratedClass_1 = __decorate([\n` + + ` SomeDecorator([ej2AngularBase.ComponentBase, ej2AngularBase.FormBase]),\n` + + ` __metadata("design:paramtypes", [core.ElementRef,\n` + + ` core.Renderer2,\n` + + ` core.ViewContainerRef,\n` + + ` core.Injector])\n` + + `], exports.DecoratedClass);\n` + + `})));\n`, + } + ]; - FUNCTION_BODY_FILE = { - name: _('/function_body.js'), - contents: ` + FUNCTION_BODY_FILE = { + name: _('/function_body.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('function_body', ['exports'], factory) : @@ -746,67 +821,67 @@ runInEachFileSystem(() => { return x; } })));` - }; + }; - DECORATED_FILES = [ - { - name: _('/primary.js'), - contents: ` + DECORATED_FILES = [ + { + name: _('/primary.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('./secondary')) : typeof define === 'function' && define.amd ? define('primary', ['exports', '@angular/core', './secondary'], factory) : (factory(global.primary,global.ng.core, global.secondary)); }(this, (function (exports,core,secondary) { 'use strict'; - var A = (function() { + ${expDecl('A')} = (function() { function A() {} A.decorators = [ { type: core.Directive, args: [{ selector: '[a]' }] } ]; return A; }()); - var B = (function() { + var B = (function() { function B() {} B.decorators = [ { type: core.Directive, args: [{ selector: '[b]' }] } ]; return B; }()); - function x() {} - function y() {} - var C = (function() { + function x() {} + function y() {} + ${expDecl('C')} = (function() { function C() {} return C; }); - exports.A = A; + ${varExp('A')} exports.x = x; - exports.C = C; + ${varExp('C')} })));` - }, - { - name: _('/secondary.js'), - contents: ` + }, + { + name: _('/secondary.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('primary', ['exports', '@angular/core'], factory) : (factory(global.primary,global.ng.core)); }(this, (function (exports,core) { 'use strict'; - var D = (function() { + ${expDecl('D')} = (function() { function D() {} D.decorators = [ { type: core.Directive, args: [{ selector: '[d]' }] } ]; return D; }()); - exports.D = D; + ${varExp('D')} }))); ` - } - ]; + } + ]; - TYPINGS_SRC_FILES = [ - { - name: _('/ep/src/index.js'), - contents: ` + TYPINGS_SRC_FILES = [ + { + name: _('/ep/src/index.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./internal'), require('./class1'), require('./class2'), require('./missing-class'), require('./flat-file'), require('./func1')) : typeof define === 'function' && define.amd ? define('index', ['exports', './internal', './class1', './class2', './missing-class', './flat-file', './func1'], factory) : @@ -820,98 +895,111 @@ runInEachFileSystem(() => { __export(class2); }))); ` - }, - { - name: _('/ep/src/class1.js'), - contents: ` + }, + { + name: _('/ep/src/class1.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('class1', ['exports'], factory) : (factory(global.class1)); }(this, (function (exports) { 'use strict'; - var Class1 = (function() { + ${expDecl('Class1')} = (function() { function Class1() {} return Class1; }()); - var MissingClass1 = (function() { + ${expDecl('MissingClass1')} = (function() { function MissingClass1() {} return MissingClass1; }()); - exports.Class1 = Class1; - exports.MissingClass1 = MissingClass1; + ${varExp('Class1')} + ${varExp('MissingClass1')} }))); ` - }, - { - name: _('/ep/src/class2.js'), - contents: ` + }, + { + name: _('/ep/src/class2.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('class2', ['exports'], factory) : (factory(global.class2)); }(this, (function (exports) { 'use strict'; - var Class2 = (function() { + ${expDecl('Class2')} = (function() { function Class2() {} return Class2; }()); - exports.Class2 = Class2; + ${varExp('Class2')} }))); ` - }, - {name: _('/ep/src/func1.js'), contents: 'function mooFn() {} export {mooFn}'}, { - name: _('/ep/src/internal.js'), - contents: ` + }, + { + name: _('/ep/src/func1.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('internal', ['exports'], factory) : (factory(global.internal)); }(this, (function (exports) { 'use strict'; - var InternalClass = (function() { + function mooFn() {} + exports.mooFn = mooFn; + }))); + ` + }, + { + name: _('/ep/src/internal.js'), + contents: ` + (function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define('internal', ['exports'], factory) : + (factory(global.internal)); + }(this, (function (exports) { 'use strict'; + ${expDecl('InternalClass')} = (function() { function InternalClass() {} return InternalClass; }()); - var Class2 = (function() { + ${expDecl('Class2')} = (function() { function Class2() {} return Class2; }()); - exports.InternalClass =InternalClass; - exports.Class2 = Class2; + ${varExp('InternalClass')} + ${varExp('Class2')} }))); ` - }, - { - name: _('/ep/src/missing-class.js'), - contents: ` + }, + { + name: _('/ep/src/missing-class.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('missingClass', ['exports'], factory) : (factory(global.missingClass)); }(this, (function (exports) { 'use strict'; - var MissingClass2 = (function() { + ${expDecl('MissingClass2')} = (function() { function MissingClass2() {} return MissingClass2; }()); exports. MissingClass2 = MissingClass2; }))); ` - }, - { - name: _('/ep/src/flat-file.js'), - contents: ` + }, + { + name: _('/ep/src/flat-file.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('missingClass', ['exports'], factory) : (factory(global.missingClass)); }(this, (function (exports) { 'use strict'; - var Class1 = (function() { + ${expDecl('Class1')} = (function() { function Class1() {} return Class1; }()); - var MissingClass1 = (function() { + ${expDecl('MissingClass1')} = (function() { function MissingClass1() {} return MissingClass1; }()); - var MissingClass2 = (function() { + ${expDecl('MissingClass2')} = (function() { function MissingClass2() {} return MissingClass2; }()); @@ -919,157 +1007,380 @@ runInEachFileSystem(() => { function SourceClass() {} return SourceClass; }()); - exports.Class1 = Class1; + ${varExp('Class1')} exports.AliasedClass = SourceClass; - exports.MissingClass1 = MissingClass1; - exports.MissingClass2 = MissingClass2; + ${varExp('MissingClass1')} + ${varExp('MissingClass2')} }))); ` - } - ]; + } + ]; - TYPINGS_DTS_FILES = [ - { - name: _('/ep/typings/index.d.ts'), - contents: ` + TYPINGS_DTS_FILES = [ + { + name: _('/ep/typings/index.d.ts'), + contents: ` import '../../an_external_lib/index'; import {InternalClass} from './internal'; import {mooFn} from './func1'; export * from './class1'; export * from './class2'; ` - }, - { - name: _('/ep/typings/class1.d.ts'), - contents: `export declare class Class1 {}\nexport declare class OtherClass {}` - }, - { - name: _('/ep/typings/class2.d.ts'), - contents: ` + }, + { + name: _('/ep/typings/class1.d.ts'), + contents: `export declare class Class1 {}\nexport declare class OtherClass {}` + }, + { + name: _('/ep/typings/class2.d.ts'), + contents: ` export declare class Class2 {} export declare interface SomeInterface {} export {TypingsClass as AliasedClass} from './typings-class'; ` - }, - {name: _('/ep/typings/func1.d.ts'), contents: 'export declare function mooFn(): void;'}, - { - name: _('/ep/typings/internal.d.ts'), - contents: `export declare class InternalClass {}\nexport declare class Class2 {}` - }, - { - name: _('/ep/typings/typings-class.d.ts'), - contents: `export declare class TypingsClass {}` - }, - {name: _('/ep/typings/shadow-class.d.ts'), contents: `export declare class ShadowClass {}`}, - {name: _('/an_external_lib/index.d.ts'), contents: 'export declare class ShadowClass {}'}, - ]; - }); - - describe('getDecoratorsOfDeclaration()', () => { - it('should find the decorators on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedFunctionDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); - - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); + }, + {name: _('/ep/typings/func1.d.ts'), contents: 'export declare function mooFn(): void;'}, + { + name: _('/ep/typings/internal.d.ts'), + contents: `export declare class InternalClass {}\nexport declare class Class2 {}` + }, + { + name: _('/ep/typings/typings-class.d.ts'), + contents: `export declare class TypingsClass {}` + }, + { + name: _('/ep/typings/shadow-class.d.ts'), + contents: `export declare class ShadowClass {}` + }, + {name: _('/an_external_lib/index.d.ts'), contents: 'export declare class ShadowClass {}'}, + ]; }); - it('should find the decorators on a class at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; + describe('getDecoratorsOfDeclaration()', () => { + it('should find the decorators on a class', () => { + loadTestFiles([SOME_DIRECTIVE_FILE]); + const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode)!; - expect(decorators).toBeDefined(); - expect(decorators.length).toEqual(1); + expect(decorators).toBeDefined(); + expect(decorators.length).toEqual(1); - const decorator = decorators[0]; - expect(decorator.name).toEqual('Directive'); - expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); - expect(decorator.args!.map(arg => arg.getText())).toEqual([ - '{ selector: \'[someDirective]\' }', - ]); + const decorator = decorators[0]; + expect(decorator.name).toEqual('Directive'); + expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); + expect(decorator.args!.map(arg => arg.getText())).toEqual([ + '{ selector: \'[someDirective]\' }', + ]); + }); + + it('should find the decorators on a class at the top level', () => { + loadTestFiles([TOPLEVEL_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', isDesiredDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode)!; + + expect(decorators).toBeDefined(); + expect(decorators.length).toEqual(1); + + const decorator = decorators[0]; + expect(decorator.name).toEqual('Directive'); + expect(decorator.import).toEqual({name: 'Directive', from: '@angular/core'}); + expect(decorator.args!.map(arg => arg.getText())).toEqual([ + '{ selector: \'[someDirective]\' }', + ]); + }); + + it('should return null if the symbol is not a class', () => { + loadTestFiles([FOO_FUNCTION_FILE]); + const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const functionNode = getDeclaration( + bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); + const decorators = host.getDecoratorsOfDeclaration(functionNode); + expect(decorators).toBe(null); + }); + + it('should return null if there are no decorators', () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode); + expect(decorators).toBe(null); + }); + + it('should ignore `decorators` if it is not an array literal', () => { + loadTestFiles([INVALID_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_DECORATORS_FILE.name, 'NotArrayLiteral', + isDesiredDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode); + expect(decorators).toEqual([]); + }); + + it('should ignore decorator elements that are not object literals', () => { + loadTestFiles([INVALID_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_DECORATORS_FILE.name, 'NotObjectLiteral', + isDesiredDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode)!; + + expect(decorators.length).toBe(1); + expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); + }); + + it('should ignore decorator elements that have no `type` property', () => { + loadTestFiles([INVALID_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_DECORATORS_FILE.name, 'NoTypeProperty', isDesiredDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode)!; + + expect(decorators.length).toBe(1); + expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); + }); + + it('should ignore decorator elements whose `type` value is not an identifier', () => { + loadTestFiles([INVALID_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_DECORATORS_FILE.name, 'NotIdentifier', isDesiredDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode)!; + + expect(decorators.length).toBe(1); + expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); + }); + + it('should have import information on decorators', () => { + loadTestFiles([SOME_DIRECTIVE_FILE]); + const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + + const classNode = getDeclaration( + bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode)!; + + expect(decorators.length).toEqual(1); + expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); + }); + + it('should find decorated members on a class at the top level', () => { + loadTestFiles([TOPLEVEL_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + + const input1 = members.find(member => member.name === 'input1')!; + expect(input1.kind).toEqual(ClassMemberKind.Property); + expect(input1.isStatic).toEqual(false); + expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); + + const input2 = members.find(member => member.name === 'input2')!; + expect(input2.kind).toEqual(ClassMemberKind.Property); + expect(input2.isStatic).toEqual(false); + expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); + }); + + describe('(returned decorators `args`)', () => { + it('should be an empty array if decorator has no `args` property', () => { + loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); + const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', + isDesiredDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode)!; + + expect(decorators.length).toBe(1); + expect(decorators[0].name).toBe('Directive'); + expect(decorators[0].args).toEqual([]); + }); + + it('should be an empty array if decorator\'s `args` has no property assignment', () => { + loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); + const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', + isDesiredDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode)!; + + expect(decorators.length).toBe(1); + expect(decorators[0].name).toBe('Directive'); + expect(decorators[0].args).toEqual([]); + }); + + it('should be an empty array if `args` property value is not an array literal', () => { + loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); + const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', + isDesiredDeclaration); + const decorators = host.getDecoratorsOfDeclaration(classNode)!; + + expect(decorators.length).toBe(1); + expect(decorators[0].name).toBe('Directive'); + expect(decorators[0].args).toEqual([]); + }); + }); }); - it('should return null if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - const decorators = host.getDecoratorsOfDeclaration(functionNode); - expect(decorators).toBe(null); + describe('getMembersOfClass()', () => { + it('should find decorated members on a class', () => { + loadTestFiles([SOME_DIRECTIVE_FILE]); + const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + + const input1 = members.find(member => member.name === 'input1')!; + expect(input1.kind).toEqual(ClassMemberKind.Property); + expect(input1.isStatic).toEqual(false); + expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); + + const input2 = members.find(member => member.name === 'input2')!; + expect(input2.kind).toEqual(ClassMemberKind.Property); + expect(input2.isStatic).toEqual(false); + expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); + }); + + it('should find non decorated properties on a class', () => { + loadTestFiles([SOME_DIRECTIVE_FILE]); + const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + + const instanceProperty = members.find(member => member.name === 'instanceProperty')!; + expect(instanceProperty.kind).toEqual(ClassMemberKind.Property); + expect(instanceProperty.isStatic).toEqual(false); + expect(ts.isBinaryExpression(instanceProperty.implementation!)).toEqual(true); + expect(instanceProperty.value!.getText()).toEqual(`'instance'`); + }); + + it('should find static methods on a class', () => { + loadTestFiles([SOME_DIRECTIVE_FILE]); + const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + + const staticMethod = members.find(member => member.name === 'staticMethod')!; + expect(staticMethod.kind).toEqual(ClassMemberKind.Method); + expect(staticMethod.isStatic).toEqual(true); + expect(ts.isFunctionExpression(staticMethod.implementation!)).toEqual(true); + }); + + it('should find static properties on a class', () => { + loadTestFiles([SOME_DIRECTIVE_FILE]); + const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + + const staticProperty = members.find(member => member.name === 'staticProperty')!; + expect(staticProperty.kind).toEqual(ClassMemberKind.Property); + expect(staticProperty.isStatic).toEqual(true); + expect(ts.isPropertyAccessExpression(staticProperty.implementation!)).toEqual(true); + expect(staticProperty.value!.getText()).toEqual(`'static'`); + }); + + it('should throw if the symbol is not a class', () => { + loadTestFiles([FOO_FUNCTION_FILE]); + const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const functionNode = getDeclaration( + bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); + expect(() => { + host.getMembersOfClass(functionNode); + }).toThrowError(`Attempted to get members of a non-class: "function foo() {}"`); + }); + + it('should return an empty array if there are no prop decorators', () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + + expect(members).toEqual([]); + }); + + it('should not process decorated properties in `propDecorators` if it is not an object literal', + () => { + loadTestFiles([INVALID_PROP_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteral', + isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + + expect(members.map(member => member.name)).not.toContain('prop'); + }); + + it('should ignore prop decorator elements that are not object literals', () => { + loadTestFiles([INVALID_PROP_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteralProp', + isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + const prop = members.find(m => m.name === 'prop')!; + const decorators = prop.decorators!; + + expect(decorators.length).toBe(1); + expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); + }); + + it('should ignore prop decorator elements that have no `type` property', () => { + loadTestFiles([INVALID_PROP_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NoTypeProperty', + isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + const prop = members.find(m => m.name === 'prop')!; + const decorators = prop.decorators!; + + expect(decorators.length).toBe(1); + expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); + }); }); - it('should return null if there are no decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + it('should ignore prop decorator elements whose `type` value is not an identifier', () => { + loadTestFiles([INVALID_PROP_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode); - expect(decorators).toBe(null); - }); - - it('should ignore `decorators` if it is not an array literal', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode); - expect(decorators).toEqual([]); - }); - - it('should ignore decorator elements that are not object literals', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATORS_FILE.name, 'NotIdentifier', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; + bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotIdentifier', + isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + const prop = members.find(m => m.name === 'prop')!; + const decorators = prop.decorators!; expect(decorators.length).toBe(1); expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); @@ -1081,306 +1392,85 @@ runInEachFileSystem(() => { const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedFunctionDeclaration); + bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); const decorators = host.getDecoratorsOfDeclaration(classNode)!; expect(decorators.length).toEqual(1); expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); }); - it('should find decorated members on a class at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - describe('(returned decorators `args`)', () => { - it('should be an empty array if decorator has no `args` property', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); + describe('(returned prop decorators `args`)', () => { + it('should be an empty array if prop decorator has no `args` property', () => { + loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); + const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; + bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', + isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + const prop = members.find(m => m.name === 'prop')!; + const decorators = prop.decorators!; expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); + expect(decorators[0].name).toBe('Input'); expect(decorators[0].args).toEqual([]); }); - it('should be an empty array if decorator\'s `args` has no property assignment', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; + it('should be an empty array if prop decorator\'s `args` has no property assignment', + () => { + loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); + const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', + isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + const prop = members.find(m => m.name === 'prop')!; + const decorators = prop.decorators!; - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); - expect(decorators[0].args).toEqual([]); - }); + expect(decorators.length).toBe(1); + expect(decorators[0].name).toBe('Input'); + expect(decorators[0].args).toEqual([]); + }); it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_DECORATOR_ARGS_FILE.name); + loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); + const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); const classNode = getDeclaration( - bundle.program, INVALID_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; + bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', + isDesiredDeclaration); + const members = host.getMembersOfClass(classNode); + const prop = members.find(m => m.name === 'prop')!; + const decorators = prop.decorators!; expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Directive'); + expect(decorators[0].name).toBe('Input'); expect(decorators[0].args).toEqual([]); }); }); - }); - describe('getMembersOfClass()', () => { - it('should find decorated members on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedFunctionDeclaration); - const members = host.getMembersOfClass(classNode); - - const input1 = members.find(member => member.name === 'input1')!; - expect(input1.kind).toEqual(ClassMemberKind.Property); - expect(input1.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - - const input2 = members.find(member => member.name === 'input2')!; - expect(input2.kind).toEqual(ClassMemberKind.Property); - expect(input2.isStatic).toEqual(false); - expect(input1.decorators!.map(d => d.name)).toEqual(['Input']); - }); - - it('should find non decorated properties on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedFunctionDeclaration); - const members = host.getMembersOfClass(classNode); - - const instanceProperty = members.find(member => member.name === 'instanceProperty')!; - expect(instanceProperty.kind).toEqual(ClassMemberKind.Property); - expect(instanceProperty.isStatic).toEqual(false); - expect(ts.isBinaryExpression(instanceProperty.implementation!)).toEqual(true); - expect(instanceProperty.value!.getText()).toEqual(`'instance'`); - }); - - it('should find static methods on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedFunctionDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticMethod = members.find(member => member.name === 'staticMethod')!; - expect(staticMethod.kind).toEqual(ClassMemberKind.Method); - expect(staticMethod.isStatic).toEqual(true); - expect(ts.isFunctionExpression(staticMethod.implementation!)).toEqual(true); - }); - - it('should find static properties on a class', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedFunctionDeclaration); - const members = host.getMembersOfClass(classNode); - - const staticProperty = members.find(member => member.name === 'staticProperty')!; - expect(staticProperty.kind).toEqual(ClassMemberKind.Property); - expect(staticProperty.isStatic).toEqual(true); - expect(ts.isPropertyAccessExpression(staticProperty.implementation!)).toEqual(true); - expect(staticProperty.value!.getText()).toEqual(`'static'`); - }); - - it('should throw if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(() => { - host.getMembersOfClass(functionNode); - }).toThrowError(`Attempted to get members of a non-class: "function foo() {}"`); - }); - - it('should return an empty array if there are no prop decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(members).toEqual([]); - }); - - it('should not process decorated properties in `propDecorators` if it is not an object literal', - () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(members.map(member => member.name)).not.toContain('prop'); - }); - - it('should ignore prop decorator elements that are not object literals', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteralProp', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should ignore prop decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - }); - - it('should ignore prop decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_PROP_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATORS_FILE.name, 'NotIdentifier', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); - }); - - it('should have import information on decorators', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedFunctionDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode)!; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); - }); - - describe('(returned prop decorators `args`)', () => { - it('should be an empty array if prop decorator has no `args` property', () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if prop decorator\'s `args` has no property assignment', () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_PROP_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - const prop = members.find(m => m.name === 'prop')!; - const decorators = prop.decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Input'); - expect(decorators[0].args).toEqual([]); - }); - }); - - describe('getConstructorParameters', () => { - it('should retain imported name for type value references for decorated constructor parameter types', - () => { - const files = [ - { - name: _('/node_modules/shared-lib/foo.d.ts'), - contents: ` + describe('getConstructorParameters', () => { + it('should retain imported name for type value references for decorated constructor parameter types', + () => { + const files = [ + { + name: _('/node_modules/shared-lib/foo.d.ts'), + contents: ` declare class Foo {} export {Foo as Bar}; `, - }, - { - name: _('/node_modules/shared-lib/index.d.ts'), - contents: ` + }, + { + name: _('/node_modules/shared-lib/index.d.ts'), + contents: ` export {Bar as Baz} from './foo'; `, - }, - { - name: _('/local.js'), - contents: ` + }, + { + name: _('/local.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('local', ['exports'], factory) : @@ -1394,295 +1484,299 @@ runInEachFileSystem(() => { exports.External = Internal; }))); ` - }, - { - name: _('/main.js'), - contents: ` + }, + { + name: _('/main.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('shared-lib'), require('./local')) : typeof define === 'function' && define.amd ? define('main', ['exports', 'shared-lib', './local'], factory) : (factory(global.main, global.shared, global.local)); }(this, (function (exports, shared, local) { 'use strict'; - var SameFile = (function() { + ${expDecl('SameFile')} = (function() { function SameFile() { } return SameFile; }()); - exports.SameFile = SameFile; + ${varExp('SameFile')} - var SomeClass = (function() { + ${expDecl('SomeClass')} = (function() { function SomeClass(arg1, arg2, arg3) {} return SomeClass; }()); - SomeClass.ctorParameters = function() { return [{ type: shared.Baz }, { type: local.External }, { type: SameFile }]; }; - exports.SomeClass = SomeClass; + ${ + expDecl( + 'SomeClass', + true)}.ctorParameters = function() { return [{ type: shared.Baz }, { type: local.External }, { type: SameFile }]; }; + ${varExp('SomeClass')} }))); `, - }, - ]; + }, + ]; - loadTestFiles(files); - const bundle = makeTestBundleProgram(_('/main.js')); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, _('/main.js'), 'SomeClass', isNamedFunctionDeclaration); + loadTestFiles(files); + const bundle = makeTestBundleProgram(_('/main.js')); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, _('/main.js'), 'SomeClass', isNamedFunctionDeclaration); - const parameters = host.getConstructorParameters(classNode)!; + const parameters = host.getConstructorParameters(classNode)!; - expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']); - expectTypeValueReferencesForParameters( - parameters, ['Baz', 'External', 'SameFile'], ['shared-lib', './local', null]); - }); + expect(parameters.map(p => p.name)).toEqual(['arg1', 'arg2', 'arg3']); + expectTypeValueReferencesForParameters( + parameters, ['Baz', 'External', 'SameFile'], ['shared-lib', './local', null]); + }); - it('should find the decorated constructor parameters', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedFunctionDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - }); - - it('should find the decorated constructor parameters at the top level', () => { - loadTestFiles([TOPLEVEL_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toBeDefined(); - expect(parameters!.map(parameter => parameter.name)).toEqual([ - '_viewContainer', '_template', 'injected' - ]); - expectTypeValueReferencesForParameters(parameters!, [ - 'ViewContainerRef', - 'TemplateRef', - null, - ]); - }); - - it('should accept `ctorParameters` as an array', () => { - loadTestFiles([CTOR_DECORATORS_ARRAY_FILE]); - const bundle = makeTestBundleProgram(CTOR_DECORATORS_ARRAY_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, CTOR_DECORATORS_ARRAY_FILE.name, 'CtorDecoratedAsArray', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode)!; - - expect(parameters).toBeDefined(); - expect(parameters.map(parameter => parameter.name)).toEqual(['arg1']); - expectTypeValueReferencesForParameters(parameters, ['ParamType']); - }); - - it('should throw if the symbol is not a class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const functionNode = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(() => { - host.getConstructorParameters(functionNode); - }) - .toThrowError( - 'Attempted to get constructor parameters of a non-class: "function foo() {}"'); - }); - - // In ES5 there is no such thing as a constructor-less class - // it('should return `null` if there is no constructor', () => { }); - - it('should return an array even if there are no decorators', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'NoDecoratorConstructorClass', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toEqual(jasmine.any(Array)); - expect(parameters!.length).toEqual(1); - expect(parameters![0].name).toEqual('foo'); - expect(parameters![0].decorators).toBe(null); - }); - - it('should return an empty array if there are no constructor parameters', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoParameters', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters).toEqual([]); - }); - - // In ES5 there are no arrow functions - // it('should ignore `ctorParameters` if it is an arrow function', () => { }); - - it('should ignore `ctorParameters` if it does not return an array literal', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters!.length).toBe(1); - expect(parameters![0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: null, - })); - }); - - describe('(returned parameters `decorators`)', () => { - it('should ignore param decorator elements that are not object literals', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotObjectLiteral', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - - expect(parameters!.length).toBe(2); - expect(parameters![0]).toEqual(jasmine.objectContaining({ - name: 'arg1', - decorators: null, - })); - expect(parameters![1]).toEqual(jasmine.objectContaining({ - name: 'arg2', - decorators: jasmine.any(Array) as any - })); - }); - - it('should ignore param decorator elements that have no `type` property', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoTypeProperty', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); - }); - - it('should ignore param decorator elements whose `type` value is not an identifier', () => { - loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotIdentifier', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); - }); - - it('should use `getImportOfIdentifier()` to retrieve import info', () => { + it('should find the decorated constructor parameters', () => { loadTestFiles([SOME_DIRECTIVE_FILE]); const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', - isNamedFunctionDeclaration); - const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'}; - const spy = spyOn(UmdReflectionHost.prototype, 'getImportOfIdentifier') - .and.returnValue(mockImportInfo); - + bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![2].decorators!; - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); + expect(parameters).toBeDefined(); + expect(parameters!.map(parameter => parameter.name)).toEqual([ + '_viewContainer', '_template', 'injected' + ]); + expectTypeValueReferencesForParameters(parameters!, [ + 'ViewContainerRef', + 'TemplateRef', + null, + ]); }); - }); - describe('(returned parameters `decorators.args`)', () => { - it('should be an empty array if param decorator has no `args` property', () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); + it('should find the decorated constructor parameters at the top level', () => { + loadTestFiles([TOPLEVEL_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(TOPLEVEL_DECORATORS_FILE.name); const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', - isNamedVariableDeclaration); + bundle.program, TOPLEVEL_DECORATORS_FILE.name, 'SomeDirective', isDesiredDeclaration); const parameters = host.getConstructorParameters(classNode); + + expect(parameters).toBeDefined(); + expect(parameters!.map(parameter => parameter.name)).toEqual([ + '_viewContainer', '_template', 'injected' + ]); + expectTypeValueReferencesForParameters(parameters!, [ + 'ViewContainerRef', + 'TemplateRef', + null, + ]); + }); + + it('should accept `ctorParameters` as an array', () => { + loadTestFiles([CTOR_DECORATORS_ARRAY_FILE]); + const bundle = makeTestBundleProgram(CTOR_DECORATORS_ARRAY_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, CTOR_DECORATORS_ARRAY_FILE.name, 'CtorDecoratedAsArray', + isDesiredDeclaration); + const parameters = host.getConstructorParameters(classNode)!; + + expect(parameters).toBeDefined(); + expect(parameters.map(parameter => parameter.name)).toEqual(['arg1']); + expectTypeValueReferencesForParameters(parameters, ['ParamType']); + }); + + it('should throw if the symbol is not a class', () => { + loadTestFiles([FOO_FUNCTION_FILE]); + const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const functionNode = getDeclaration( + bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); + expect(() => { + host.getConstructorParameters(functionNode); + }) + .toThrowError( + 'Attempted to get constructor parameters of a non-class: "function foo() {}"'); + }); + + // In ES5 there is no such thing as a constructor-less class + // it('should return `null` if there is no constructor', () => { }); + + it('should return an array even if there are no decorators', () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'NoDecoratorConstructorClass', + isDesiredDeclaration); + const parameters = host.getConstructorParameters(classNode); + + expect(parameters).toEqual(jasmine.any(Array)); + expect(parameters!.length).toEqual(1); + expect(parameters![0].name).toEqual('foo'); + expect(parameters![0].decorators).toBe(null); + }); + + it('should return an empty array if there are no constructor parameters', () => { + loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoParameters', + isDesiredDeclaration); + const parameters = host.getConstructorParameters(classNode); + + expect(parameters).toEqual([]); + }); + + // In ES5 there are no arrow functions + // it('should ignore `ctorParameters` if it is an arrow function', () => { }); + + it('should ignore `ctorParameters` if it does not return an array literal', () => { + loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrayLiteral', + isDesiredDeclaration); + const parameters = host.getConstructorParameters(classNode); + expect(parameters!.length).toBe(1); - const decorators = parameters![0].decorators!; - - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); + expect(parameters![0]).toEqual(jasmine.objectContaining({ + name: 'arg1', + decorators: null, + })); }); - it('should be an empty array if param decorator\'s `args` has no property assignment', - () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; + describe('(returned parameters `decorators`)', () => { + it('should ignore param decorator elements that are not object literals', () => { + loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotObjectLiteral', + isDesiredDeclaration); + const parameters = host.getConstructorParameters(classNode); - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); - }); + expect(parameters!.length).toBe(2); + expect(parameters![0]).toEqual(jasmine.objectContaining({ + name: 'arg1', + decorators: null, + })); + expect(parameters![1]).toEqual(jasmine.objectContaining({ + name: 'arg2', + decorators: jasmine.any(Array) as any + })); + }); - it('should be an empty array if `args` property value is not an array literal', () => { - loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); - const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', - isNamedVariableDeclaration); - const parameters = host.getConstructorParameters(classNode); - const decorators = parameters![0].decorators!; + it('should ignore param decorator elements that have no `type` property', () => { + loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NoTypeProperty', + isDesiredDeclaration); + const parameters = host.getConstructorParameters(classNode); + const decorators = parameters![0].decorators!; - expect(decorators.length).toBe(1); - expect(decorators[0].name).toBe('Inject'); - expect(decorators[0].args).toEqual([]); + expect(decorators.length).toBe(1); + expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); + }); + + it('should ignore param decorator elements whose `type` value is not an identifier', + () => { + loadTestFiles([INVALID_CTOR_DECORATORS_FILE]); + const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATORS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_CTOR_DECORATORS_FILE.name, 'NotIdentifier', + isDesiredDeclaration); + const parameters = host.getConstructorParameters(classNode); + const decorators = parameters![0].decorators!; + + expect(decorators.length).toBe(1); + expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); + }); + + it('should use `getImportOfIdentifier()` to retrieve import info', () => { + loadTestFiles([SOME_DIRECTIVE_FILE]); + const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); + const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'}; + const spy = spyOn(UmdReflectionHost.prototype, 'getImportOfIdentifier') + .and.returnValue(mockImportInfo); + + const parameters = host.getConstructorParameters(classNode); + const decorators = parameters![2].decorators!; + + expect(decorators.length).toEqual(1); + expect(decorators[0].import).toBe(mockImportInfo); + }); }); - }); - function getConstructorParameters( - constructor: string, mode: 'inlined'|'inlined_with_suffix'|'imported' = 'imported') { - let fileHeaderWithUmd = ''; + describe('(returned parameters `decorators.args`)', () => { + it('should be an empty array if param decorator has no `args` property', () => { + loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); + const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', + isDesiredDeclaration); + const parameters = host.getConstructorParameters(classNode); + expect(parameters!.length).toBe(1); + const decorators = parameters![0].decorators!; - switch (mode) { - case 'imported': - fileHeaderWithUmd = ` + expect(decorators.length).toBe(1); + expect(decorators[0].name).toBe('Inject'); + expect(decorators[0].args).toEqual([]); + }); + + it('should be an empty array if param decorator\'s `args` has no property assignment', + () => { + loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); + const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', + isDesiredDeclaration); + const parameters = host.getConstructorParameters(classNode); + const decorators = parameters![0].decorators!; + + expect(decorators.length).toBe(1); + expect(decorators[0].name).toBe('Inject'); + expect(decorators[0].args).toEqual([]); + }); + + it('should be an empty array if `args` property value is not an array literal', () => { + loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]); + const bundle = makeTestBundleProgram(INVALID_CTOR_DECORATOR_ARGS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', + isDesiredDeclaration); + const parameters = host.getConstructorParameters(classNode); + const decorators = parameters![0].decorators!; + + expect(decorators.length).toBe(1); + expect(decorators[0].name).toBe('Inject'); + expect(decorators[0].args).toEqual([]); + }); + }); + + function getConstructorParameters( + constructor: string, mode: 'inlined'|'inlined_with_suffix'|'imported' = 'imported') { + let fileHeaderWithUmd = ''; + + switch (mode) { + case 'imported': + fileHeaderWithUmd = ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib'))) : typeof define === 'function' && define.amd ? define('test', ['exports', 'tslib'], factory) : (factory(global.test, global.tslib)); }(this, (function (exports, tslib) { 'use strict'; `; - break; - case 'inlined': - fileHeaderWithUmd = ` + break; + case 'inlined': + fileHeaderWithUmd = ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports)) : typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : @@ -1691,9 +1785,9 @@ runInEachFileSystem(() => { var __spread = (this && this.__spread) || function (...args) { /* ... */ } `; - break; - case 'inlined_with_suffix': - fileHeaderWithUmd = ` + break; + case 'inlined_with_suffix': + fileHeaderWithUmd = ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports)) : typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : @@ -1702,12 +1796,12 @@ runInEachFileSystem(() => { var __spread$1 = (this && this.__spread$1) || function (...args) { /* ... */ } `; - break; - } + break; + } - const file = { - name: _('/synthesized_constructors.js'), - contents: ` + const file = { + name: _('/synthesized_constructors.js'), + contents: ` ${fileHeaderWithUmd} var TestClass = /** @class */ (function (_super) { __extends(TestClass, _super); @@ -1718,19 +1812,19 @@ runInEachFileSystem(() => { exports.TestClass = TestClass; }))); `, - }; + }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - return host.getConstructorParameters(classNode); - } + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = + getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); + return host.getConstructorParameters(classNode); + } - describe('TS -> ES5: synthesized constructors', () => { - it('recognizes _this assignment from super call', () => { - const parameters = getConstructorParameters(` + describe('TS -> ES5: synthesized constructors', () => { + it('recognizes _this assignment from super call', () => { + const parameters = getConstructorParameters(` function TestClass() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.synthesizedProperty = null; @@ -1738,21 +1832,21 @@ runInEachFileSystem(() => { } `); - expect(parameters).toBeNull(); - }); + expect(parameters).toBeNull(); + }); - it('recognizes super call as return statement', () => { - const parameters = getConstructorParameters(` + it('recognizes super call as return statement', () => { + const parameters = getConstructorParameters(` function TestClass() { return _super !== null && _super.apply(this, arguments) || this; } `); - expect(parameters).toBeNull(); - }); + expect(parameters).toBeNull(); + }); - it('handles the case where a unique name was generated for _super or _this', () => { - const parameters = getConstructorParameters(` + it('handles the case where a unique name was generated for _super or _this', () => { + const parameters = getConstructorParameters(` function TestClass() { var _this_1 = _super_1 !== null && _super_1.apply(this, arguments) || this; _this_1._this = null; @@ -1761,79 +1855,43 @@ runInEachFileSystem(() => { } `); - expect(parameters).toBeNull(); - }); + expect(parameters).toBeNull(); + }); - it('does not consider constructors with parameters as synthesized', () => { - const parameters = getConstructorParameters(` + it('does not consider constructors with parameters as synthesized', () => { + const parameters = getConstructorParameters(` function TestClass(arg) { return _super !== null && _super.apply(this, arguments) || this; } `); - expect(parameters!.length).toBe(1); - }); + expect(parameters!.length).toBe(1); + }); - it('does not consider manual super calls as synthesized', () => { - const parameters = getConstructorParameters(` + it('does not consider manual super calls as synthesized', () => { + const parameters = getConstructorParameters(` function TestClass() { return _super.call(this) || this; } `); - expect(parameters!.length).toBe(0); + expect(parameters!.length).toBe(0); + }); + + it('does not consider empty constructors as synthesized', () => { + const parameters = getConstructorParameters(`function TestClass() {}`); + expect(parameters!.length).toBe(0); + }); }); - it('does not consider empty constructors as synthesized', () => { - const parameters = getConstructorParameters(`function TestClass() {}`); - expect(parameters!.length).toBe(0); - }); - }); - - // See: https://github.com/angular/angular/issues/38453. - describe('ES2015 -> ES5: synthesized constructors through TSC downleveling', () => { - it('recognizes delegate super call using inline spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spread(arguments)) || this; - }`, - 'inlined'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using inline spread helper with suffix', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, __spread$1(arguments)) || this; - }`, - 'inlined_with_suffix'); - - expect(parameters).toBeNull(); - }); - - it('recognizes delegate super call using imported spread helper', () => { - const parameters = getConstructorParameters( - ` - function TestClass() { - return _super.apply(this, tslib_1.__spread(arguments)) || this; - }`, - 'imported'); - - expect(parameters).toBeNull(); - }); - - describe('with class member assignment', () => { + // See: https://github.com/angular/angular/issues/38453. + describe('ES2015 -> ES5: synthesized constructors through TSC downleveling', () => { it('recognizes delegate super call using inline spread helper', () => { const parameters = getConstructorParameters( ` - function TestClass() { - var _this = _super.apply(this, __spread(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, + function TestClass() { + return _super.apply(this, __spread(arguments)) || this; + }`, 'inlined'); expect(parameters).toBeNull(); @@ -1842,11 +1900,9 @@ runInEachFileSystem(() => { it('recognizes delegate super call using inline spread helper with suffix', () => { const parameters = getConstructorParameters( ` - function TestClass() { - var _this = _super.apply(this, __spread$1(arguments)) || this; - _this.synthesizedProperty = null; - return _this; - }`, + function TestClass() { + return _super.apply(this, __spread$1(arguments)) || this; + }`, 'inlined_with_suffix'); expect(parameters).toBeNull(); @@ -1855,213 +1911,253 @@ runInEachFileSystem(() => { it('recognizes delegate super call using imported spread helper', () => { const parameters = getConstructorParameters( ` + function TestClass() { + return _super.apply(this, tslib_1.__spread(arguments)) || this; + }`, + 'imported'); + + expect(parameters).toBeNull(); + }); + + describe('with class member assignment', () => { + it('recognizes delegate super call using inline spread helper', () => { + const parameters = getConstructorParameters( + ` + function TestClass() { + var _this = _super.apply(this, __spread(arguments)) || this; + _this.synthesizedProperty = null; + return _this; + }`, + 'inlined'); + + expect(parameters).toBeNull(); + }); + + it('recognizes delegate super call using inline spread helper with suffix', () => { + const parameters = getConstructorParameters( + ` + function TestClass() { + var _this = _super.apply(this, __spread$1(arguments)) || this; + _this.synthesizedProperty = null; + return _this; + }`, + 'inlined_with_suffix'); + + expect(parameters).toBeNull(); + }); + + it('recognizes delegate super call using imported spread helper', () => { + const parameters = getConstructorParameters( + ` function TestClass() { var _this = _super.apply(this, tslib_1.__spread(arguments)) || this; _this.synthesizedProperty = null; return _this; }`, - 'imported'); + 'imported'); - expect(parameters).toBeNull(); + expect(parameters).toBeNull(); + }); }); - }); - it('handles the case where a unique name was generated for _super or _this', () => { - const parameters = getConstructorParameters( - ` + it('handles the case where a unique name was generated for _super or _this', () => { + const parameters = getConstructorParameters( + ` function TestClass() { var _this_1 = _super_1.apply(this, __spread(arguments)) || this; _this_1._this = null; _this_1._super = null; return _this_1; }`, - 'inlined'); + 'inlined'); - expect(parameters).toBeNull(); - }); + expect(parameters).toBeNull(); + }); - it('does not consider constructors with parameters as synthesized', () => { - const parameters = getConstructorParameters( - ` + it('does not consider constructors with parameters as synthesized', () => { + const parameters = getConstructorParameters( + ` function TestClass(arg) { return _super.apply(this, __spread(arguments)) || this; }`, - 'inlined'); + 'inlined'); - expect(parameters!.length).toBe(1); + expect(parameters!.length).toBe(1); + }); }); }); - }); - describe('getDefinitionOfFunction()', () => { - it('should return an object describing the function declaration passed as an argument', - () => { - loadTestFiles([FUNCTION_BODY_FILE]); - const bundle = makeTestBundleProgram(FUNCTION_BODY_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + describe('getDefinitionOfFunction()', () => { + it('should return an object describing the function declaration passed as an argument', + () => { + loadTestFiles([FUNCTION_BODY_FILE]); + const bundle = makeTestBundleProgram(FUNCTION_BODY_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const fooNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration)!; - const fooDef = host.getDefinitionOfFunction(fooNode)!; - expect(fooDef.node).toBe(fooNode); - expect(fooDef.body!.length).toEqual(1); - expect(fooDef.body![0].getText()).toEqual(`return x;`); - expect(fooDef.parameters.length).toEqual(1); - expect(fooDef.parameters[0].name).toEqual('x'); - expect(fooDef.parameters[0].initializer).toBe(null); + const fooNode = getDeclaration( + bundle.program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration)!; + const fooDef = host.getDefinitionOfFunction(fooNode)!; + expect(fooDef.node).toBe(fooNode); + expect(fooDef.body!.length).toEqual(1); + expect(fooDef.body![0].getText()).toEqual(`return x;`); + expect(fooDef.parameters.length).toEqual(1); + expect(fooDef.parameters[0].name).toEqual('x'); + expect(fooDef.parameters[0].initializer).toBe(null); - const barNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration)!; - const barDef = host.getDefinitionOfFunction(barNode)!; - expect(barDef.node).toBe(barNode); - expect(barDef.body!.length).toEqual(1); - expect(ts.isReturnStatement(barDef.body![0])).toBeTruthy(); - expect(barDef.body![0].getText()).toEqual(`return x + y;`); - expect(barDef.parameters.length).toEqual(2); - expect(barDef.parameters[0].name).toEqual('x'); - expect(fooDef.parameters[0].initializer).toBe(null); - expect(barDef.parameters[1].name).toEqual('y'); - expect(barDef.parameters[1].initializer!.getText()).toEqual('42'); + const barNode = getDeclaration( + bundle.program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration)!; + const barDef = host.getDefinitionOfFunction(barNode)!; + expect(barDef.node).toBe(barNode); + expect(barDef.body!.length).toEqual(1); + expect(ts.isReturnStatement(barDef.body![0])).toBeTruthy(); + expect(barDef.body![0].getText()).toEqual(`return x + y;`); + expect(barDef.parameters.length).toEqual(2); + expect(barDef.parameters[0].name).toEqual('x'); + expect(fooDef.parameters[0].initializer).toBe(null); + expect(barDef.parameters[1].name).toEqual('y'); + expect(barDef.parameters[1].initializer!.getText()).toEqual('42'); - const bazNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration)!; - const bazDef = host.getDefinitionOfFunction(bazNode)!; - expect(bazDef.node).toBe(bazNode); - expect(bazDef.body!.length).toEqual(3); - expect(bazDef.parameters.length).toEqual(1); - expect(bazDef.parameters[0].name).toEqual('x'); - expect(bazDef.parameters[0].initializer).toBe(null); + const bazNode = getDeclaration( + bundle.program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration)!; + const bazDef = host.getDefinitionOfFunction(bazNode)!; + expect(bazDef.node).toBe(bazNode); + expect(bazDef.body!.length).toEqual(3); + expect(bazDef.parameters.length).toEqual(1); + expect(bazDef.parameters[0].name).toEqual('x'); + expect(bazDef.parameters[0].initializer).toBe(null); - const quxNode = getDeclaration( - bundle.program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration)!; - const quxDef = host.getDefinitionOfFunction(quxNode)!; - expect(quxDef.node).toBe(quxNode); - expect(quxDef.body!.length).toEqual(2); - expect(quxDef.parameters.length).toEqual(1); - expect(quxDef.parameters[0].name).toEqual('x'); - expect(quxDef.parameters[0].initializer).toBe(null); - }); - }); - - describe('getImportOfIdentifier', () => { - it('should find the import of an identifier', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/file_b.js'), 'b', isNamedVariableDeclaration); - const identifier = - (variableNode.initializer && ts.isPropertyAccessExpression(variableNode.initializer) && - ts.isIdentifier(variableNode.initializer.name)) ? - variableNode.initializer.name : - null; - - expect(identifier).not.toBe(null); - const importOfIdent = host.getImportOfIdentifier(identifier!); - expect(importOfIdent).toEqual({name: 'a', from: './file_a'}); + const quxNode = getDeclaration( + bundle.program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration)!; + const quxDef = host.getDefinitionOfFunction(quxNode)!; + expect(quxDef.node).toBe(quxNode); + expect(quxDef.body!.length).toEqual(2); + expect(quxDef.parameters.length).toEqual(1); + expect(quxDef.parameters[0].name).toEqual('x'); + expect(quxDef.parameters[0].initializer).toBe(null); + }); }); - it('should return null if the identifier was not imported', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/file_b.js'), 'd', isNamedVariableDeclaration); - const importOfIdent = host.getImportOfIdentifier(variableNode.initializer as ts.Identifier); + describe('getImportOfIdentifier', () => { + it('should find the import of an identifier', () => { + loadTestFiles(IMPORTS_FILES); + const bundle = makeTestBundleProgram(_('/index.js')); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const variableNode = + getDeclaration(bundle.program, _('/file_b.js'), 'b', isNamedVariableDeclaration); + const identifier = (variableNode.initializer && + ts.isPropertyAccessExpression(variableNode.initializer) && + ts.isIdentifier(variableNode.initializer.name)) ? + variableNode.initializer.name : + null; - expect(importOfIdent).toBeNull(); + expect(identifier).not.toBe(null); + const importOfIdent = host.getImportOfIdentifier(identifier!); + expect(importOfIdent).toEqual({name: 'a', from: './file_a'}); + }); + + it('should return null if the identifier was not imported', () => { + loadTestFiles(IMPORTS_FILES); + const bundle = makeTestBundleProgram(_('/index.js')); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const variableNode = + getDeclaration(bundle.program, _('/file_b.js'), 'd', isNamedVariableDeclaration); + const importOfIdent = + host.getImportOfIdentifier(variableNode.initializer as ts.Identifier); + + expect(importOfIdent).toBeNull(); + }); + + it('should handle factory functions not wrapped in parentheses', () => { + loadTestFiles(IMPORTS_FILES); + const bundle = makeTestBundleProgram(_('/index.js')); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const variableNode = + getDeclaration(bundle.program, _('/file_c.js'), 'c', isNamedVariableDeclaration); + const identifier = (variableNode.initializer && + ts.isPropertyAccessExpression(variableNode.initializer) && + ts.isIdentifier(variableNode.initializer.name)) ? + variableNode.initializer.name : + null; + + expect(identifier).not.toBe(null); + const importOfIdent = host.getImportOfIdentifier(identifier!); + expect(importOfIdent).toEqual({name: 'a', from: './file_a'}); + }); }); - it('should handle factory functions not wrapped in parentheses', () => { - loadTestFiles(IMPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const variableNode = - getDeclaration(bundle.program, _('/file_c.js'), 'c', isNamedVariableDeclaration); - const identifier = - (variableNode.initializer && ts.isPropertyAccessExpression(variableNode.initializer) && - ts.isIdentifier(variableNode.initializer.name)) ? - variableNode.initializer.name : - null; + describe('getDeclarationOfIdentifier', () => { + // Helpers + const createTestForTsHelper = + (host: NgccReflectionHost, factoryFn: ts.FunctionExpression, + getHelperDeclaration: (factoryFn: ts.FunctionExpression, name: string) => + ts.Declaration) => + (varName: string, helperName: string, knownAs: KnownDeclaration, + viaModule: string|null = null) => { + const node = getVariableDeclaration(factoryFn, varName); + const helperIdentifier = getIdentifierFromCallExpression(node); + const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - expect(identifier).not.toBe(null); - const importOfIdent = host.getImportOfIdentifier(identifier!); - expect(importOfIdent).toEqual({name: 'a', from: './file_a'}); - }); - }); + expect(helperDeclaration).toEqual({ + kind: DeclarationKind.Concrete, + known: knownAs, + node: getHelperDeclaration(factoryFn, helperName), + viaModule, + identity: null, + }); + }; - describe('getDeclarationOfIdentifier', () => { - // Helpers - const createTestForTsHelper = - (host: NgccReflectionHost, factoryFn: ts.FunctionExpression, - getHelperDeclaration: (factoryFn: ts.FunctionExpression, name: string) => - ts.Declaration) => - (varName: string, helperName: string, knownAs: KnownDeclaration, - viaModule: string|null = null) => { - const node = getVariableDeclaration(factoryFn, varName); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); + const getFunctionDeclaration = (factoryFn: ts.FunctionExpression, name: string) => + factoryFn.body.statements.filter(ts.isFunctionDeclaration) + .find(decl => (decl.name !== undefined) && (decl.name.text === name))!; - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Concrete, - known: knownAs, - node: getHelperDeclaration(factoryFn, helperName), - viaModule, - identity: null, - }); - }; + const getIdentifierFromCallExpression = (decl: ts.VariableDeclaration) => { + if (decl.initializer !== undefined && ts.isCallExpression(decl.initializer)) { + const expr = decl.initializer.expression; + if (ts.isIdentifier(expr)) return expr; + if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.name)) return expr.name; + } + throw new Error(`Unable to extract identifier from declaration '${decl.getText()}'.`); + }; - const getFunctionDeclaration = (factoryFn: ts.FunctionExpression, name: string) => - factoryFn.body.statements.filter(ts.isFunctionDeclaration) - .find(decl => (decl.name !== undefined) && (decl.name.text === name))!; + const getVariableDeclaration = (factoryFn: ts.FunctionExpression, name: string) => + factoryFn.body.statements.filter(ts.isVariableStatement) + .map(stmt => stmt.declarationList.declarations[0]) + .find(decl => ts.isIdentifier(decl.name) && (decl.name.text === name))!; - const getIdentifierFromCallExpression = (decl: ts.VariableDeclaration) => { - if (decl.initializer !== undefined && ts.isCallExpression(decl.initializer)) { - const expr = decl.initializer.expression; - if (ts.isIdentifier(expr)) return expr; - if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.name)) return expr.name; - } - throw new Error(`Unable to extract identifier from declaration '${decl.getText()}'.`); - }; + it('should return the declaration of a locally defined identifier', () => { + loadTestFiles([SOME_DIRECTIVE_FILE]); + const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); + const ctrDecorators = host.getConstructorParameters(classNode)!; + const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference! as { + kind: TypeValueReferenceKind.LOCAL, + expression: ts.Identifier, + defaultImportStatement: null, + }).expression; - const getVariableDeclaration = (factoryFn: ts.FunctionExpression, name: string) => - factoryFn.body.statements.filter(ts.isVariableStatement) - .map(stmt => stmt.declarationList.declarations[0]) - .find(decl => ts.isIdentifier(decl.name) && (decl.name.text === name))!; + const expectedDeclarationNode = getDeclaration( + bundle.program, SOME_DIRECTIVE_FILE.name, 'ViewContainerRef', + isNamedVariableDeclaration); + const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfViewContainerRef); + expect(actualDeclaration).not.toBe(null); + expect(actualDeclaration!.node).toBe(expectedDeclarationNode); + expect(actualDeclaration!.viaModule).toBe(null); + expect((actualDeclaration as ConcreteDeclaration).identity).toBe(null); + }); - it('should return the declaration of a locally defined identifier', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedFunctionDeclaration); - const ctrDecorators = host.getConstructorParameters(classNode)!; - const identifierOfViewContainerRef = (ctrDecorators[0].typeValueReference! as { - kind: TypeValueReferenceKind.LOCAL, - expression: ts.Identifier, - defaultImportStatement: null, - }).expression; - - const expectedDeclarationNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'ViewContainerRef', - isNamedVariableDeclaration); - const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfViewContainerRef); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe(null); - expect((actualDeclaration as ConcreteDeclaration).identity).toBe(null); - }); - - it('should return the correct declaration for an outer alias identifier', () => { - const PROGRAM_FILE: TestFile = { - name: _('/test.js'), - contents: ` + it('should return the correct declaration for an outer alias identifier', () => { + const PROGRAM_FILE: TestFile = { + name: _('/test.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : (factory(global.test)); }(this, (function (exports,module) { 'use strict'; - var AliasedClass = AliasedClass_1 = (function () { + ${expDecl('AliasedClass')} = AliasedClass_1 = (function () { function InnerClass() { } return InnerClass; @@ -2069,155 +2165,164 @@ runInEachFileSystem(() => { var AliasedClass_1; }))); `, - }; + }; - loadTestFiles([PROGRAM_FILE]); - const bundle = makeTestBundleProgram(PROGRAM_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + loadTestFiles([PROGRAM_FILE]); + const bundle = makeTestBundleProgram(PROGRAM_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const expectedDeclaration = getDeclaration( - bundle.program, PROGRAM_FILE.name, 'AliasedClass', isNamedVariableDeclaration); - // Grab the `AliasedClass_1` identifier (which is an alias for `AliasedClass`). - const aliasIdentifier = - (expectedDeclaration.initializer as ts.BinaryExpression).left as ts.Identifier; - const actualDeclaration = host.getDeclarationOfIdentifier(aliasIdentifier); + const expectedDeclaration = getDeclaration( + bundle.program, PROGRAM_FILE.name, 'AliasedClass', isDesiredDeclaration); + // Grab the `AliasedClass_1` identifier (which is an alias for `AliasedClass`). + const aliasIdentifier = getDeclaration( + bundle.program, PROGRAM_FILE.name, 'AliasedClass_1', isNamedVariableDeclaration); + const actualDeclaration = host.getDeclarationOfIdentifier(aliasIdentifier.name); - expect(aliasIdentifier.getText()).toBe('AliasedClass_1'); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclaration); - }); + expect(aliasIdentifier.getText()).toBe('AliasedClass_1'); + expect(actualDeclaration).not.toBe(null); + expect(actualDeclaration!.node).toBe(expectedDeclaration); + }); - it('should return the source-file of an import namespace', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([SOME_DIRECTIVE_FILE]); - const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = getDeclaration( - bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedFunctionDeclaration); - const classDecorators = host.getDecoratorsOfDeclaration(classNode)!; - const identifierOfDirective = - (((classDecorators[0].node as ts.ObjectLiteralExpression).properties[0] as - ts.PropertyAssignment) - .initializer as ts.PropertyAccessExpression) - .expression as ts.Identifier; + it('should return the source-file of an import namespace', () => { + loadFakeCore(getFileSystem()); + loadTestFiles([SOME_DIRECTIVE_FILE]); + const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = getDeclaration( + bundle.program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isDesiredDeclaration); + const classDecorators = host.getDecoratorsOfDeclaration(classNode)!; + const identifierOfDirective = + (((classDecorators[0].node as ts.ObjectLiteralExpression).properties[0] as + ts.PropertyAssignment) + .initializer as ts.PropertyAccessExpression) + .expression as ts.Identifier; - const expectedDeclarationNode = - getSourceFileOrError(bundle.program, _('/node_modules/@angular/core/index.d.ts')); - const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfDirective); - expect(actualDeclaration).not.toBe(null); - expect(actualDeclaration!.node).toBe(expectedDeclarationNode); - expect(actualDeclaration!.viaModule).toBe('@angular/core'); - }); + const expectedDeclarationNode = + getSourceFileOrError(bundle.program, _('/node_modules/@angular/core/index.d.ts')); + const actualDeclaration = host.getDeclarationOfIdentifier(identifierOfDirective); + expect(actualDeclaration).not.toBe(null); + expect(actualDeclaration!.node).toBe(expectedDeclarationNode); + expect(actualDeclaration!.viaModule).toBe('@angular/core'); + }); - it('should return the source file as the declaration of a standalone "exports" identifier', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const xVar = getDeclaration( - bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'x', ts.isVariableDeclaration); - const exportsIdentifier = xVar.initializer; - if (exportsIdentifier === undefined || !ts.isIdentifier(exportsIdentifier)) { - throw new Error('Bad test - unable to find `var x = exports;` statement'); - } - const exportsDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportsDeclaration).not.toBeNull(); - expect(exportsDeclaration!.node).toBe(bundle.file); - }); + it('should return the source file as the declaration of a standalone "exports" identifier', + () => { + loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); + const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const xVar = getDeclaration( + bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'x', isNamedVariableDeclaration); + const exportsIdentifier = xVar.initializer; + if (exportsIdentifier === undefined || !ts.isIdentifier(exportsIdentifier)) { + throw new Error('Bad test - unable to find `var x = exports;` statement'); + } + const exportsDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); + expect(exportsDeclaration).not.toBeNull(); + expect(exportsDeclaration!.node).toBe(bundle.file); + }); - it('should return the source file as the declaration of "exports" in the LHS of a property access', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const exportsStatement = walkForNode(bundle.file, isExportsStatement); - if (exportsStatement === undefined) { - throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); - } - const exportsIdentifier = exportsStatement.expression.left.expression; - const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportDeclaration).not.toBeNull(); - expect(exportDeclaration!.node).toBe(bundle.file); - }); + it('should return the source file as the declaration of "exports" in the LHS of a property access', + () => { + loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); + const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const exportsStatement = walkForNode(bundle.file, isExportsStatement); + if (exportsStatement === undefined) { + throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); + } + const exportsIdentifier = exportsStatement.expression.left.expression; + const exportsDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); + expect(exportsDeclaration).not.toBeNull(); + expect(exportsDeclaration!.node).toBe(bundle.file); + }); - it('should return the source file as the declaration of "exports" in the LHS of a property access inside a local function', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const simpleFn = getDeclaration( - bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'simpleFn', ts.isFunctionDeclaration); - const exportsStatement = walkForNode(simpleFn, isExportsStatement); - if (exportsStatement === undefined) { - throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); - } - const exportsIdentifier = exportsStatement.expression.left.expression; - const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportDeclaration).not.toBeNull(); - expect(exportDeclaration!.node).toBe(bundle.file); - }); + it('should return the source file as the declaration of "exports" in the LHS of a property access inside a local function', + () => { + loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); + const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const simpleFn = getDeclaration( + bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'simpleFn', + ts.isFunctionDeclaration); + const exportsStatement = walkForNode(simpleFn, isExportsStatement); + if (exportsStatement === undefined) { + throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); + } + const exportsIdentifier = exportsStatement.expression.left.expression; + const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); + expect(exportDeclaration).not.toBeNull(); + expect(exportDeclaration!.node).toBe(bundle.file); + }); - it('should return the variable declaration if a standalone "exports" is declared locally', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const localVarFn = getDeclaration( - bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'localVar', ts.isFunctionDeclaration); - const exportsVar = walkForNode(localVarFn, ts.isVariableDeclaration); - if (exportsVar === undefined) { - throw new Error('Bad test - unable to find `var exports = {}` statement'); - } - const exportsIdentifier = exportsVar.name; - if (exportsIdentifier === undefined || !ts.isIdentifier(exportsIdentifier)) { - throw new Error('Bad test - unable to find `var exports = {};` statement'); - } - const exportsDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportsDeclaration).not.toBeNull(); - expect(exportsDeclaration!.node).toBe(exportsVar); - }); + it('should return the variable declaration if a standalone "exports" is declared locally', + () => { + loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); + const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const localVarFn = getDeclaration( + bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'localVar', + ts.isFunctionDeclaration); + const exportsVar = walkForNode(localVarFn, isNamedVariableDeclaration); + if (exportsVar === undefined) { + throw new Error('Bad test - unable to find `var exports = {}` statement'); + } + const exportsIdentifier = exportsVar.name; + if (exportsIdentifier === undefined || !ts.isIdentifier(exportsIdentifier)) { + throw new Error('Bad test - unable to find `var exports = {};` statement'); + } + const exportsDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); + expect(exportsDeclaration).not.toBeNull(); + expect(exportsDeclaration!.node).toBe(exportsVar); + }); - it('should return the variable declaration of "exports" in the LHS of a property access, if it is declared locally', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const localVarFn = getDeclaration( - bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'localVar', ts.isFunctionDeclaration); - const exportsVar = walkForNode(localVarFn, ts.isVariableDeclaration); - const exportsStatement = walkForNode(localVarFn, isExportsStatement); - if (exportsStatement === undefined) { - throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); - } - const exportsIdentifier = exportsStatement.expression.left.expression; - const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportDeclaration).not.toBeNull(); - expect(exportDeclaration?.node).toBe(exportsVar); - }); + it('should return the variable declaration of "exports" in the LHS of a property access, if it is declared locally', + () => { + loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); + const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const localVarFn = getDeclaration( + bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'localVar', + ts.isFunctionDeclaration); + const exportsVar = walkForNode(localVarFn, isNamedVariableDeclaration); + const exportsStatement = walkForNode(localVarFn, isExportsStatement); + if (exportsStatement === undefined) { + throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); + } + const exportsIdentifier = exportsStatement.expression.left.expression; + const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); + expect(exportDeclaration).not.toBeNull(); + expect(exportDeclaration?.node).toBe(exportsVar); + }); - it('should return the variable declaration of "exports" in the LHS of a property access, if it is a local function parameter', - () => { - loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); - const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const exportsArgFn = getDeclaration( - bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'exportsArg', - ts.isFunctionDeclaration); - const exportsVar = exportsArgFn.parameters[0]; - const exportsStatement = walkForNode(exportsArgFn, isExportsStatement); - if (exportsStatement === undefined) { - throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); - } - const exportsIdentifier = exportsStatement.expression.left.expression; - const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); - expect(exportDeclaration).not.toBeNull(); - expect(exportDeclaration?.node).toBe(exportsVar); - }); + it('should return the variable declaration of "exports" in the LHS of a property access, if it is a local function parameter', + () => { + loadTestFiles([EXPORTS_IDENTIFIERS_FILE]); + const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const exportsArgFn = getDeclaration( + bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'exportsArg', + ts.isFunctionDeclaration); + const exportsVar = exportsArgFn.parameters[0]; + const exportsStatement = walkForNode(exportsArgFn, isExportsStatement); + if (exportsStatement === undefined) { + throw new Error('Bad test - unable to find `exports.foo = 42;` statement'); + } + const exportsIdentifier = exportsStatement.expression.left.expression; + const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier); + expect(exportDeclaration).not.toBeNull(); + expect(exportDeclaration?.node).toBe(exportsVar); + }); - it('should recognize TypeScript helpers (as function declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` + it('should recognize TypeScript helpers (as function declarations)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : @@ -2232,24 +2337,24 @@ runInEachFileSystem(() => { var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); }))); `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const {factoryFn} = parseStatementForUmdModule( + getSourceFileOrError(bundle.program, file.name).statements[0])!; - const testForHelper = createTestForTsHelper(host, factoryFn, getFunctionDeclaration); + const testForHelper = createTestForTsHelper(host, factoryFn, getFunctionDeclaration); - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - }); + testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + }); - it('should recognize suffixed TypeScript helpers (as function declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` + it('should recognize suffixed TypeScript helpers (as function declarations)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : @@ -2264,24 +2369,24 @@ runInEachFileSystem(() => { var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); }))); `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const {factoryFn} = parseStatementForUmdModule( + getSourceFileOrError(bundle.program, file.name).statements[0])!; - const testForHelper = createTestForTsHelper(host, factoryFn, getFunctionDeclaration); + const testForHelper = createTestForTsHelper(host, factoryFn, getFunctionDeclaration); - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - }); + testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + }); - it('should recognize TypeScript helpers (as variable declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` + it('should recognize TypeScript helpers (as variable declarations)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : @@ -2296,24 +2401,24 @@ runInEachFileSystem(() => { var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); }))); `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const {factoryFn} = parseStatementForUmdModule( + getSourceFileOrError(bundle.program, file.name).statements[0])!; - const testForHelper = createTestForTsHelper(host, factoryFn, getVariableDeclaration); + const testForHelper = createTestForTsHelper(host, factoryFn, getVariableDeclaration); - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - }); + testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + }); - it('should recognize suffixed TypeScript helpers (as variable declarations)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` + it('should recognize suffixed TypeScript helpers (as variable declarations)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : @@ -2328,25 +2433,25 @@ runInEachFileSystem(() => { var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); }))); `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const {factoryFn} = parseStatementForUmdModule( + getSourceFileOrError(bundle.program, file.name).statements[0])!; - const testForHelper = createTestForTsHelper(host, factoryFn, getVariableDeclaration); + const testForHelper = createTestForTsHelper(host, factoryFn, getVariableDeclaration); - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - }); + testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + }); - it('should recognize imported TypeScript helpers', () => { - const files: TestFile[] = [ - { - name: _('/test.js'), - contents: ` + it('should recognize imported TypeScript helpers', () => { + const files: TestFile[] = [ + { + name: _('/test.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib')) : typeof define === 'function' && define.amd ? define('test', ['exports', 'tslib'], factory) : @@ -2357,37 +2462,37 @@ runInEachFileSystem(() => { var c = tslib_1.__spreadArrays(['foo', 'bar'], ['baz', 'qux']); }))); `, - }, - { - name: _('/node_modules/tslib/index.d.ts'), - contents: ` + }, + { + name: _('/node_modules/tslib/index.d.ts'), + contents: ` export declare function __assign(t: any, ...sources: any[]): any; export declare function __spread(...args: any[][]): any[]; export declare function __spreadArrays(...args: any[][]): any[]; `, - }, - ]; - loadTestFiles(files); + }, + ]; + loadTestFiles(files); - const [testFile, tslibFile] = files; - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, testFile.name).statements[0])!; - const testForHelper = createTestForTsHelper( - host, factoryFn, - (_fn, helperName) => getDeclaration( - bundle.program, tslibFile.name, helperName, ts.isFunctionDeclaration)); + const [testFile, tslibFile] = files; + const bundle = makeTestBundleProgram(testFile.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const {factoryFn} = parseStatementForUmdModule( + getSourceFileOrError(bundle.program, testFile.name).statements[0])!; + const testForHelper = createTestForTsHelper( + host, factoryFn, + (_fn, helperName) => getDeclaration( + bundle.program, tslibFile.name, helperName, ts.isFunctionDeclaration)); - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib'); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); - }); + testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib'); + testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); + testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); + }); - it('should recognize undeclared, unimported TypeScript helpers (by name)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` + it('should recognize undeclared, unimported TypeScript helpers (by name)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : @@ -2398,35 +2503,36 @@ runInEachFileSystem(() => { var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); }))); `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const {factoryFn} = parseStatementForUmdModule( + getSourceFileOrError(bundle.program, file.name).statements[0])!; - const testForHelper = (varName: string, helperName: string, knownAs: KnownDeclaration) => { - const node = getVariableDeclaration(factoryFn, varName); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); + const testForHelper = + (varName: string, helperName: string, knownAs: KnownDeclaration) => { + const node = getVariableDeclaration(factoryFn, varName); + const helperIdentifier = getIdentifierFromCallExpression(node); + const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Inline, - known: knownAs, - node: helperIdentifier, - viaModule: null, - }); - }; + expect(helperDeclaration).toEqual({ + kind: DeclarationKind.Inline, + known: knownAs, + node: helperIdentifier, + viaModule: null, + }); + }; - testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); - }); + testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + }); - it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', () => { - const file: TestFile = { - name: _('/test.js'), - contents: ` + it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : @@ -2437,35 +2543,36 @@ runInEachFileSystem(() => { var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); }))); `, - }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, file.name).statements[0])!; + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const {factoryFn} = parseStatementForUmdModule( + getSourceFileOrError(bundle.program, file.name).statements[0])!; - const testForHelper = (varName: string, helperName: string, knownAs: KnownDeclaration) => { - const node = getVariableDeclaration(factoryFn, varName); - const helperIdentifier = getIdentifierFromCallExpression(node); - const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); + const testForHelper = + (varName: string, helperName: string, knownAs: KnownDeclaration) => { + const node = getVariableDeclaration(factoryFn, varName); + const helperIdentifier = getIdentifierFromCallExpression(node); + const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); - expect(helperDeclaration).toEqual({ - kind: DeclarationKind.Inline, - known: knownAs, - node: helperIdentifier, - viaModule: null, - }); - }; + expect(helperDeclaration).toEqual({ + kind: DeclarationKind.Inline, + known: knownAs, + node: helperIdentifier, + viaModule: null, + }); + }; - testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); - testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); - testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); - }); + testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + }); - it('should recognize enum declarations with string values', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` + it('should recognize enum declarations with string values', () => { + const testFile: TestFile = { + name: _('/node_modules/test-package/some/file.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) : @@ -2480,30 +2587,30 @@ runInEachFileSystem(() => { var value = Enum; }))); ` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, _('/node_modules/test-package/some/file.js')) - .statements[0])!; - const valueDecl = getVariableDeclaration(factoryFn, 'value'); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; + }; + loadTestFiles([testFile]); + const bundle = makeTestBundleProgram(testFile.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const {factoryFn} = parseStatementForUmdModule( + getSourceFileOrError(bundle.program, _('/node_modules/test-package/some/file.js')) + .statements[0])!; + const valueDecl = getVariableDeclaration(factoryFn, 'value'); + const declaration = host.getDeclarationOfIdentifier( + valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(enumMembers!.length).toBe(2); - expect(enumMembers![0].name.getText()).toBe('"ValueA"'); - expect(enumMembers![0].initializer!.getText()).toBe('"1"'); - expect(enumMembers![1].name.getText()).toBe('"ValueB"'); - expect(enumMembers![1].initializer!.getText()).toBe('"2"'); - }); + const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; + expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); + expect(enumMembers!.length).toBe(2); + expect(enumMembers![0].name.getText()).toBe('"ValueA"'); + expect(enumMembers![0].initializer!.getText()).toBe('"1"'); + expect(enumMembers![1].name.getText()).toBe('"ValueB"'); + expect(enumMembers![1].initializer!.getText()).toBe('"2"'); + }); - it('should recognize enum declarations with numeric values', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` + it('should recognize enum declarations with numeric values', () => { + const testFile: TestFile = { + name: _('/node_modules/test-package/some/file.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) : @@ -2518,31 +2625,31 @@ runInEachFileSystem(() => { var value = Enum; }))); ` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const {factoryFn} = parseStatementForUmdModule( - getSourceFileOrError(bundle.program, _('/node_modules/test-package/some/file.js')) - .statements[0])!; - const valueDecl = getVariableDeclaration(factoryFn, 'value'); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; + }; + loadTestFiles([testFile]); + const bundle = makeTestBundleProgram(testFile.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const {factoryFn} = parseStatementForUmdModule( + getSourceFileOrError(bundle.program, _('/node_modules/test-package/some/file.js')) + .statements[0])!; + const valueDecl = getVariableDeclaration(factoryFn, 'value'); + const declaration = host.getDeclarationOfIdentifier( + valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(enumMembers!.length).toBe(2); - expect(enumMembers![0].name.getText()).toBe('"ValueA"'); - expect(enumMembers![0].initializer!.getText()).toBe('"1"'); - expect(enumMembers![1].name.getText()).toBe('"ValueB"'); - expect(enumMembers![1].initializer!.getText()).toBe('"2"'); - }); + const enumMembers = (declaration.identity as DownleveledEnum).enumMembers; + expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); + expect(enumMembers!.length).toBe(2); + expect(enumMembers![0].name.getText()).toBe('"ValueA"'); + expect(enumMembers![0].initializer!.getText()).toBe('"1"'); + expect(enumMembers![1].name.getText()).toBe('"ValueB"'); + expect(enumMembers![1].initializer!.getText()).toBe('"2"'); + }); - it('should not consider IIFEs that do no assign members to the parameter as an enum declaration', - () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` + it('should not consider IIFEs that do no assign members to the parameter as an enum declaration', + () => { + const testFile: TestFile = { + name: _('/node_modules/test-package/some/file.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) : @@ -2557,24 +2664,25 @@ runInEachFileSystem(() => { var value = Enum; }))); ` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; + }; + loadTestFiles([testFile]); + const bundle = makeTestBundleProgram(testFile.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const valueDecl = getDeclaration( + bundle.program, _('/node_modules/test-package/some/file.js'), 'value', + isNamedVariableDeclaration); + const declaration = host.getDeclarationOfIdentifier( + valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(declaration.identity).toBe(null); - }); + expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); + expect(declaration.identity).toBe(null); + }); - it('should not consider IIFEs without call argument as an enum declaration', () => { - const testFile: TestFile = { - name: _('/node_modules/test-package/some/file.js'), - contents: ` + it('should not consider IIFEs without call argument as an enum declaration', () => { + const testFile: TestFile = { + name: _('/node_modules/test-package/some/file.js'), + contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) : typeof define === 'function' && define.amd ? define('some_directive', ['exports', '@angular/core'], factory) : @@ -2589,784 +2697,798 @@ runInEachFileSystem(() => { var value = Enum; }))); ` - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const valueDecl = getDeclaration( - bundle.program, _('/node_modules/test-package/some/file.js'), 'value', - ts.isVariableDeclaration); - const declaration = host.getDeclarationOfIdentifier( - valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; + }; + loadTestFiles([testFile]); + const bundle = makeTestBundleProgram(testFile.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const valueDecl = getDeclaration( + bundle.program, _('/node_modules/test-package/some/file.js'), 'value', + isNamedVariableDeclaration); + const declaration = host.getDeclarationOfIdentifier( + valueDecl.initializer as ts.Identifier) as ConcreteDeclaration; - expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); - expect(declaration.identity).toBe(null); + expect(declaration.node.parent.parent.getText()).toBe('var Enum;'); + expect(declaration.identity).toBe(null); + }); }); - }); - describe('getExportsOfModule()', () => { - it('should return a map of all the exports from a given module', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/b_module.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], - ['a', `a = 'a'`, null], - ['b', `b = a_module.a`, null], - ['c', `a = 'a'`, null], - ['d', `b = a_module.a`, null], - ['e', `e = 'e'`, null], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], - [ - 'SomeClass', `SomeClass = (function() { + describe('getExportsOfModule()', () => { + it('should return a map of all the exports from a given module', () => { + loadFakeCore(getFileSystem()); + loadTestFiles(EXPORTS_FILES); + const bundle = makeTestBundleProgram(_('/index.js')); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const file = getSourceFileOrError(bundle.program, _('/b_module.js')); + const exportDeclarations = host.getExportsOfModule(file); + expect(exportDeclarations).not.toBe(null); + expect(Array.from(exportDeclarations!.entries()) + .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) + .toEqual([ + ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], + ['a', `a = 'a'`, null], + ['b', `b = a_module.a`, null], + ['c', `a = 'a'`, null], + ['d', `b = a_module.a`, null], + ['e', `e = 'e'`, null], + ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, '@angular/core'], + [ + 'SomeClass', `SomeClass = (function() { function SomeClass() {} return SomeClass; }())`, - null - ], - ]); - }); + null + ], + ]); + }); - it('should handle wildcard re-exports of other modules (with emitted helpers)', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/wildcard_reexports.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - ['a', `a = 'a'`, _('/b_module')], - ['b', `b = a_module.a`, _('/b_module')], - ['c', `a = 'a'`, _('/b_module')], - ['d', `b = a_module.a`, _('/b_module')], - ['e', `e = 'e'`, _('/b_module')], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, - _('/b_module') - ], - ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], - ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], - ]); - }); + it('should return the correct declaration for a decorated class', () => { + loadFakeCore(getFileSystem()); + loadTestFiles(EXPORTS_FILES); + const bundle = makeTestBundleProgram(_('/decorated_class.js')); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const file = getSourceFileOrError(bundle.program, _('/decorated_class.js')); + const exportDeclarations = host.getExportsOfModule(file); + expect(exportDeclarations).not.toBe(null); + expect(exportDeclarations!.size).toEqual(1); + const classDecl = exportDeclarations!.get('DecoratedClass')!; + expect(classDecl).toBeDefined(); + expect(classDecl.kind).toEqual(DeclarationKind.Inline); + expect(classDecl.known).toBe(null); + expect(classDecl.viaModule).toBe(null); + expect(classDecl.node.getText()).toEqual('exports.DecoratedClass'); + expect(classDecl.node.parent.parent.getText()).toContain('function DecoratedClass() {'); + }); - it('should handle wildcard re-exports of other modules (with imported helpers)', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = - getSourceFileOrError(bundle.program, _('/wildcard_reexports_imported_helpers.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - ['a', `a = 'a'`, _('/b_module')], - ['b', `b = a_module.a`, _('/b_module')], - ['c', `a = 'a'`, _('/b_module')], - ['d', `b = a_module.a`, _('/b_module')], - ['e', `e = 'e'`, _('/b_module')], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, - _('/b_module') - ], - ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], - ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], - ]); - }); + it('should handle wildcard re-exports of other modules (with emitted helpers)', () => { + loadFakeCore(getFileSystem()); + loadTestFiles(EXPORTS_FILES); + const bundle = makeTestBundleProgram(_('/index.js')); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const file = getSourceFileOrError(bundle.program, _('/wildcard_reexports.js')); + const exportDeclarations = host.getExportsOfModule(file); + expect(exportDeclarations).not.toBe(null); + expect(Array.from(exportDeclarations!.entries()) + .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) + .toEqual([ + ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + ['a', `a = 'a'`, _('/b_module')], + ['b', `b = a_module.a`, _('/b_module')], + ['c', `a = 'a'`, _('/b_module')], + ['d', `b = a_module.a`, _('/b_module')], + ['e', `e = 'e'`, _('/b_module')], + ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + [ + 'SomeClass', + `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, + _('/b_module') + ], + ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], + ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], + ]); + }); - it('should handle inline exports', () => { - loadFakeCore(getFileSystem()); - loadTestFiles([INLINE_EXPORT_FILE]); - const bundle = makeTestBundleProgram(INLINE_EXPORT_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, INLINE_EXPORT_FILE.name); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - const decl = exportDeclarations!.get('directives')!; - expect(decl).toBeDefined(); - expect(decl.node).toBeDefined(); - expect(decl.kind).toEqual(DeclarationKind.Inline); - }); + it('should handle wildcard re-exports of other modules (with imported helpers)', () => { + loadFakeCore(getFileSystem()); + loadTestFiles(EXPORTS_FILES); + const bundle = makeTestBundleProgram(_('/index.js')); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const file = + getSourceFileOrError(bundle.program, _('/wildcard_reexports_imported_helpers.js')); + const exportDeclarations = host.getExportsOfModule(file); + expect(exportDeclarations).not.toBe(null); + expect(Array.from(exportDeclarations!.entries()) + .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) + .toEqual([ + ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + ['a', `a = 'a'`, _('/b_module')], + ['b', `b = a_module.a`, _('/b_module')], + ['c', `a = 'a'`, _('/b_module')], + ['d', `b = a_module.a`, _('/b_module')], + ['e', `e = 'e'`, _('/b_module')], + ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + [ + 'SomeClass', + `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, + _('/b_module') + ], + ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], + ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], + ]); + }); - it('should recognize declarations of known TypeScript helpers', () => { - const tslib = { - name: _('/tslib.d.ts'), - contents: ` + it('should handle inline exports', () => { + loadFakeCore(getFileSystem()); + loadTestFiles([INLINE_EXPORT_FILE]); + const bundle = makeTestBundleProgram(INLINE_EXPORT_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const file = getSourceFileOrError(bundle.program, INLINE_EXPORT_FILE.name); + const exportDeclarations = host.getExportsOfModule(file); + expect(exportDeclarations).not.toBe(null); + const decl = exportDeclarations!.get('directives')!; + expect(decl).toBeDefined(); + expect(decl.node).toBeDefined(); + expect(decl.kind).toEqual(DeclarationKind.Inline); + }); + + it('should recognize declarations of known TypeScript helpers', () => { + const tslib = { + name: _('/tslib.d.ts'), + contents: ` export declare function __assign(t: any, ...sources: any[]): any; export declare function __spread(...args: any[][]): any[]; export declare function __spreadArrays(...args: any[][]): any[]; export declare function __unknownHelper(...args: any[]): any; `, - }; - loadTestFiles([tslib]); - const bundle = makeTestBundleProgram(tslib.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const sf = getSourceFileOrError(bundle.program, tslib.name); - const exportDeclarations = host.getExportsOfModule(sf)!; + }; + loadTestFiles([tslib]); + const bundle = makeTestBundleProgram(tslib.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const sf = getSourceFileOrError(bundle.program, tslib.name); + const exportDeclarations = host.getExportsOfModule(sf)!; - expect([...exportDeclarations].map(([exportName, {known}]) => [exportName, known])) - .toEqual([ - ['__assign', KnownDeclaration.TsHelperAssign], - ['__spread', KnownDeclaration.TsHelperSpread], - ['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays], - ['__unknownHelper', null], - ]); + expect([...exportDeclarations].map(([exportName, {known}]) => [exportName, known])) + .toEqual([ + ['__assign', KnownDeclaration.TsHelperAssign], + ['__spread', KnownDeclaration.TsHelperSpread], + ['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays], + ['__unknownHelper', null], + ]); + }); + + it('should define property exports from a module', () => { + loadFakeCore(getFileSystem()); + loadTestFiles(EXPORTS_FILES); + const bundle = makeTestBundleProgram(_('/index.js')); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const file = getSourceFileOrError(bundle.program, _('/define_property_reexports.js')); + const exportDeclarations = host.getExportsOfModule(file); + expect(exportDeclarations).not.toBe(null); + expect(Array.from(exportDeclarations!.entries()) + .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) + .toEqual([ + ['newA', `a = 'a'`, null], + ]); + }); }); - it('should define property exports from a module', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/define_property_reexports.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['newA', `a = 'a'`, null], - ]); - }); - }); + describe('getClassSymbol()', () => { + it('should return the class symbol for an ES2015 class', () => { + loadTestFiles([SIMPLE_ES2015_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const node = getDeclaration( + bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); + const classSymbol = host.getClassSymbol(node); - describe('getClassSymbol()', () => { - it('should return the class symbol for an ES2015 class', () => { - loadTestFiles([SIMPLE_ES2015_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - const classSymbol = host.getClassSymbol(node); + expect(classSymbol).toBeDefined(); + expect(classSymbol!.declaration.valueDeclaration).toBe(node); + expect(classSymbol!.implementation.valueDeclaration).toBe(node); + }); - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(node); - expect(classSymbol!.implementation.valueDeclaration).toBe(node); + it('should handle wildcard re-exports of other modules (with emitted helpers)', () => { + loadFakeCore(getFileSystem()); + loadTestFiles(EXPORTS_FILES); + const bundle = makeTestBundleProgram(_('/index.js')); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const file = getSourceFileOrError(bundle.program, _('/wildcard_reexports.js')); + const exportDeclarations = host.getExportsOfModule(file); + expect(exportDeclarations).not.toBe(null); + expect(Array.from(exportDeclarations!.entries()) + .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) + .toEqual([ + ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + ['a', `a = 'a'`, _('/b_module')], + ['b', `b = a_module.a`, _('/b_module')], + ['c', `a = 'a'`, _('/b_module')], + ['d', `b = a_module.a`, _('/b_module')], + ['e', `e = 'e'`, _('/b_module')], + ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + [ + 'SomeClass', + `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, + _('/b_module') + ], + ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], + ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], + ]); + }); + + it('should handle wildcard re-exports of other modules (with imported helpers)', () => { + loadFakeCore(getFileSystem()); + loadTestFiles(EXPORTS_FILES); + const bundle = makeTestBundleProgram(_('/index.js')); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const file = + getSourceFileOrError(bundle.program, _('/wildcard_reexports_imported_helpers.js')); + const exportDeclarations = host.getExportsOfModule(file); + expect(exportDeclarations).not.toBe(null); + expect(Array.from(exportDeclarations!.entries()) + .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) + .toEqual([ + ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + ['a', `a = 'a'`, _('/b_module')], + ['b', `b = a_module.a`, _('/b_module')], + ['c', `a = 'a'`, _('/b_module')], + ['d', `b = a_module.a`, _('/b_module')], + ['e', `e = 'e'`, _('/b_module')], + ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + [ + 'SomeClass', + `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, + _('/b_module') + ], + ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], + ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], + ]); + }); + + it('should handle wildcard re-exports of other modules using `require()` calls', () => { + loadFakeCore(getFileSystem()); + loadTestFiles(EXPORTS_FILES); + const bundle = makeTestBundleProgram(_('/index.js')); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const file = + getSourceFileOrError(bundle.program, _('/wildcard_reexports_with_require.js')); + const exportDeclarations = host.getExportsOfModule(file); + expect(exportDeclarations).not.toBe(null); + expect(Array.from(exportDeclarations!.entries()) + .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) + .toEqual([ + ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + ['a', `a = 'a'`, _('/b_module')], + ['b', `b = a_module.a`, _('/b_module')], + ['c', `a = 'a'`, _('/b_module')], + ['d', `b = a_module.a`, _('/b_module')], + ['e', `e = 'e'`, _('/b_module')], + ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], + [ + 'SomeClass', + `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, + _('/b_module') + ], + ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], + ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], + ]); + }); + + it('should return the class symbol for an ES5 class (outer variable declaration)', () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const outerNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); + const innerNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedFunctionDeclaration); + const classSymbol = host.getClassSymbol(outerNode); + + expect(classSymbol).toBeDefined(); + expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode as any); + expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); + }); + + it('should return the class symbol for an ES5 class (inner function declaration)', () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const outerNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); + const innerNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedFunctionDeclaration); + const classSymbol = host.getClassSymbol(innerNode); + + expect(classSymbol).toBeDefined(); + expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode as any); + expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); + }); + + it('should return the same class symbol (of the outer declaration) for outer and inner declarations', + () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const outerNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); + const innerNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedFunctionDeclaration); + + const innerSymbol = host.getClassSymbol(innerNode)!; + const outerSymbol = host.getClassSymbol(outerNode)!; + expect(innerSymbol.declaration).toBe(outerSymbol.declaration); + expect(innerSymbol.implementation).toBe(outerSymbol.implementation); + }); + + it('should return the class symbol for an ES5 class whose IIFE is not wrapped in parens', + () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const outerNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass', isDesiredDeclaration); + const innerNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass', + isNamedFunctionDeclaration); + const classSymbol = host.getClassSymbol(outerNode); + + expect(classSymbol).toBeDefined(); + expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode as any); + expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); + }); + + it('should return the class symbol for an ES5 class whose IIFE is not wrapped with inner parens', + () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const outerNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass', isDesiredDeclaration); + const innerNode = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass', + isNamedFunctionDeclaration); + const classSymbol = host.getClassSymbol(outerNode); + + expect(classSymbol).toBeDefined(); + expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode as any); + expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); + }); + + it('should return undefined if node is not an ES5 class', () => { + loadTestFiles([FOO_FUNCTION_FILE]); + const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const node = getDeclaration( + bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); + const classSymbol = host.getClassSymbol(node); + + expect(classSymbol).toBeUndefined(); + }); + + it('should return undefined if variable declaration is not initialized using an IIFE', + () => { + const testFile = { + name: _('/test.js'), + contents: `var MyClass = null;`, + }; + loadTestFiles([testFile]); + const bundle = makeTestBundleProgram(testFile.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const node = getDeclaration( + bundle.program, testFile.name, 'MyClass', isNamedVariableDeclaration); + const classSymbol = host.getClassSymbol(node); + + expect(classSymbol).toBeUndefined(); + }); }); - it('should handle wildcard re-exports of other modules (with emitted helpers)', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/wildcard_reexports.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - ['a', `a = 'a'`, _('/b_module')], - ['b', `b = a_module.a`, _('/b_module')], - ['c', `a = 'a'`, _('/b_module')], - ['d', `b = a_module.a`, _('/b_module')], - ['e', `e = 'e'`, _('/b_module')], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, - _('/b_module') - ], - ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], - ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], - ]); + describe('isClass()', () => { + it('should return true if a given node is a TS class declaration', () => { + loadTestFiles([SIMPLE_ES2015_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const node = getDeclaration( + bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); + expect(host.isClass(node)).toBe(true); + }); + + it('should return true if a given node is the outer variable declaration of a class', + () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const node = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); + expect(host.isClass(node)).toBe(true); + }); + + it('should return true if a given node is the inner variable declaration of a class', + () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const node = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedFunctionDeclaration); + expect(host.isClass(node)).toBe(true); + }); + + it('should return false if a given node is a function declaration', () => { + loadTestFiles([FOO_FUNCTION_FILE]); + const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const node = getDeclaration( + bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); + expect(host.isClass(node)).toBe(false); + }); }); - it('should handle wildcard re-exports of other modules (with imported helpers)', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = - getSourceFileOrError(bundle.program, _('/wildcard_reexports_imported_helpers.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - ['a', `a = 'a'`, _('/b_module')], - ['b', `b = a_module.a`, _('/b_module')], - ['c', `a = 'a'`, _('/b_module')], - ['d', `b = a_module.a`, _('/b_module')], - ['e', `e = 'e'`, _('/b_module')], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, - _('/b_module') - ], - ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], - ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], - ]); - }); + describe('hasBaseClass()', () => { + function hasBaseClass(source: string) { + const file = { + name: _('/synthesized_constructors.js'), + contents: source, + }; - it('should handle wildcard re-exports of other modules using `require()` calls', () => { - loadFakeCore(getFileSystem()); - loadTestFiles(EXPORTS_FILES); - const bundle = makeTestBundleProgram(_('/index.js')); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const file = getSourceFileOrError(bundle.program, _('/wildcard_reexports_with_require.js')); - const exportDeclarations = host.getExportsOfModule(file); - expect(exportDeclarations).not.toBe(null); - expect(Array.from(exportDeclarations!.entries()) - .map(entry => [entry[0], entry[1].node!.getText(), entry[1].viaModule])) - .toEqual([ - ['Directive', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - ['a', `a = 'a'`, _('/b_module')], - ['b', `b = a_module.a`, _('/b_module')], - ['c', `a = 'a'`, _('/b_module')], - ['d', `b = a_module.a`, _('/b_module')], - ['e', `e = 'e'`, _('/b_module')], - ['DirectiveX', `Directive: FnWithArg<(clazz: any) => any>`, _('/b_module')], - [ - 'SomeClass', - `SomeClass = (function() {\n function SomeClass() {}\n return SomeClass;\n }())`, - _('/b_module') - ], - ['xtra1', `xtra1 = 'xtra1'`, _('/xtra_module')], - ['xtra2', `xtra2 = 'xtra2'`, _('/xtra_module')], - ]); - }); + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = + getDeclaration(bundle.program, file.name, 'TestClass', isDesiredDeclaration); + return host.hasBaseClass(classNode); + } - it('should return the class symbol for an ES5 class (outer variable declaration)', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the class symbol for an ES5 class (inner function declaration)', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(innerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the same class symbol (of the outer declaration) for outer and inner declarations', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - - const innerSymbol = host.getClassSymbol(innerNode)!; - const outerSymbol = host.getClassSymbol(outerNode)!; - expect(innerSymbol.declaration).toBe(outerSymbol.declaration); - expect(innerSymbol.implementation).toBe(outerSymbol.implementation); - }); - - it('should return the class symbol for an ES5 class whose IIFE is not wrapped in parens', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'NoParensClass', isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode as any); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return the class symbol for an ES5 class whose IIFE is not wrapped with inner parens', - () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'InnerParensClass', - isNamedVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - const classSymbol = host.getClassSymbol(outerNode); - - expect(classSymbol).toBeDefined(); - expect(classSymbol!.declaration.valueDeclaration).toBe(outerNode); - expect(classSymbol!.implementation.valueDeclaration).toBe(innerNode); - }); - - it('should return undefined if node is not an ES5 class', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeUndefined(); - }); - - it('should return undefined if variable declaration is not initialized using an IIFE', () => { - const testFile = { - name: _('/test.js'), - contents: `var MyClass = null;`, - }; - loadTestFiles([testFile]); - const bundle = makeTestBundleProgram(testFile.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = - getDeclaration(bundle.program, testFile.name, 'MyClass', isNamedVariableDeclaration); - const classSymbol = host.getClassSymbol(node); - - expect(classSymbol).toBeUndefined(); - }); - }); - - describe('isClass()', () => { - it('should return true if a given node is a TS class declaration', () => { - loadTestFiles([SIMPLE_ES2015_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_ES2015_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_ES2015_CLASS_FILE.name, 'EmptyClass', isNamedClassDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return true if a given node is the outer variable declaration of a class', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration); - expect(host.isClass(node)).toBe(true); - }); - - it('should return true if a given node is the inner variable declaration of a class', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const outerNode = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration); - const innerNode = (getIifeBody(outerNode.initializer!) as ts.Block) - .statements.find(isNamedFunctionDeclaration)!; - expect(host.isClass(innerNode)).toBe(true); - }); - - it('should return false if a given node is a function declaration', () => { - loadTestFiles([FOO_FUNCTION_FILE]); - const bundle = makeTestBundleProgram(FOO_FUNCTION_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const node = getDeclaration( - bundle.program, FOO_FUNCTION_FILE.name, 'foo', isNamedFunctionDeclaration); - expect(host.isClass(node)).toBe(false); - }); - }); - - describe('hasBaseClass()', () => { - function hasBaseClass(source: string) { - const file = { - name: _('/synthesized_constructors.js'), - contents: source, - }; - - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - return host.hasBaseClass(classNode); - } - - it('should consider an IIFE with _super parameter as having a base class', () => { - const result = hasBaseClass(` - var TestClass = /** @class */ (function (_super) { + it('should consider an IIFE with _super parameter as having a base class', () => { + const result = hasBaseClass(` + ${expDecl('TestClass')} = /** @class */ (function (_super) { __extends(TestClass, _super); function TestClass() {} return TestClass; }(null));`); - expect(result).toBe(true); - }); + expect(result).toBe(true); + }); - it('should consider an IIFE with a unique name generated for the _super parameter as having a base class', - () => { - const result = hasBaseClass(` - var TestClass = /** @class */ (function (_super_1) { + it('should consider an IIFE with a unique name generated for the _super parameter as having a base class', + () => { + const result = hasBaseClass(` + ${expDecl('TestClass')} = /** @class */ (function (_super_1) { __extends(TestClass, _super_1); function TestClass() {} return TestClass; }(null));`); - expect(result).toBe(true); - }); + expect(result).toBe(true); + }); - it('should not consider an IIFE without parameter as having a base class', () => { - const result = hasBaseClass(` - var TestClass = /** @class */ (function () { + it('should not consider an IIFE without parameter as having a base class', () => { + const result = hasBaseClass(` + ${expDecl('TestClass')} = /** @class */ (function () { __extends(TestClass, _super); function TestClass() {} return TestClass; }(null));`); - expect(result).toBe(false); + expect(result).toBe(false); + }); }); - }); - describe('getBaseClassExpression()', () => { - function getBaseClassIdentifier(source: string): ts.Identifier|null { - const file = { - name: _('/synthesized_constructors.js'), - contents: source, - }; + describe('getBaseClassExpression()', () => { + function getBaseClassIdentifier(source: string): ts.Identifier|null { + const file = { + name: _('/synthesized_constructors.js'), + contents: source, + }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - const expression = host.getBaseClassExpression(classNode); - if (expression !== null && !ts.isIdentifier(expression)) { - throw new Error( - 'Expected class to inherit via an identifier but got: ' + expression.getText()); + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = + getDeclaration(bundle.program, file.name, 'TestClass', isDesiredDeclaration); + const expression = host.getBaseClassExpression(classNode); + if (expression !== null && !ts.isIdentifier(expression)) { + throw new Error( + 'Expected class to inherit via an identifier but got: ' + expression.getText()); + } + return expression; } - return expression; - } - it('should find the base class of an IIFE with _super parameter', () => { - const identifier = getBaseClassIdentifier(` - var BaseClass = /** @class */ (function () { + it('should find the base class of an IIFE with _super parameter', () => { + const identifier = getBaseClassIdentifier(` + ${expDecl('BaseClass')} = /** @class */ (function () { function BaseClass() {} return BaseClass; }()); - var TestClass = /** @class */ (function (_super) { + ${expDecl('TestClass')} = /** @class */ (function (_super) { __extends(TestClass, _super); function TestClass() {} return TestClass; }(BaseClass));`); - expect(identifier!.text).toBe('BaseClass'); - }); + expect(identifier!.text).toBe('BaseClass'); + }); - it('should find the base class of an IIFE with a unique name generated for the _super parameter', - () => { - const identifier = getBaseClassIdentifier(` - var BaseClass = /** @class */ (function () { + it('should find the base class of an IIFE with a unique name generated for the _super parameter', + () => { + const identifier = getBaseClassIdentifier(` + ${expDecl('BaseClass')} = /** @class */ (function () { function BaseClass() {} return BaseClass; }()); - var TestClass = /** @class */ (function (_super_1) { + ${expDecl('TestClass')} = /** @class */ (function (_super_1) { __extends(TestClass, _super_1); function TestClass() {} return TestClass; }(BaseClass));`); - expect(identifier!.text).toBe('BaseClass'); - }); + expect(identifier!.text).toBe('BaseClass'); + }); - it('should not find a base class for an IIFE without parameter', () => { - const identifier = getBaseClassIdentifier(` - var BaseClass = /** @class */ (function () { + it('should not find a base class for an IIFE without parameter', () => { + const identifier = getBaseClassIdentifier(` + ${expDecl('BaseClass')} = /** @class */ (function () { function BaseClass() {} return BaseClass; }()); - var TestClass = /** @class */ (function () { + ${expDecl('TestClass')} = /** @class */ (function () { __extends(TestClass, _super); function TestClass() {} return TestClass; }(BaseClass));`); - expect(identifier).toBe(null); - }); + expect(identifier).toBe(null); + }); - it('should find a dynamic base class expression of an IIFE', () => { - const file = { - name: _('/synthesized_constructors.js'), - contents: ` - var BaseClass = /** @class */ (function () { + it('should find a dynamic base class expression of an IIFE', () => { + const file = { + name: _('/synthesized_constructors.js'), + contents: ` + ${expDecl('BaseClass')} = /** @class */ (function () { function BaseClass() {} return BaseClass; }()); function foo() { return BaseClass; } - var TestClass = /** @class */ (function (_super) { + ${expDecl('TestClass')} = /** @class */ (function (_super) { __extends(TestClass, _super); function TestClass() {} return TestClass; }(foo()));`, - }; + }; - loadTestFiles([file]); - const bundle = makeTestBundleProgram(file.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const classNode = - getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration); - const expression = host.getBaseClassExpression(classNode)!; - expect(expression.getText()).toBe('foo()'); + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const classNode = + getDeclaration(bundle.program, file.name, 'TestClass', isDesiredDeclaration); + const expression = host.getBaseClassExpression(classNode)!; + expect(expression.getText()).toBe('foo()'); + }); + }); + + describe('findClassSymbols()', () => { + it('should return an array of all classes in the given source file', () => { + loadTestFiles(DECORATED_FILES); + const bundle = makeTestBundleProgram(DECORATED_FILES[0].name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); + const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); + + const classSymbolsPrimary = host.findClassSymbols(primaryFile); + expect(classSymbolsPrimary.length).toEqual(2); + expect(classSymbolsPrimary.map(c => c.name)).toEqual(['A', 'B']); + + const classSymbolsSecondary = host.findClassSymbols(secondaryFile); + expect(classSymbolsSecondary.length).toEqual(1); + expect(classSymbolsSecondary.map(c => c.name)).toEqual(['D']); + }); + }); + + describe('getDecoratorsOfSymbol()', () => { + it('should return decorators of class symbol', () => { + loadTestFiles(DECORATED_FILES); + const bundle = makeTestBundleProgram(DECORATED_FILES[0].name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); + const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); + + const classSymbolsPrimary = host.findClassSymbols(primaryFile); + const classDecoratorsPrimary = + classSymbolsPrimary.map(s => host.getDecoratorsOfSymbol(s)); + expect(classDecoratorsPrimary.length).toEqual(2); + expect(classDecoratorsPrimary[0]!.map(d => d.name)).toEqual(['Directive']); + expect(classDecoratorsPrimary[1]!.map(d => d.name)).toEqual(['Directive']); + + const classSymbolsSecondary = host.findClassSymbols(secondaryFile); + const classDecoratorsSecondary = + classSymbolsSecondary.map(s => host.getDecoratorsOfSymbol(s)); + expect(classDecoratorsSecondary.length).toEqual(1); + expect(classDecoratorsSecondary[0]!.map(d => d.name)).toEqual(['Directive']); + }); + }); + + describe('getDtsDeclaration()', () => { + it('should find the dts declaration that has the same relative path to the source file', + () => { + loadTestFiles(TYPINGS_SRC_FILES); + loadTestFiles(TYPINGS_DTS_FILES); + const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); + const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); + const class1 = getDeclaration( + bundle.program, _('/ep/src/class1.js'), 'Class1', isDesiredDeclaration); + + const dtsDeclaration = host.getDtsDeclaration(class1); + expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts')); + }); + + it('should find the dts declaration for exported functions', () => { + loadTestFiles(TYPINGS_SRC_FILES); + loadTestFiles(TYPINGS_DTS_FILES); + const bundle = makeTestBundleProgram(_('/ep/src/func1.js')); + const dts = makeTestBundleProgram(_('/ep/typings/func1.d.ts')); + const mooFn = + getDeclaration(bundle.program, _('/ep/src/func1.js'), 'mooFn', isExportsDeclaration); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); + + const dtsDeclaration = host.getDtsDeclaration(mooFn); + expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/func1.d.ts')); + }); + + it('should return null if there is no matching class in the matching dts file', () => { + loadTestFiles(TYPINGS_SRC_FILES); + loadTestFiles(TYPINGS_DTS_FILES); + const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); + const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); + const missingClass = getDeclaration( + bundle.program, _('/ep/src/class1.js'), 'MissingClass1', isDesiredDeclaration); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); + expect(host.getDtsDeclaration(missingClass)).toBe(null); + }); + + it('should return null if there is no matching dts file', () => { + loadTestFiles(TYPINGS_SRC_FILES); + loadTestFiles(TYPINGS_DTS_FILES); + const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); + const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); + const missingClass = getDeclaration( + bundle.program, _('/ep/src/missing-class.js'), 'MissingClass2', isDesiredDeclaration); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); + + expect(host.getDtsDeclaration(missingClass)).toBe(null); + }); + + + + it('should find the dts file that contains a matching class declaration, even if the source files do not match', + () => { + loadTestFiles(TYPINGS_SRC_FILES); + loadTestFiles(TYPINGS_DTS_FILES); + const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); + const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); + const class1 = getDeclaration( + bundle.program, _('/ep/src/flat-file.js'), 'Class1', isDesiredDeclaration); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); + + const dtsDeclaration = host.getDtsDeclaration(class1); + expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts')); + }); + + it('should find aliased exports', () => { + loadTestFiles(TYPINGS_SRC_FILES); + loadTestFiles(TYPINGS_DTS_FILES); + const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); + const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); + const sourceClass = getDeclaration( + bundle.program, _('/ep/src/flat-file.js'), 'AliasedClass', isExportsDeclaration); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); + + const dtsDeclaration = host.getDtsDeclaration(sourceClass); + if (dtsDeclaration === null) { + return fail('Expected dts class to be found'); + } + if (!isNamedClassDeclaration(dtsDeclaration)) { + return fail('Expected a named class to be found.'); + } + expect(dtsDeclaration.name.text).toEqual('TypingsClass'); + expect(_(dtsDeclaration.getSourceFile().fileName)) + .toEqual(_('/ep/typings/typings-class.d.ts')); + }); + + it('should match publicly and internal exported classes correctly, even if they have the same name', + () => { + loadTestFiles(TYPINGS_SRC_FILES); + loadTestFiles(TYPINGS_DTS_FILES); + const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); + const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); + const class2 = getDeclaration( + bundle.program, _('/ep/src/class2.js'), 'Class2', isDesiredDeclaration); + const class2DtsDeclaration = host.getDtsDeclaration(class2); + expect(class2DtsDeclaration!.getSourceFile().fileName) + .toEqual(_('/ep/typings/class2.d.ts')); + + const internalClass2 = getDeclaration( + bundle.program, _('/ep/src/internal.js'), 'Class2', isDesiredDeclaration); + const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2); + expect(internalClass2DtsDeclaration!.getSourceFile().fileName) + .toEqual(_('/ep/typings/internal.d.ts')); + }); + + it('should prefer the publicly exported class if there are multiple classes with the same name', + () => { + loadTestFiles(TYPINGS_SRC_FILES); + loadTestFiles(TYPINGS_DTS_FILES); + const bundle = makeTestBundleProgram(_('/ep/src/index.js')); + const dts = makeTestBundleProgram(_('/ep/typings/index.d.ts')); + const class2 = getDeclaration( + bundle.program, _('/ep/src/class2.js'), 'Class2', isDesiredDeclaration); + const internalClass2 = getDeclaration( + bundle.program, _('/ep/src/internal.js'), 'Class2', isDesiredDeclaration); + const host = + createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); + + const class2DtsDeclaration = host.getDtsDeclaration(class2); + expect(class2DtsDeclaration!.getSourceFile().fileName) + .toEqual(_('/ep/typings/class2.d.ts')); + + const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2); + expect(internalClass2DtsDeclaration!.getSourceFile().fileName) + .toEqual(_('/ep/typings/internal.d.ts')); + }); + }); + + describe('getInternalNameOfClass()', () => { + it('should return the name of the inner class declaration', () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + + const emptyClass = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); + expect(host.getInternalNameOfClass(emptyClass).text).toEqual('EmptyClass'); + + const class1 = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isDesiredDeclaration); + expect(host.getInternalNameOfClass(class1).text).toEqual('InnerClass1'); + + const class2 = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isDesiredDeclaration); + expect(host.getInternalNameOfClass(class2).text).toEqual('InnerClass2'); + + const childClass = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isDesiredDeclaration); + expect(host.getInternalNameOfClass(childClass).text).toEqual('InnerChildClass'); + }); + }); + + describe('getAdjacentNameOfClass()', () => { + it('should return the name of the inner class declaration', () => { + loadTestFiles([SIMPLE_CLASS_FILE]); + const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + + const emptyClass = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isDesiredDeclaration); + expect(host.getAdjacentNameOfClass(emptyClass).text).toEqual('EmptyClass'); + + const class1 = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isDesiredDeclaration); + expect(host.getAdjacentNameOfClass(class1).text).toEqual('InnerClass1'); + + const class2 = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isDesiredDeclaration); + expect(host.getAdjacentNameOfClass(class2).text).toEqual('InnerClass2'); + + const childClass = getDeclaration( + bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isDesiredDeclaration); + expect(host.getAdjacentNameOfClass(childClass).text).toEqual('InnerChildClass'); + }); }); }); - - describe('findClassSymbols()', () => { - it('should return an array of all classes in the given source file', () => { - loadTestFiles(DECORATED_FILES); - const bundle = makeTestBundleProgram(DECORATED_FILES[0].name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); - const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); - - const classSymbolsPrimary = host.findClassSymbols(primaryFile); - expect(classSymbolsPrimary.length).toEqual(2); - expect(classSymbolsPrimary.map(c => c.name)).toEqual(['A', 'B']); - - const classSymbolsSecondary = host.findClassSymbols(secondaryFile); - expect(classSymbolsSecondary.length).toEqual(1); - expect(classSymbolsSecondary.map(c => c.name)).toEqual(['D']); - }); - }); - - describe('getDecoratorsOfSymbol()', () => { - it('should return decorators of class symbol', () => { - loadTestFiles(DECORATED_FILES); - const bundle = makeTestBundleProgram(DECORATED_FILES[0].name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - const primaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[0].name); - const secondaryFile = getSourceFileOrError(bundle.program, DECORATED_FILES[1].name); - - const classSymbolsPrimary = host.findClassSymbols(primaryFile); - const classDecoratorsPrimary = classSymbolsPrimary.map(s => host.getDecoratorsOfSymbol(s)); - expect(classDecoratorsPrimary.length).toEqual(2); - expect(classDecoratorsPrimary[0]!.map(d => d.name)).toEqual(['Directive']); - expect(classDecoratorsPrimary[1]!.map(d => d.name)).toEqual(['Directive']); - - const classSymbolsSecondary = host.findClassSymbols(secondaryFile); - const classDecoratorsSecondary = - classSymbolsSecondary.map(s => host.getDecoratorsOfSymbol(s)); - expect(classDecoratorsSecondary.length).toEqual(1); - expect(classDecoratorsSecondary[0]!.map(d => d.name)).toEqual(['Directive']); - }); - }); - - describe('getDtsDeclaration()', () => { - it('should find the dts declaration that has the same relative path to the source file', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const class1 = getDeclaration( - bundle.program, _('/ep/src/class1.js'), 'Class1', ts.isVariableDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(class1); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts')); - }); - - it('should find the dts declaration for exported functions', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/func1.js')); - const dts = makeTestBundleProgram(_('/ep/typings/func1.d.ts')); - const mooFn = getDeclaration( - bundle.program, _('/ep/src/func1.js'), 'mooFn', ts.isFunctionDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(mooFn); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/func1.d.ts')); - }); - - it('should return null if there is no matching class in the matching dts file', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const missingClass = getDeclaration( - bundle.program, _('/ep/src/class1.js'), 'MissingClass1', ts.isVariableDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - expect(host.getDtsDeclaration(missingClass)).toBe(null); - }); - - it('should return null if there is no matching dts file', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const missingClass = getDeclaration( - bundle.program, _('/ep/src/missing-class.js'), 'MissingClass2', - ts.isVariableDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - expect(host.getDtsDeclaration(missingClass)).toBe(null); - }); - - it('should find the dts file that contains a matching class declaration, even if the source files do not match', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const class1 = getDeclaration( - bundle.program, _('/ep/src/flat-file.js'), 'Class1', ts.isVariableDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(class1); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts')); - }); - - it('should find the dts file that contains a matching class declaration, even if the source files do not match', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const class1 = getDeclaration( - bundle.program, _('/ep/src/flat-file.js'), 'Class1', ts.isVariableDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(class1); - expect(dtsDeclaration!.getSourceFile().fileName).toEqual(_('/ep/typings/class1.d.ts')); - }); - - it('should find aliased exports', () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/flat-file.js')); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const sourceClass = getDeclaration( - bundle.program, _('/ep/src/flat-file.js'), 'SourceClass', ts.isVariableDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - const dtsDeclaration = host.getDtsDeclaration(sourceClass); - if (dtsDeclaration === null) { - return fail('Expected dts class to be found'); - } - if (!isNamedClassDeclaration(dtsDeclaration)) { - return fail('Expected a named class to be found.'); - } - expect(dtsDeclaration.name.text).toEqual('TypingsClass'); - expect(_(dtsDeclaration.getSourceFile().fileName)) - .toEqual(_('/ep/typings/typings-class.d.ts')); - }); - - it('should match publicly and internal exported classes correctly, even if they have the same name', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(getRootFiles(TYPINGS_SRC_FILES)[0]); - const dts = makeTestBundleProgram(getRootFiles(TYPINGS_DTS_FILES)[0]); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - const class2 = getDeclaration( - bundle.program, _('/ep/src/class2.js'), 'Class2', isNamedVariableDeclaration); - const class2DtsDeclaration = host.getDtsDeclaration(class2); - expect(class2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/class2.d.ts')); - - const internalClass2 = getDeclaration( - bundle.program, _('/ep/src/internal.js'), 'Class2', isNamedVariableDeclaration); - const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2); - expect(internalClass2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/internal.d.ts')); - }); - - it('should prefer the publicly exported class if there are multiple classes with the same name', - () => { - loadTestFiles(TYPINGS_SRC_FILES); - loadTestFiles(TYPINGS_DTS_FILES); - const bundle = makeTestBundleProgram(_('/ep/src/index.js')); - const dts = makeTestBundleProgram(_('/ep/typings/index.d.ts')); - const class2 = getDeclaration( - bundle.program, _('/ep/src/class2.js'), 'Class2', ts.isVariableDeclaration); - const internalClass2 = getDeclaration( - bundle.program, _('/ep/src/internal.js'), 'Class2', ts.isVariableDeclaration); - const host = - createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle, dts)); - - const class2DtsDeclaration = host.getDtsDeclaration(class2); - expect(class2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/class2.d.ts')); - - const internalClass2DtsDeclaration = host.getDtsDeclaration(internalClass2); - expect(internalClass2DtsDeclaration!.getSourceFile().fileName) - .toEqual(_('/ep/typings/internal.d.ts')); - }); - }); - - describe('getInternalNameOfClass()', () => { - it('should return the name of the inner class declaration', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - - const emptyClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(emptyClass).text).toEqual('EmptyClass'); - - const class1 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(class1).text).toEqual('InnerClass1'); - - const class2 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(class2).text).toEqual('InnerClass2'); - - const childClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration); - expect(host.getInternalNameOfClass(childClass).text).toEqual('InnerChildClass'); - }); - }); - - describe('getAdjacentNameOfClass()', () => { - it('should return the name of the inner class declaration', () => { - loadTestFiles([SIMPLE_CLASS_FILE]); - const bundle = makeTestBundleProgram(SIMPLE_CLASS_FILE.name); - const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); - - const emptyClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'EmptyClass', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(emptyClass).text).toEqual('EmptyClass'); - - const class1 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass1', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(class1).text).toEqual('InnerClass1'); - - const class2 = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'OuterClass2', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(class2).text).toEqual('InnerClass2'); - - const childClass = getDeclaration( - bundle.program, SIMPLE_CLASS_FILE.name, 'ChildClass', isNamedVariableDeclaration); - expect(host.getAdjacentNameOfClass(childClass).text).toEqual('InnerChildClass'); - }); - }); - }); + } }); type WalkerPredicate = (node: ts.Node) => node is T;