diff --git a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts index fe498123ec..d7b873752b 100644 --- a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts +++ b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts @@ -634,7 +634,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N if (!this.preprocessedSourceFiles.has(sourceFile)) { this.preprocessedSourceFiles.add(sourceFile); - for (const statement of sourceFile.statements) { + for (const statement of this.getModuleStatements(sourceFile)) { this.preprocessStatement(statement); } } @@ -660,7 +660,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N const declaration = declarations[0]; const initializer = declaration.initializer; if (!ts.isIdentifier(declaration.name) || !initializer || !isAssignment(initializer) || - !ts.isIdentifier(initializer.left) || !ts.isClassExpression(initializer.right)) { + !ts.isIdentifier(initializer.left) || !this.isClass(declaration)) { return; } diff --git a/packages/compiler-cli/ngcc/src/host/esm5_host.ts b/packages/compiler-cli/ngcc/src/host/esm5_host.ts index ad14081330..725cf4e2ae 100644 --- a/packages/compiler-cli/ngcc/src/host/esm5_host.ts +++ b/packages/compiler-cli/ngcc/src/host/esm5_host.ts @@ -295,8 +295,8 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost { * @param checker the TS program TypeChecker * @returns the inner function declaration or `undefined` if it is not a "class". */ - protected getInnerFunctionDeclarationFromClassDeclaration(decl: ts.Declaration): ts.FunctionDeclaration - |undefined { + protected getInnerFunctionDeclarationFromClassDeclaration(decl: ts.Declaration): + ts.FunctionDeclaration|undefined { // Extract the IIFE body (if any). const iifeBody = getIifeBody(decl); if (!iifeBody) return undefined; @@ -605,7 +605,15 @@ export function getIifeBody(declaration: ts.Declaration): ts.Block|undefined { return undefined; } - const call = stripParentheses(declaration.initializer); + // Recognize a variable declaration of one of the forms: + // - `var MyClass = (function () { ... }());` + // - `var MyClass = MyClass_1 = (function () { ... }());` + let parenthesizedCall = declaration.initializer; + while (isAssignment(parenthesizedCall)) { + parenthesizedCall = parenthesizedCall.right; + } + + const call = stripParentheses(parenthesizedCall); if (!ts.isCallExpression(call)) { return undefined; } diff --git a/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts b/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts index 25457342f7..b824d3d486 100644 --- a/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts @@ -795,7 +795,8 @@ exports.MissingClass2 = MissingClass2; contents: ` var functions = require('./functions'); var methods = require('./methods'); - var aliased_class = require('./aliased_class'); + var outer_aliased_class = require('./outer_aliased_class'); + var inner_aliased_class = require('./inner_aliased_class'); ` }, { @@ -877,7 +878,19 @@ exports.InternalModule = InternalModule; ` }, { - name: _('/src/aliased_class.js'), + name: _('/src/outer_aliased_class.js'), + contents: ` +var AliasedModule = AliasedModule_1 = (function() { + function AliasedModule() {} + return AliasedModule; +}()); +AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; }; +exports.AliasedModule = AliasedModule; +var AliasedModule_1; + ` + }, + { + name: _('/src/inner_aliased_class.js'), contents: ` var AliasedModule = (function() { function AliasedModule() {} @@ -1670,6 +1683,35 @@ exports.ExternalModule = ExternalModule; expect(actualDeclaration !.viaModule).toBe(null); }); + it('should return the correct declaration for an outer alias identifier', () => { + const PROGRAM_FILE: TestFile = { + name: _('/test.js'), + contents: ` + var AliasedClass = AliasedClass_1 = (function () { + function InnerClass() { + } + return InnerClass; + }()); + var AliasedClass_1; + `, + }; + + loadTestFiles([PROGRAM_FILE]); + const bundle = makeTestBundleProgram(PROGRAM_FILE.name); + const host = new CommonJsReflectionHost(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); + + 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]); @@ -2403,17 +2445,30 @@ exports.ExternalModule = ExternalModule; ]); }); + it('should resolve aliased module references to their original declaration (outer alias)', + () => { + loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM); + const bundle = makeTestBundleProgram(_('/src/index.js')); + const host = new CommonJsReflectionHost(new MockLogger(), false, bundle); + const file = getSourceFileOrError(bundle.program, _('/src/outer_aliased_class.js')); + const fn = host.getModuleWithProvidersFunctions(file); + expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([ + ['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'], + ]); + }); + // https://github.com/angular/angular/issues/29078 - it('should resolve aliased module references to their original declaration', () => { - loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM); - const bundle = makeTestBundleProgram(_('/src/index.js')); - const host = new CommonJsReflectionHost(new MockLogger(), false, bundle); - const file = getSourceFileOrError(bundle.program, _('/src/aliased_class.js')); - const fn = host.getModuleWithProvidersFunctions(file); - expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([ - ['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'], - ]); - }); + it('should resolve aliased module references to their original declaration (inner alias)', + () => { + loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM); + const bundle = makeTestBundleProgram(_('/src/index.js')); + const host = new CommonJsReflectionHost(new MockLogger(), false, bundle); + const file = getSourceFileOrError(bundle.program, _('/src/inner_aliased_class.js')); + const fn = host.getModuleWithProvidersFunctions(file); + expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([ + ['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'], + ]); + }); }); }); }); diff --git a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts b/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts index 845e5358f5..128875fb29 100644 --- a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts @@ -772,7 +772,8 @@ runInEachFileSystem(() => { contents: ` import * as functions from './functions'; import * as methods from './methods'; - import * as aliased_class from './aliased_class'; + import * as outer_aliased_class from './outer_aliased_class'; + import * as inner_aliased_class from './inner_aliased_class'; ` }, { @@ -842,7 +843,19 @@ runInEachFileSystem(() => { ` }, { - name: _('/src/aliased_class.js'), + name: _('/src/outer_aliased_class.js'), + contents: ` + var AliasedModule = AliasedModule_1 = (function() { + function AliasedModule() {} + return AliasedModule; + }()); + AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; }; + export { AliasedModule }; + var AliasedModule_1; + ` + }, + { + name: _('/src/inner_aliased_class.js'), contents: ` var AliasedModule = (function() { function AliasedModule() {} @@ -2028,6 +2041,34 @@ runInEachFileSystem(() => { expect(superGetDeclarationOfIdentifierSpy).toHaveBeenCalledTimes(2); }); + it('should return the correct declaration for an outer alias identifier', () => { + const PROGRAM_FILE: TestFile = { + name: _('/test.js'), + contents: ` + var AliasedClass = AliasedClass_1 = (function () { + function InnerClass() { + } + return InnerClass; + }()); + var AliasedClass_1; + `, + }; + + loadTestFiles([PROGRAM_FILE]); + const bundle = makeTestBundleProgram(PROGRAM_FILE.name); + const host = new Esm5ReflectionHost(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) !; + + expect(aliasIdentifier.getText()).toBe('AliasedClass_1'); + expect(actualDeclaration.node !.getText()).toBe(expectedDeclaration.getText()); + }); + it('should return the correct outer declaration for an aliased inner class declaration inside an ES5 IIFE', () => { // Note that the inner class declaration `function FroalaEditorModule() {}` is aliased @@ -2672,17 +2713,30 @@ runInEachFileSystem(() => { ]); }); + it('should resolve aliased module references to their original declaration (outer alias)', + () => { + loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM); + const bundle = makeTestBundleProgram(_('/src/index.js')); + const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); + const file = getSourceFileOrError(bundle.program, _('/src/outer_aliased_class.js')); + const fn = host.getModuleWithProvidersFunctions(file); + expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([ + ['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'], + ]); + }); + // https://github.com/angular/angular/issues/29078 - it('should resolve aliased module references to their original declaration', () => { - loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM); - const bundle = makeTestBundleProgram(_('/src/index.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); - const file = getSourceFileOrError(bundle.program, _('/src/aliased_class.js')); - const fn = host.getModuleWithProvidersFunctions(file); - expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([ - ['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'], - ]); - }); + it('should resolve aliased module references to their original declaration (inner alias)', + () => { + loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM); + const bundle = makeTestBundleProgram(_('/src/index.js')); + const host = new Esm5ReflectionHost(new MockLogger(), false, bundle); + const file = getSourceFileOrError(bundle.program, _('/src/inner_aliased_class.js')); + const fn = host.getModuleWithProvidersFunctions(file); + expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([ + ['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'], + ]); + }); }); describe('getEndOfClass()', () => { 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 e7cfee9199..fba66dbc49 100644 --- a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts @@ -929,10 +929,10 @@ runInEachFileSystem(() => { name: _('/src/index.js'), contents: ` (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./functions'), require('./methods'), require('./aliased_class')) : - typeof define === 'function' && define.amd ? define('index', ['exports', './functions', './methods', './aliased_class'], factory) : - (factory(global.index,global.functions,global.methods,global.aliased_class)); - }(this, (function (exports,functions,methods,aliased_class) { 'use strict'; + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./functions'), require('./methods'), require('./outer_aliased_class'), require('./inner_aliased_class')) : + typeof define === 'function' && define.amd ? define('index', ['exports', './functions', './methods', './outer_aliased_class', './inner_aliased_class'], factory) : + (factory(global.index,global.functions,global.methods,global.outer_aliased_class,global.inner_aliased_class)); + }(this, (function (exports,functions,methods,outer_aliased_class,inner_aliased_class) { 'use strict'; })))); `, }, @@ -1025,12 +1025,30 @@ runInEachFileSystem(() => { ` }, { - name: _('/src/aliased_class.js'), + name: _('/src/outer_aliased_class.js'), contents: ` (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define('aliased_class', ['exports'], factory) : - (factory(global.aliased_class)); + typeof define === 'function' && define.amd ? define('outer_aliased_class', ['exports'], factory) : + (factory(global.outer_aliased_class)); + }(this, (function (exports,module) { 'use strict'; + var AliasedModule = AliasedModule_1 = (function() { + function AliasedModule() {} + return AliasedModule; + }()); + AliasedModule.forRoot = function() { return { ngModule: AliasedModule_1 }; }; + exports.AliasedModule = AliasedModule; + var AliasedModule_1; + }))); + ` + }, + { + name: _('/src/inner_aliased_class.js'), + contents: ` + (function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define('inner_aliased_class', ['exports'], factory) : + (factory(global.inner_aliased_class)); }(this, (function (exports,module) { 'use strict'; var AliasedModule = (function() { function AliasedModule() {} @@ -1831,6 +1849,41 @@ runInEachFileSystem(() => { expect(actualDeclaration !.viaModule).toBe(null); }); + 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 () { + function InnerClass() { + } + return InnerClass; + }()); + var AliasedClass_1; + }))); + `, + }; + + loadTestFiles([PROGRAM_FILE]); + const bundle = makeTestBundleProgram(PROGRAM_FILE.name); + const host = 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); + + 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]); @@ -2642,17 +2695,30 @@ runInEachFileSystem(() => { ]); }); + it('should resolve aliased module references to their original declaration (outer alias)', + () => { + loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM); + const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]); + const host = new UmdReflectionHost(new MockLogger(), false, bundle); + const file = getSourceFileOrError(bundle.program, _('/src/outer_aliased_class.js')); + const fn = host.getModuleWithProvidersFunctions(file); + expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([ + ['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'], + ]); + }); + // https://github.com/angular/angular/issues/29078 - it('should resolve aliased module references to their original declaration', () => { - loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM); - const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]); - const host = new UmdReflectionHost(new MockLogger(), false, bundle); - const file = getSourceFileOrError(bundle.program, _('/src/aliased_class.js')); - const fn = host.getModuleWithProvidersFunctions(file); - expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([ - ['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'], - ]); - }); + it('should resolve aliased module references to their original declaration (inner alias)', + () => { + loadTestFiles(MODULE_WITH_PROVIDERS_PROGRAM); + const bundle = makeTestBundleProgram(getRootFiles(MODULE_WITH_PROVIDERS_PROGRAM)[0]); + const host = new UmdReflectionHost(new MockLogger(), false, bundle); + const file = getSourceFileOrError(bundle.program, _('/src/inner_aliased_class.js')); + const fn = host.getModuleWithProvidersFunctions(file); + expect(fn.map(fn => [fn.declaration.getText(), fn.ngModule.node.name.text])).toEqual([ + ['function() { return { ngModule: AliasedModule_1 }; }', 'AliasedModule'], + ]); + }); }); }); });