refactor(ivy): move metadata registration to its own package (#29698)

Previously, metadata registration (the recording of collected metadata
during analysis of directives, pipes, and NgModules) was only used to
produce the `LocalModuleScope`, and thus was handled by the
`LocalModuleScopeRegistry`.

However, the template type-checker also needs information about registered
directives, outside of the NgModule scope determinations. Rather than
reuse the scope registry for an unintended purpose, this commit introduces
new abstractions for metadata registration and lookups in a separate
'metadata' package, which the scope registry implements.

This paves the way for a future commit to make use of this metadata for the
template type-checking system.

Testing strategy: this commit is a refactoring which introduces no new
functionality, so existing tests are sufficient.

PR Close #29698
This commit is contained in:
Alex Rickabaugh
2019-03-26 14:02:16 -07:00
committed by Ben Lesh
parent 410151b07f
commit 9277afce61
27 changed files with 532 additions and 290 deletions

View File

@ -12,6 +12,7 @@ ts_library(
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/scope",
"//packages/compiler-cli/src/ngtsc/testing",

View File

@ -10,6 +10,7 @@ import {ExternalExpr, ExternalReference} from '@angular/compiler';
import * as ts from 'typescript';
import {AliasGenerator, FileToModuleHost, Reference} from '../../imports';
import {DtsMetadataReader} from '../../metadata';
import {ClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {makeProgram} from '../../testing/in_memory_typescript';
import {ExportScope} from '../src/api';
@ -54,8 +55,9 @@ function makeTestEnv(
});
const {program} = makeProgram(files);
const checker = program.getTypeChecker();
const resolver = new MetadataDtsModuleScopeResolver(
checker, new TypeScriptReflectionHost(checker), aliasGenerator);
const reflector = new TypeScriptReflectionHost(checker);
const resolver =
new MetadataDtsModuleScopeResolver(new DtsMetadataReader(checker, reflector), aliasGenerator);
// Resolver for the refs object.
const get = (target: {}, name: string): Reference<ts.ClassDeclaration> => {

View File

@ -9,12 +9,13 @@
import * as ts from 'typescript';
import {Reference, ReferenceEmitter} from '../../imports';
import {CompoundMetadataRegistry, DirectiveMeta, LocalMetadataRegistry, MetadataRegistry, PipeMeta} from '../../metadata';
import {ClassDeclaration} from '../../reflection';
import {ScopeData, ScopeDirective, ScopePipe} from '../src/api';
import {ScopeData} from '../src/api';
import {DtsModuleScopeResolver} from '../src/dependency';
import {LocalModuleScopeRegistry} from '../src/local';
function registerFakeRefs(registry: LocalModuleScopeRegistry):
function registerFakeRefs(registry: MetadataRegistry):
{[name: string]: Reference<ClassDeclaration>} {
const get = (target: {}, name: string): Reference<ClassDeclaration> => {
const sf = ts.createSourceFile(
@ -22,9 +23,9 @@ function registerFakeRefs(registry: LocalModuleScopeRegistry):
const clazz = sf.statements[0] as unknown as ClassDeclaration;
const ref = new Reference(clazz);
if (name.startsWith('Dir') || name.startsWith('Cmp')) {
registry.registerDirective(fakeDirective(ref));
registry.registerDirectiveMetadata(fakeDirective(ref));
} else if (name.startsWith('Pipe')) {
registry.registerPipe(fakePipe(ref));
registry.registerPipeMetadata(fakePipe(ref));
}
return ref;
};
@ -33,70 +34,108 @@ function registerFakeRefs(registry: LocalModuleScopeRegistry):
describe('LocalModuleScopeRegistry', () => {
const refEmitter = new ReferenceEmitter([]);
let registry !: LocalModuleScopeRegistry;
let scopeRegistry !: LocalModuleScopeRegistry;
let metaRegistry !: MetadataRegistry;
beforeEach(() => {
registry = new LocalModuleScopeRegistry(new MockDtsModuleScopeResolver(), refEmitter, null);
const localRegistry = new LocalMetadataRegistry();
scopeRegistry = new LocalModuleScopeRegistry(
localRegistry, new MockDtsModuleScopeResolver(), refEmitter, null);
metaRegistry = new CompoundMetadataRegistry([localRegistry, scopeRegistry]);
});
it('should produce an accurate LocalModuleScope for a basic NgModule', () => {
const {Dir1, Dir2, Pipe1, Module} = registerFakeRefs(registry);
const {Dir1, Dir2, Pipe1, Module} = registerFakeRefs(metaRegistry);
registry.registerNgModule(Module.node, {
metaRegistry.registerNgModuleMetadata({
ref: new Reference(Module.node),
imports: [],
declarations: [Dir1, Dir2, Pipe1],
exports: [Dir1, Pipe1],
});
const scope = registry.getScopeOfModule(Module.node) !;
const scope = scopeRegistry.getScopeOfModule(Module.node) !;
expect(scopeToRefs(scope.compilation)).toEqual([Dir1, Dir2, Pipe1]);
expect(scopeToRefs(scope.exported)).toEqual([Dir1, Pipe1]);
});
it('should produce accurate LocalModuleScopes for a complex module chain', () => {
const {DirA, DirB, DirCI, DirCE, ModuleA, ModuleB, ModuleC} = registerFakeRefs(registry);
const {DirA, DirB, DirCI, DirCE, ModuleA, ModuleB, ModuleC} = registerFakeRefs(metaRegistry);
registry.registerNgModule(
ModuleA.node, {imports: [ModuleB], declarations: [DirA], exports: []});
registry.registerNgModule(
ModuleB.node, {exports: [ModuleC, DirB], declarations: [DirB], imports: []});
registry.registerNgModule(
ModuleC.node, {declarations: [DirCI, DirCE], exports: [DirCE], imports: []});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleA.node),
imports: [ModuleB],
declarations: [DirA],
exports: [],
});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleB.node),
exports: [ModuleC, DirB],
declarations: [DirB],
imports: []
});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleC.node),
declarations: [DirCI, DirCE],
exports: [DirCE],
imports: []
});
const scopeA = registry.getScopeOfModule(ModuleA.node) !;
const scopeA = scopeRegistry.getScopeOfModule(ModuleA.node) !;
expect(scopeToRefs(scopeA.compilation)).toEqual([DirA, DirB, DirCE]);
expect(scopeToRefs(scopeA.exported)).toEqual([]);
});
it('should not treat exported modules as imported', () => {
const {Dir, ModuleA, ModuleB} = registerFakeRefs(registry);
const {Dir, ModuleA, ModuleB} = registerFakeRefs(metaRegistry);
registry.registerNgModule(ModuleA.node, {exports: [ModuleB], imports: [], declarations: []});
registry.registerNgModule(ModuleB.node, {declarations: [Dir], exports: [Dir], imports: []});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleA.node),
exports: [ModuleB],
imports: [],
declarations: [],
});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleB.node),
declarations: [Dir],
exports: [Dir],
imports: [],
});
const scopeA = registry.getScopeOfModule(ModuleA.node) !;
const scopeA = scopeRegistry.getScopeOfModule(ModuleA.node) !;
expect(scopeToRefs(scopeA.compilation)).toEqual([]);
expect(scopeToRefs(scopeA.exported)).toEqual([Dir]);
});
it('should deduplicate declarations and exports', () => {
const {DirA, ModuleA, DirB, ModuleB, ModuleC} = registerFakeRefs(registry);
const {DirA, ModuleA, DirB, ModuleB, ModuleC} = registerFakeRefs(metaRegistry);
registry.registerNgModule(ModuleA.node, {
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleA.node),
declarations: [DirA, DirA],
imports: [ModuleB, ModuleC],
exports: [DirA, DirA, DirB, ModuleB],
});
registry.registerNgModule(ModuleB.node, {declarations: [DirB], imports: [], exports: [DirB]});
registry.registerNgModule(ModuleC.node, {declarations: [], imports: [], exports: [ModuleB]});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleB.node),
declarations: [DirB],
imports: [],
exports: [DirB],
});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleC.node),
declarations: [],
imports: [],
exports: [ModuleB],
});
const scope = registry.getScopeOfModule(ModuleA.node) !;
const scope = scopeRegistry.getScopeOfModule(ModuleA.node) !;
expect(scopeToRefs(scope.compilation)).toEqual([DirA, DirB]);
expect(scopeToRefs(scope.exported)).toEqual([DirA, DirB]);
});
it('should preserve reference identities in module metadata', () => {
const {Dir, Module} = registerFakeRefs(registry);
const {Dir, Module} = registerFakeRefs(metaRegistry);
const idSf = ts.createSourceFile('id.ts', 'var id;', ts.ScriptTarget.Latest, true);
// Create a new Reference to Dir, with a special `ts.Identifier`, and register the directive
@ -105,39 +144,64 @@ describe('LocalModuleScopeRegistry', () => {
const id = idVar.declarationList.declarations[0].name as ts.Identifier;
const DirInModule = new Reference(Dir.node);
DirInModule.addIdentifier(id);
registry.registerNgModule(Module.node, {exports: [], imports: [], declarations: [DirInModule]});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(Module.node),
exports: [],
imports: [],
declarations: [DirInModule],
});
const scope = registry.getScopeOfModule(Module.node) !;
const scope = scopeRegistry.getScopeOfModule(Module.node) !;
expect(scope.compilation.directives[0].ref.getIdentityIn(idSf)).toBe(id);
});
it('should allow directly exporting a directive that\'s not imported', () => {
const {Dir, ModuleA, ModuleB} = registerFakeRefs(registry);
const {Dir, ModuleA, ModuleB} = registerFakeRefs(metaRegistry);
registry.registerNgModule(ModuleA.node, {exports: [Dir], imports: [ModuleB], declarations: []});
registry.registerNgModule(ModuleB.node, {declarations: [Dir], exports: [Dir], imports: []});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleA.node),
exports: [Dir],
imports: [ModuleB],
declarations: [],
});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleB.node),
declarations: [Dir],
exports: [Dir],
imports: [],
});
const scopeA = registry.getScopeOfModule(ModuleA.node) !;
const scopeA = scopeRegistry.getScopeOfModule(ModuleA.node) !;
expect(scopeToRefs(scopeA.exported)).toEqual([Dir]);
});
it('should not allow directly exporting a directive that\'s not imported', () => {
const {Dir, ModuleA, ModuleB} = registerFakeRefs(registry);
const {Dir, ModuleA, ModuleB} = registerFakeRefs(metaRegistry);
registry.registerNgModule(ModuleA.node, {exports: [Dir], imports: [], declarations: []});
registry.registerNgModule(ModuleB.node, {declarations: [Dir], exports: [Dir], imports: []});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleA.node),
exports: [Dir],
imports: [],
declarations: [],
});
metaRegistry.registerNgModuleMetadata({
ref: new Reference(ModuleB.node),
declarations: [Dir],
exports: [Dir],
imports: [],
});
expect(registry.getScopeOfModule(ModuleA.node)).toBe(null);
expect(scopeRegistry.getScopeOfModule(ModuleA.node)).toBe(null);
// ModuleA should have associated diagnostics as it exports `Dir` without declaring it.
expect(registry.getDiagnosticsOfModule(ModuleA.node)).not.toBeNull();
expect(scopeRegistry.getDiagnosticsOfModule(ModuleA.node)).not.toBeNull();
// ModuleB should have no diagnostics as it correctly declares `Dir`.
expect(registry.getDiagnosticsOfModule(ModuleB.node)).toBeNull();
expect(scopeRegistry.getDiagnosticsOfModule(ModuleB.node)).toBeNull();
});
});
function fakeDirective(ref: Reference<ClassDeclaration>): ScopeDirective {
function fakeDirective(ref: Reference<ClassDeclaration>): DirectiveMeta {
const name = ref.debugName !;
return {
ref,
@ -150,10 +214,11 @@ function fakeDirective(ref: Reference<ClassDeclaration>): ScopeDirective {
queries: [],
hasNgTemplateContextGuard: false,
ngTemplateGuards: [],
baseClass: null,
};
}
function fakePipe(ref: Reference<ClassDeclaration>): ScopePipe {
function fakePipe(ref: Reference<ClassDeclaration>): PipeMeta {
const name = ref.debugName !;
return {ref, name};
}