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:

committed by
Ben Lesh

parent
410151b07f
commit
9277afce61
@ -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",
|
||||
|
@ -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> => {
|
||||
|
@ -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};
|
||||
}
|
||||
|
Reference in New Issue
Block a user