fix(ivy): consider exported modules from other compilation scopes (#25425)

PR Close #25425
This commit is contained in:
Alex Rickabaugh 2018-08-10 12:43:20 +01:00 committed by Ben Lesh
parent b40c437379
commit 26066f282e
2 changed files with 87 additions and 18 deletions

View File

@ -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];
}

View File

@ -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<SomeModule, [typeof i0.SomeCmp], never, [typeof i0.SomeCmp]>;
}
export declare class SomeCmp {
static ngComponentDef: NgComponentDef<SomeCmp, 'some-cmp'>;
}
`
},
{
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<SomeCmp, 'some-cmp'>;
}
`
},
{
@ -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<A, B> {}
export interface NgModuleDef<A, B, C, D> {}
`
},
{
name: 'node_modules/some_library/index.d.ts',
contents: `
import {NgComponentDef, NgModuleDef} from '@angular/core';
export declare class SomeModule {
static ngModuleDef: NgModuleDef<SomeModule, [typeof SomeCmp], never, [typeof SomeCmp]>;
}
export declare class SomeCmp {
static ngComponentDef: NgComponentDef<SomeCmp, 'some-cmp'>;
}
`
},
{
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);
});
});