From 26066f282ed4d6f9a741a8ee5ff98d2a778b9906 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Fri, 10 Aug 2018 12:43:20 +0100 Subject: [PATCH] fix(ivy): consider exported modules from other compilation scopes (#25425) PR Close #25425 --- .../ngtsc/annotations/src/selector_scope.ts | 33 ++++++--- .../annotations/test/selector_scope_spec.ts | 72 +++++++++++++++++-- 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts b/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts index 62f97cb4c5..07e61af38a 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/selector_scope.ts @@ -157,7 +157,7 @@ export class SelectorScopeRegistry { // Process the declaration scope of the module, and lookup the selector of every declared type. // The initial value of ngModuleImportedFrom is 'null' which signifies that the NgModule // was not imported from a .d.ts source. - this.lookupScopes(module !, /* ngModuleImportedFrom */ null).compilation.forEach(ref => { + this.lookupScopesOrDie(module !, /* ngModuleImportedFrom */ null).compilation.forEach(ref => { const node = ts.getOriginalNode(ref.node) as ts.Declaration; // Either the node represents a directive or a pipe. Look for both. @@ -183,6 +183,15 @@ export class SelectorScopeRegistry { return convertScopeToExpressions(scope, node); } + private lookupScopesOrDie(node: ts.Declaration, ngModuleImportedFrom: string|null): + SelectorScopes { + const result = this.lookupScopes(node, ngModuleImportedFrom); + if (result === null) { + throw new Error(`Module not found: ${reflectIdentifierOfDeclaration(node)}`); + } + return result; + } + /** * Lookup `SelectorScopes` for a given module. * @@ -190,7 +199,8 @@ export class SelectorScopeRegistry { * (`ngModuleImportedFrom`) then all of its declarations are exported at that same path, as well * as imports and exports from other modules that are relatively imported. */ - private lookupScopes(node: ts.Declaration, ngModuleImportedFrom: string|null): SelectorScopes { + private lookupScopes(node: ts.Declaration, ngModuleImportedFrom: string|null): SelectorScopes + |null { let data: ModuleData|null = null; // Either this module was analyzed directly, or has a precompiled ngModuleDef. @@ -206,7 +216,7 @@ export class SelectorScopeRegistry { } if (data === null) { - throw new Error(`Module not registered: ${reflectNameOfDeclaration(node)}`); + return null; } return { @@ -214,18 +224,19 @@ export class SelectorScopeRegistry { ...data.declarations, // Expand imports to the exported scope of those imports. ...flatten(data.imports.map( - ref => - this.lookupScopes(ref.node as ts.Declaration, absoluteModuleName(ref)).exported)), + ref => this.lookupScopesOrDie(ref.node as ts.Declaration, absoluteModuleName(ref)) + .exported)), // And include the compilation scope of exported modules. ...flatten( - data.exports.filter(ref => this._moduleToData.has(ref.node as ts.Declaration)) - .map( - ref => this.lookupScopes(ref.node as ts.Declaration, absoluteModuleName(ref)) - .exported)) + data.exports + .map(ref => this.lookupScopes(ref.node as ts.Declaration, absoluteModuleName(ref))) + .filter((scope: SelectorScopes | null): scope is SelectorScopes => scope !== null) + .map(scope => scope.exported)) ], exported: flatten(data.exports.map(ref => { - if (this._moduleToData.has(ref.node as ts.Declaration)) { - return this.lookupScopes(ref.node as ts.Declaration, absoluteModuleName(ref)).exported; + const scope = this.lookupScopes(ref.node as ts.Declaration, absoluteModuleName(ref)); + if (scope !== null) { + return scope.exported; } else { return [ref]; } diff --git a/packages/compiler-cli/src/ngtsc/annotations/test/selector_scope_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/test/selector_scope_spec.ts index a4a287e444..0fab44b047 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/test/selector_scope_spec.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/test/selector_scope_spec.ts @@ -27,22 +27,22 @@ describe('SelectorScopeRegistry', () => { { name: 'node_modules/some_library/index.d.ts', contents: ` - import {NgComponentDef, NgModuleDef} from '@angular/core'; + import {NgModuleDef} from '@angular/core'; import * as i0 from './component'; export declare class SomeModule { static ngModuleDef: NgModuleDef; } - - export declare class SomeCmp { - static ngComponentDef: NgComponentDef; - } ` }, { name: 'node_modules/some_library/component.d.ts', contents: ` - export declare class SomeCmp {} + import {NgComponentDef} from '@angular/core'; + + export declare class SomeCmp { + static ngComponentDef: NgComponentDef; + } ` }, { @@ -76,6 +76,64 @@ describe('SelectorScopeRegistry', () => { const scope = registry.lookupCompilationScope(ProgramCmp) !; expect(scope).toBeDefined(); expect(scope.directives).toBeDefined(); - expect(scope.directives.size).toBe(1); + expect(scope.directives.size).toBe(2); + }); + + it('exports of third-party libs work', () => { + const {program} = makeProgram([ + { + name: 'node_modules/@angular/core/index.d.ts', + contents: ` + export interface NgComponentDef {} + export interface NgModuleDef {} + ` + }, + { + name: 'node_modules/some_library/index.d.ts', + contents: ` + import {NgComponentDef, NgModuleDef} from '@angular/core'; + + export declare class SomeModule { + static ngModuleDef: NgModuleDef; + } + + export declare class SomeCmp { + static ngComponentDef: NgComponentDef; + } + ` + }, + { + name: 'entry.ts', + contents: ` + export class ProgramCmp {} + export class ProgramModule {} + ` + }, + ]); + const checker = program.getTypeChecker(); + const host = new TypeScriptReflectionHost(checker); + const ProgramModule = + getDeclaration(program, 'entry.ts', 'ProgramModule', ts.isClassDeclaration); + const ProgramCmp = getDeclaration(program, 'entry.ts', 'ProgramCmp', ts.isClassDeclaration); + const SomeModule = getDeclaration( + program, 'node_modules/some_library/index.d.ts', 'SomeModule', ts.isClassDeclaration); + expect(ProgramModule).toBeDefined(); + expect(SomeModule).toBeDefined(); + + const registry = new SelectorScopeRegistry(checker, host); + + registry.registerModule(ProgramModule, { + declarations: [new ResolvedReference(ProgramCmp, ProgramCmp.name !)], + exports: [new AbsoluteReference(SomeModule, SomeModule.name !, 'some_library', 'SomeModule')], + imports: [], + }); + + registry.registerSelector(ProgramCmp, 'program-cmp'); + + debugger; + const scope = registry.lookupCompilationScope(ProgramCmp) !; + expect(scope).toBeDefined(); + expect(scope.directives).toBeDefined(); + expect(scope.directives.size).toBe(2); }); }); \ No newline at end of file