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

@ -29,6 +29,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/entry_point",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/incremental",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/path",
"//packages/compiler-cli/src/ngtsc/perf",

View File

@ -14,6 +14,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/annotations",
"//packages/compiler-cli/src/ngtsc/cycles",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/path",
"//packages/compiler-cli/src/ngtsc/perf",

View File

@ -14,6 +14,7 @@ import * as ts from 'typescript';
import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader} from '../../../src/ngtsc/annotations';
import {CycleAnalyzer, ImportGraph} from '../../../src/ngtsc/cycles';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../../src/ngtsc/imports';
import {CompoundMetadataRegistry, DtsMetadataReader, LocalMetadataRegistry} from '../../../src/ngtsc/metadata';
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
import {AbsoluteFsPath, LogicalFileSystem} from '../../../src/ngtsc/path';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope';
@ -65,6 +66,8 @@ class NgccResourceLoader implements ResourceLoader {
*/
export class DecorationAnalyzer {
resourceManager = new NgccResourceLoader();
metaRegistry = new LocalMetadataRegistry();
dtsMetaReader = new DtsMetadataReader(this.typeChecker, this.reflectionHost);
refEmitter = new ReferenceEmitter([
new LocalIdentifierStrategy(),
new AbsoluteModuleStrategy(this.program, this.typeChecker, this.options, this.host),
@ -73,10 +76,11 @@ export class DecorationAnalyzer {
// on whether a bestGuessOwningModule is present in the Reference.
new LogicalProjectStrategy(this.typeChecker, new LogicalFileSystem(this.rootDirs)),
]);
dtsModuleScopeResolver = new MetadataDtsModuleScopeResolver(
this.typeChecker, this.reflectionHost, /* aliasGenerator */ null);
dtsModuleScopeResolver =
new MetadataDtsModuleScopeResolver(this.dtsMetaReader, /* aliasGenerator */ null);
scopeRegistry = new LocalModuleScopeRegistry(
this.dtsModuleScopeResolver, this.refEmitter, /* aliasGenerator */ null);
this.metaRegistry, this.dtsModuleScopeResolver, this.refEmitter, /* aliasGenerator */ null);
fullRegistry = new CompoundMetadataRegistry([this.metaRegistry, this.scopeRegistry]);
evaluator = new PartialEvaluator(this.reflectionHost, this.typeChecker);
moduleResolver = new ModuleResolver(this.program, this.options, this.host);
importGraph = new ImportGraph(this.moduleResolver);
@ -84,20 +88,22 @@ export class DecorationAnalyzer {
handlers: DecoratorHandler<any, any>[] = [
new BaseDefDecoratorHandler(this.reflectionHost, this.evaluator, this.isCore),
new ComponentDecoratorHandler(
this.reflectionHost, this.evaluator, this.scopeRegistry, this.isCore, this.resourceManager,
this.rootDirs, /* defaultPreserveWhitespaces */ false, /* i18nUseExternalIds */ true,
this.moduleResolver, this.cycleAnalyzer, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER),
this.reflectionHost, this.evaluator, this.fullRegistry, this.scopeRegistry, this.isCore,
this.resourceManager, this.rootDirs, /* defaultPreserveWhitespaces */ false,
/* i18nUseExternalIds */ true, this.moduleResolver, this.cycleAnalyzer, this.refEmitter,
NOOP_DEFAULT_IMPORT_RECORDER),
new DirectiveDecoratorHandler(
this.reflectionHost, this.evaluator, this.scopeRegistry, NOOP_DEFAULT_IMPORT_RECORDER,
this.reflectionHost, this.evaluator, this.fullRegistry, NOOP_DEFAULT_IMPORT_RECORDER,
this.isCore),
new InjectableDecoratorHandler(
this.reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, this.isCore,
/* strictCtorDeps */ false),
new NgModuleDecoratorHandler(
this.reflectionHost, this.evaluator, this.scopeRegistry, this.referencesRegistry,
this.isCore, /* routeAnalyzer */ null, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER),
this.reflectionHost, this.evaluator, this.fullRegistry, this.scopeRegistry,
this.referencesRegistry, this.isCore, /* routeAnalyzer */ null, this.refEmitter,
NOOP_DEFAULT_IMPORT_RECORDER),
new PipeDecoratorHandler(
this.reflectionHost, this.evaluator, this.scopeRegistry, NOOP_DEFAULT_IMPORT_RECORDER,
this.reflectionHost, this.evaluator, this.metaRegistry, NOOP_DEFAULT_IMPORT_RECORDER,
this.isCore),
];

View File

@ -12,6 +12,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/cycles",
"//packages/compiler-cli/src/ngtsc/diagnostics",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/routing",

View File

@ -13,9 +13,10 @@ import * as ts from 'typescript';
import {CycleAnalyzer} from '../../cycles';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
import {DirectiveMeta, MetadataRegistry, extractDirectiveGuards} from '../../metadata';
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
import {ClassDeclaration, Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry, ScopeDirective, extractDirectiveGuards} from '../../scope';
import {LocalModuleScopeRegistry} from '../../scope';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../transform';
import {TypeCheckContext} from '../../typecheck';
import {tsSourceMapBug29300Fixed} from '../../util/src/ts_source_map_bug_29300';
@ -41,14 +42,14 @@ export class ComponentDecoratorHandler implements
DecoratorHandler<ComponentHandlerData, Decorator> {
constructor(
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private scopeRegistry: LocalModuleScopeRegistry, private isCore: boolean,
private resourceLoader: ResourceLoader, private rootDirs: string[],
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
private isCore: boolean, private resourceLoader: ResourceLoader, private rootDirs: string[],
private defaultPreserveWhitespaces: boolean, private i18nUseExternalIds: boolean,
private moduleResolver: ModuleResolver, private cycleAnalyzer: CycleAnalyzer,
private refEmitter: ReferenceEmitter, private defaultImportRecorder: DefaultImportRecorder) {}
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
private boundTemplateCache = new Map<ts.Declaration, BoundTarget<ScopeDirective>>();
private boundTemplateCache = new Map<ts.Declaration, BoundTarget<DirectiveMeta>>();
private elementSchemaRegistry = new DomElementSchemaRegistry();
/**
@ -211,7 +212,7 @@ export class ComponentDecoratorHandler implements
// determined.
if (metadata.selector !== null) {
const ref = new Reference(node);
this.scopeRegistry.registerDirective({
this.metaRegistry.registerDirectiveMetadata({
ref,
name: node.name.text,
selector: metadata.selector,
@ -220,6 +221,7 @@ export class ComponentDecoratorHandler implements
outputs: metadata.outputs,
queries: metadata.queries.map(query => query.propertyName),
isComponent: true, ...extractDirectiveGuards(node, this.reflector),
baseClass: null,
});
}
@ -302,7 +304,7 @@ export class ComponentDecoratorHandler implements
return;
}
const scope = this.scopeRegistry.getScopeForComponent(node);
const matcher = new SelectorMatcher<ScopeDirective>();
const matcher = new SelectorMatcher<DirectiveMeta>();
if (scope !== null) {
for (const meta of scope.compilation.directives) {
matcher.addSelectables(CssSelector.parse(meta.selector), meta);
@ -343,7 +345,7 @@ export class ComponentDecoratorHandler implements
// Set up the R3TargetBinder, as well as a 'directives' array and a 'pipes' map that are later
// fed to the TemplateDefinitionBuilder. First, a SelectorMatcher is constructed to match
// directives that are in scope.
const matcher = new SelectorMatcher<ScopeDirective&{expression: Expression}>();
const matcher = new SelectorMatcher<DirectiveMeta&{expression: Expression}>();
const directives: {selector: string, expression: Expression}[] = [];
for (const dir of scope.compilation.directives) {

View File

@ -11,10 +11,11 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, Reference} from '../../imports';
import {MetadataRegistry} from '../../metadata';
import {extractDirectiveGuards} from '../../metadata/src/util';
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
import {ClassDeclaration, ClassMember, ClassMemberKind, Decorator, ReflectionHost, filterToMembersWithDecorator, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope/src/local';
import {extractDirectiveGuards} from '../../scope/src/util';
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
import {generateSetClassMetadataCall} from './metadata';
@ -30,8 +31,8 @@ export class DirectiveDecoratorHandler implements
DecoratorHandler<DirectiveHandlerData, Decorator> {
constructor(
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private scopeRegistry: LocalModuleScopeRegistry,
private defaultImportRecorder: DefaultImportRecorder, private isCore: boolean) {}
private metaRegistry: MetadataRegistry, private defaultImportRecorder: DefaultImportRecorder,
private isCore: boolean) {}
readonly precedence = HandlerPrecedence.PRIMARY;
@ -59,7 +60,7 @@ export class DirectiveDecoratorHandler implements
// when this directive appears in an `@NgModule` scope, its selector can be determined.
if (analysis && analysis.selector !== null) {
const ref = new Reference(node);
this.scopeRegistry.registerDirective({
this.metaRegistry.registerDirectiveMetadata({
ref,
name: node.name.text,
selector: analysis.selector,
@ -68,6 +69,7 @@ export class DirectiveDecoratorHandler implements
outputs: analysis.outputs,
queries: analysis.queries.map(query => query.propertyName),
isComponent: false, ...extractDirectiveGuards(node, this.reflector),
baseClass: null,
});
}

View File

@ -11,6 +11,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports';
import {MetadataRegistry} from '../../metadata';
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
import {NgModuleRouteAnalyzer} from '../../routing';
@ -39,7 +40,7 @@ export interface NgModuleAnalysis {
export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalysis, Decorator> {
constructor(
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private scopeRegistry: LocalModuleScopeRegistry,
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
private referencesRegistry: ReferencesRegistry, private isCore: boolean,
private routeAnalyzer: NgModuleRouteAnalyzer|null, private refEmitter: ReferenceEmitter,
private defaultImportRecorder: DefaultImportRecorder) {}
@ -134,8 +135,12 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
// Register this module's information with the LocalModuleScopeRegistry. This ensures that
// during the compile() phase, the module's metadata is available for selector scope
// computation.
this.scopeRegistry.registerNgModule(
node, {declarations: declarationRefs, imports: importRefs, exports: exportRefs});
this.metaRegistry.registerNgModuleMetadata({
ref: new Reference(node),
declarations: declarationRefs,
imports: importRefs,
exports: exportRefs
});
const valueContext = node.getSourceFile();

View File

@ -11,6 +11,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, Reference} from '../../imports';
import {MetadataRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
import {LocalModuleScopeRegistry} from '../../scope/src/local';
@ -27,8 +28,8 @@ export interface PipeHandlerData {
export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, Decorator> {
constructor(
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
private scopeRegistry: LocalModuleScopeRegistry,
private defaultImportRecorder: DefaultImportRecorder, private isCore: boolean) {}
private metaRegistry: MetadataRegistry, private defaultImportRecorder: DefaultImportRecorder,
private isCore: boolean) {}
readonly precedence = HandlerPrecedence.PRIMARY;
@ -76,7 +77,7 @@ export class PipeDecoratorHandler implements DecoratorHandler<PipeHandlerData, D
ErrorCode.VALUE_HAS_WRONG_TYPE, pipeNameExpr, `@Pipe.name must be a string`);
}
const ref = new Reference(clazz);
this.scopeRegistry.registerPipe({ref, name: pipeName});
this.metaRegistry.registerPipeMetadata({ref, name: pipeName});
let pure = true;
if (pipe.has('pure')) {

View File

@ -15,6 +15,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/cycles",
"//packages/compiler-cli/src/ngtsc/diagnostics",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/path",
"//packages/compiler-cli/src/ngtsc/reflection",

View File

@ -11,6 +11,7 @@ import * as ts from 'typescript';
import {CycleAnalyzer, ImportGraph} from '../../cycles';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
import {DtsMetadataReader, LocalMetadataRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator';
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
@ -48,14 +49,16 @@ describe('ComponentDecoratorHandler', () => {
const moduleResolver = new ModuleResolver(program, options, host);
const importGraph = new ImportGraph(moduleResolver);
const cycleAnalyzer = new CycleAnalyzer(importGraph);
const metaRegistry = new LocalMetadataRegistry();
const dtsReader = new DtsMetadataReader(checker, reflectionHost);
const scopeRegistry = new LocalModuleScopeRegistry(
new MetadataDtsModuleScopeResolver(checker, reflectionHost, null), new ReferenceEmitter([]),
metaRegistry, new MetadataDtsModuleScopeResolver(dtsReader, null), new ReferenceEmitter([]),
null);
const refEmitter = new ReferenceEmitter([]);
const handler = new ComponentDecoratorHandler(
reflectionHost, evaluator, scopeRegistry, false, new NoopResourceLoader(), [''], false,
true, moduleResolver, cycleAnalyzer, refEmitter, NOOP_DEFAULT_IMPORT_RECORDER);
reflectionHost, evaluator, metaRegistry, scopeRegistry, false, new NoopResourceLoader(),
[''], false, true, moduleResolver, cycleAnalyzer, refEmitter, NOOP_DEFAULT_IMPORT_RECORDER);
const TestCmp = getDeclaration(program, 'entry.ts', 'TestCmp', isNamedClassDeclaration);
const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp));
if (detected === undefined) {

View File

@ -9,6 +9,7 @@
import * as ts from 'typescript';
import {NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
import {DtsMetadataReader, LocalMetadataRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator';
import {ClassDeclaration, TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
@ -40,8 +41,10 @@ describe('DirectiveDecoratorHandler', () => {
const checker = program.getTypeChecker();
const reflectionHost = new TestReflectionHost(checker);
const evaluator = new PartialEvaluator(reflectionHost, checker);
const metaReader = new LocalMetadataRegistry();
const dtsReader = new DtsMetadataReader(checker, reflectionHost);
const scopeRegistry = new LocalModuleScopeRegistry(
new MetadataDtsModuleScopeResolver(checker, reflectionHost, null), new ReferenceEmitter([]),
metaReader, new MetadataDtsModuleScopeResolver(dtsReader, null), new ReferenceEmitter([]),
null);
const handler = new DirectiveDecoratorHandler(
reflectionHost, evaluator, scopeRegistry, NOOP_DEFAULT_IMPORT_RECORDER, false);

View File

@ -11,6 +11,7 @@ import {R3Reference} from '@angular/compiler/src/compiler';
import * as ts from 'typescript';
import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
import {DtsMetadataReader, LocalMetadataRegistry} from '../../metadata';
import {PartialEvaluator} from '../../partial_evaluator';
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
@ -55,14 +56,16 @@ describe('NgModuleDecoratorHandler', () => {
const reflectionHost = new TypeScriptReflectionHost(checker);
const evaluator = new PartialEvaluator(reflectionHost, checker);
const referencesRegistry = new NoopReferencesRegistry();
const metaRegistry = new LocalMetadataRegistry();
const dtsReader = new DtsMetadataReader(checker, reflectionHost);
const scopeRegistry = new LocalModuleScopeRegistry(
new MetadataDtsModuleScopeResolver(checker, reflectionHost, null), new ReferenceEmitter([]),
metaRegistry, new MetadataDtsModuleScopeResolver(dtsReader, null), new ReferenceEmitter([]),
null);
const refEmitter = new ReferenceEmitter([new LocalIdentifierStrategy()]);
const handler = new NgModuleDecoratorHandler(
reflectionHost, evaluator, scopeRegistry, referencesRegistry, false, null, refEmitter,
NOOP_DEFAULT_IMPORT_RECORDER);
reflectionHost, evaluator, metaRegistry, scopeRegistry, referencesRegistry, false, null,
refEmitter, NOOP_DEFAULT_IMPORT_RECORDER);
const TestModule = getDeclaration(program, 'entry.ts', 'TestModule', isNamedClassDeclaration);
const detected =
handler.detect(TestModule, reflectionHost.getDecoratorsOfDeclaration(TestModule));

View File

@ -0,0 +1,17 @@
load("//tools:defaults.bzl", "ts_library")
package(default_visibility = ["//visibility:public"])
ts_library(
name = "metadata",
srcs = ["index.ts"] + glob([
"src/**/*.ts",
]),
deps = [
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/util",
"@npm//typescript",
],
)

View File

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export * from './src/api';
export {DtsMetadataReader} from './src/dts';
export {CompoundMetadataRegistry, LocalMetadataRegistry} from './src/registry';
export {extractDirectiveGuards} from './src/util';

View File

@ -0,0 +1,71 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {DirectiveMeta as T2DirectiveMeta} from '@angular/compiler';
import {Reference} from '../../imports';
import {ClassDeclaration} from '../../reflection';
/**
* Metadata collected for an `NgModule`.
*/
export interface NgModuleMeta {
ref: Reference<ClassDeclaration>;
declarations: Reference<ClassDeclaration>[];
imports: Reference<ClassDeclaration>[];
exports: Reference<ClassDeclaration>[];
}
/**
* Metadata collected for a directive within an NgModule's scope.
*/
export interface DirectiveMeta extends T2DirectiveMeta {
ref: Reference<ClassDeclaration>;
/**
* Unparsed selector of the directive.
*/
selector: string;
queries: string[];
ngTemplateGuards: string[];
hasNgTemplateContextGuard: boolean;
/**
* A `Reference` to the base class for the directive, if one was detected.
*
* A value of `'dynamic'` indicates that while the analyzer detected that this directive extends
* another type, it could not statically determine the base class.
*/
baseClass: Reference<ClassDeclaration>|'dynamic'|null;
}
/**
* Metadata for a pipe within an NgModule's scope.
*/
export interface PipeMeta {
ref: Reference<ClassDeclaration>;
name: string;
}
/**
* Reads metadata for directives, pipes, and modules from a particular source, such as .d.ts files
* or a registry.
*/
export interface MetadataReader {
getDirectiveMetadata(node: Reference<ClassDeclaration>): DirectiveMeta|null;
getNgModuleMetadata(node: Reference<ClassDeclaration>): NgModuleMeta|null;
getPipeMetadata(node: Reference<ClassDeclaration>): PipeMeta|null;
}
/**
* Registers new metadata for directives, pipes, and modules.
*/
export interface MetadataRegistry {
registerDirectiveMetadata(meta: DirectiveMeta): void;
registerNgModuleMetadata(meta: NgModuleMeta): void;
registerPipeMetadata(meta: PipeMeta): void;
}

View File

@ -0,0 +1,118 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {Reference} from '../../imports';
import {ClassDeclaration, ReflectionHost} from '../../reflection';
import {DirectiveMeta, MetadataReader, NgModuleMeta, PipeMeta} from './api';
import {extractDirectiveGuards, extractReferencesFromType, readStringArrayType, readStringMapType, readStringType} from './util';
/**
* A `MetadataReader` that can read metadata from `.d.ts` files, which have static Ivy properties
* from an upstream compilation already.
*/
export class DtsMetadataReader implements MetadataReader {
constructor(private checker: ts.TypeChecker, private reflector: ReflectionHost) {}
/**
* Read the metadata from a class that has already been compiled somehow (either it's in a .d.ts
* file, or in a .ts file with a handwritten definition).
*
* @param ref `Reference` to the class of interest, with the context of how it was obtained.
*/
getNgModuleMetadata(ref: Reference<ClassDeclaration>): NgModuleMeta|null {
const clazz = ref.node;
const resolutionContext = clazz.getSourceFile().fileName;
// This operation is explicitly not memoized, as it depends on `ref.ownedByModuleGuess`.
// TODO(alxhub): investigate caching of .d.ts module metadata.
const ngModuleDef = this.reflector.getMembersOfClass(clazz).find(
member => member.name === 'ngModuleDef' && member.isStatic);
if (ngModuleDef === undefined) {
return null;
} else if (
// Validate that the shape of the ngModuleDef type is correct.
ngModuleDef.type === null || !ts.isTypeReferenceNode(ngModuleDef.type) ||
ngModuleDef.type.typeArguments === undefined ||
ngModuleDef.type.typeArguments.length !== 4) {
return null;
}
// Read the ModuleData out of the type arguments.
const [_, declarationMetadata, importMetadata, exportMetadata] = ngModuleDef.type.typeArguments;
return {
ref,
declarations: extractReferencesFromType(
this.checker, declarationMetadata, ref.ownedByModuleGuess, resolutionContext),
exports: extractReferencesFromType(
this.checker, exportMetadata, ref.ownedByModuleGuess, resolutionContext),
imports: extractReferencesFromType(
this.checker, importMetadata, ref.ownedByModuleGuess, resolutionContext),
};
}
/**
* Read directive (or component) metadata from a referenced class in a .d.ts file.
*/
getDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta|null {
const clazz = ref.node;
const def = this.reflector.getMembersOfClass(clazz).find(
field =>
field.isStatic && (field.name === 'ngComponentDef' || field.name === 'ngDirectiveDef'));
if (def === undefined) {
// No definition could be found.
return null;
} else if (
def.type === null || !ts.isTypeReferenceNode(def.type) ||
def.type.typeArguments === undefined || def.type.typeArguments.length < 2) {
// The type metadata was the wrong shape.
return null;
}
const selector = readStringType(def.type.typeArguments[1]);
if (selector === null) {
return null;
}
return {
ref,
name: clazz.name.text,
isComponent: def.name === 'ngComponentDef', selector,
exportAs: readStringArrayType(def.type.typeArguments[2]),
inputs: readStringMapType(def.type.typeArguments[3]),
outputs: readStringMapType(def.type.typeArguments[4]),
queries: readStringArrayType(def.type.typeArguments[5]),
...extractDirectiveGuards(clazz, this.reflector),
baseClass: null,
};
}
/**
* Read pipe metadata from a referenced class in a .d.ts file.
*/
getPipeMetadata(ref: Reference<ClassDeclaration>): PipeMeta|null {
const def = this.reflector.getMembersOfClass(ref.node).find(
field => field.isStatic && field.name === 'ngPipeDef');
if (def === undefined) {
// No definition could be found.
return null;
} else if (
def.type === null || !ts.isTypeReferenceNode(def.type) ||
def.type.typeArguments === undefined || def.type.typeArguments.length < 2) {
// The type metadata was the wrong shape.
return null;
}
const type = def.type.typeArguments[1];
if (!ts.isLiteralTypeNode(type) || !ts.isStringLiteral(type.literal)) {
// The type metadata was the wrong type.
return null;
}
const name = type.literal.text;
return {ref, name};
}
}

View File

@ -0,0 +1,61 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Reference} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from './api';
/**
* A registry of directive, pipe, and module metadata for types defined in the current compilation
* unit, which supports both reading and registering.
*/
export class LocalMetadataRegistry implements MetadataRegistry, MetadataReader {
private directives = new Map<ClassDeclaration, DirectiveMeta>();
private ngModules = new Map<ClassDeclaration, NgModuleMeta>();
private pipes = new Map<ClassDeclaration, PipeMeta>();
getDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta|null {
return this.directives.has(ref.node) ? this.directives.get(ref.node) ! : null;
}
getNgModuleMetadata(ref: Reference<ClassDeclaration>): NgModuleMeta|null {
return this.ngModules.has(ref.node) ? this.ngModules.get(ref.node) ! : null;
}
getPipeMetadata(ref: Reference<ClassDeclaration>): PipeMeta|null {
return this.pipes.has(ref.node) ? this.pipes.get(ref.node) ! : null;
}
registerDirectiveMetadata(meta: DirectiveMeta): void { this.directives.set(meta.ref.node, meta); }
registerNgModuleMetadata(meta: NgModuleMeta): void { this.ngModules.set(meta.ref.node, meta); }
registerPipeMetadata(meta: PipeMeta): void { this.pipes.set(meta.ref.node, meta); }
}
/**
* A `MetadataRegistry` which registers metdata with multiple delegate `MetadataRegistry` instances.
*/
export class CompoundMetadataRegistry implements MetadataRegistry {
constructor(private registries: MetadataRegistry[]) {}
registerDirectiveMetadata(meta: DirectiveMeta): void {
for (const registry of this.registries) {
registry.registerDirectiveMetadata(meta);
}
}
registerNgModuleMetadata(meta: NgModuleMeta): void {
for (const registry of this.registries) {
registry.registerNgModuleMetadata(meta);
}
}
registerPipeMetadata(meta: PipeMeta): void {
for (const registry of this.registries) {
registry.registerPipeMetadata(meta);
}
}
}

View File

@ -19,6 +19,7 @@ import {ErrorCode, ngErrorCode} from './diagnostics';
import {FlatIndexGenerator, ReferenceGraph, checkForPrivateExports, findFlatIndexEntryPoint} from './entry_point';
import {AbsoluteModuleStrategy, AliasGenerator, AliasStrategy, DefaultImportTracker, FileToModuleHost, FileToModuleStrategy, ImportRewriter, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, R3SymbolsImportRewriter, Reference, ReferenceEmitter} from './imports';
import {IncrementalState} from './incremental';
import {CompoundMetadataRegistry, DtsMetadataReader, LocalMetadataRegistry} from './metadata';
import {PartialEvaluator} from './partial_evaluator';
import {AbsoluteFsPath, LogicalFileSystem} from './path';
import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf';
@ -413,14 +414,18 @@ export class NgtscProgram implements api.Program {
}
const evaluator = new PartialEvaluator(this.reflector, checker);
const depScopeReader =
new MetadataDtsModuleScopeResolver(checker, this.reflector, aliasGenerator);
const scopeRegistry =
new LocalModuleScopeRegistry(depScopeReader, this.refEmitter, aliasGenerator);
const dtsReader = new DtsMetadataReader(checker, this.reflector);
const localMetaRegistry = new LocalMetadataRegistry();
const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, aliasGenerator);
const scopeRegistry = new LocalModuleScopeRegistry(
localMetaRegistry, depScopeReader, this.refEmitter, aliasGenerator);
const metaRegistry = new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry]);
// If a flat module entrypoint was specified, then track references via a `ReferenceGraph` in
// order to produce proper diagnostics for incorrectly exported directives/pipes/etc. If there
// If a flat module entrypoint was specified, then track references via a `ReferenceGraph`
// in
// order to produce proper diagnostics for incorrectly exported directives/pipes/etc. If
// there
// is no flat module entrypoint then don't pay the cost of tracking references.
let referencesRegistry: ReferencesRegistry;
if (this.entryPoint !== null) {
@ -436,20 +441,20 @@ export class NgtscProgram implements api.Program {
const handlers = [
new BaseDefDecoratorHandler(this.reflector, evaluator, this.isCore),
new ComponentDecoratorHandler(
this.reflector, evaluator, scopeRegistry, this.isCore, this.resourceManager,
this.reflector, evaluator, metaRegistry, scopeRegistry, this.isCore, this.resourceManager,
this.rootDirs, this.options.preserveWhitespaces || false,
this.options.i18nUseExternalIds !== false, this.moduleResolver, this.cycleAnalyzer,
this.refEmitter, this.defaultImportTracker),
new DirectiveDecoratorHandler(
this.reflector, evaluator, scopeRegistry, this.defaultImportTracker, this.isCore),
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore),
new InjectableDecoratorHandler(
this.reflector, this.defaultImportTracker, this.isCore,
this.options.strictInjectionParameters || false),
new NgModuleDecoratorHandler(
this.reflector, evaluator, scopeRegistry, referencesRegistry, this.isCore,
this.reflector, evaluator, metaRegistry, scopeRegistry, referencesRegistry, this.isCore,
this.routeAnalyzer, this.refEmitter, this.defaultImportTracker),
new PipeDecoratorHandler(
this.reflector, evaluator, scopeRegistry, this.defaultImportTracker, this.isCore),
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore),
];
return new IvyCompilation(

View File

@ -11,6 +11,7 @@ ts_library(
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/diagnostics",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/typecheck",
"//packages/compiler-cli/src/ngtsc/util",

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
export {ExportScope, ScopeData, ScopeDirective, ScopePipe} from './src/api';
export {ExportScope, ScopeData} from './src/api';
export {DtsModuleScopeResolver, MetadataDtsModuleScopeResolver} from './src/dependency';
export {LocalModuleScope, LocalModuleScopeRegistry, LocalNgModuleData} from './src/local';
export {extractDirectiveGuards} from './src/util';

View File

@ -6,9 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Reference} from '../../imports';
import {ClassDeclaration} from '../../reflection';
import {TypeCheckableDirectiveMeta} from '../../typecheck';
import {DirectiveMeta, PipeMeta} from '../../metadata';
/**
* Data for one of a given NgModule's scopes (either compilation scope or export scopes).
@ -17,12 +16,12 @@ export interface ScopeData {
/**
* Directives in the exported scope of the module.
*/
directives: ScopeDirective[];
directives: DirectiveMeta[];
/**
* Pipes in the exported scope of the module.
*/
pipes: ScopePipe[];
pipes: PipeMeta[];
}
/**
@ -35,21 +34,3 @@ export interface ExportScope {
*/
exported: ScopeData;
}
/**
* Metadata for a given directive within an NgModule's scope.
*/
export interface ScopeDirective extends TypeCheckableDirectiveMeta {
/**
* Unparsed selector of the directive.
*/
selector: string;
}
/**
* Metadata for a given pipe within an NgModule's scope.
*/
export interface ScopePipe {
ref: Reference<ClassDeclaration>;
name: string;
}

View File

@ -9,10 +9,10 @@
import * as ts from 'typescript';
import {AliasGenerator, Reference} from '../../imports';
import {ClassDeclaration, ReflectionHost} from '../../reflection';
import {DirectiveMeta, MetadataReader, PipeMeta} from '../../metadata';
import {ClassDeclaration} from '../../reflection';
import {ExportScope, ScopeDirective, ScopePipe} from './api';
import {extractDirectiveGuards, extractReferencesFromType, readStringArrayType, readStringMapType, readStringType} from './util';
import {ExportScope} from './api';
export interface DtsModuleScopeResolver {
resolve(ref: Reference<ClassDeclaration>): ExportScope|null;
@ -31,9 +31,10 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
*/
private cache = new Map<ClassDeclaration, ExportScope|null>();
constructor(
private checker: ts.TypeChecker, private reflector: ReflectionHost,
private aliasGenerator: AliasGenerator|null) {}
/**
* @param dtsMetaReader a `MetadataReader` which can read metadata from `.d.ts` files.
*/
constructor(private dtsMetaReader: MetadataReader, private aliasGenerator: AliasGenerator|null) {}
/**
* Resolve a `Reference`'d NgModule from a .d.ts file and produce a transitive `ExportScope`
@ -55,10 +56,10 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
}
// Build up the export scope - those directives and pipes made visible by this module.
const directives: ScopeDirective[] = [];
const pipes: ScopePipe[] = [];
const directives: DirectiveMeta[] = [];
const pipes: PipeMeta[] = [];
const meta = this.readModuleMetadataFromClass(ref);
const meta = this.dtsMetaReader.getNgModuleMetadata(ref);
if (meta === null) {
this.cache.set(clazz, null);
return null;
@ -73,7 +74,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
// don't affect the export scope.
for (const exportRef of meta.exports) {
// Attempt to process the export as a directive.
const directive = this.readScopeDirectiveFromClassWithDef(exportRef);
const directive = this.dtsMetaReader.getDirectiveMetadata(exportRef);
if (directive !== null) {
if (!declarations.has(exportRef.node)) {
directives.push(this.maybeAlias(directive, sourceFile));
@ -84,7 +85,7 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
}
// Attempt to process the export as a pipe.
const pipe = this.readScopePipeFromClassWithDef(exportRef);
const pipe = this.dtsMetaReader.getPipeMetadata(exportRef);
if (pipe !== null) {
if (!declarations.has(exportRef.node)) {
pipes.push(this.maybeAlias(pipe, sourceFile));
@ -133,103 +134,8 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
};
}
/**
* Read the metadata from a class that has already been compiled somehow (either it's in a .d.ts
* file, or in a .ts file with a handwritten definition).
*
* @param ref `Reference` to the class of interest, with the context of how it was obtained.
*/
private readModuleMetadataFromClass(ref: Reference<ClassDeclaration>): RawDependencyMetadata
|null {
const clazz = ref.node;
const resolutionContext = clazz.getSourceFile().fileName;
// This operation is explicitly not memoized, as it depends on `ref.ownedByModuleGuess`.
// TODO(alxhub): investigate caching of .d.ts module metadata.
const ngModuleDef = this.reflector.getMembersOfClass(clazz).find(
member => member.name === 'ngModuleDef' && member.isStatic);
if (ngModuleDef === undefined) {
return null;
} else if (
// Validate that the shape of the ngModuleDef type is correct.
ngModuleDef.type === null || !ts.isTypeReferenceNode(ngModuleDef.type) ||
ngModuleDef.type.typeArguments === undefined ||
ngModuleDef.type.typeArguments.length !== 4) {
return null;
}
// Read the ModuleData out of the type arguments.
const [_, declarationMetadata, importMetadata, exportMetadata] = ngModuleDef.type.typeArguments;
return {
declarations: extractReferencesFromType(
this.checker, declarationMetadata, ref.ownedByModuleGuess, resolutionContext),
exports: extractReferencesFromType(
this.checker, exportMetadata, ref.ownedByModuleGuess, resolutionContext),
imports: extractReferencesFromType(
this.checker, importMetadata, ref.ownedByModuleGuess, resolutionContext),
};
}
/**
* Read directive (or component) metadata from a referenced class in a .d.ts file.
*/
private readScopeDirectiveFromClassWithDef(ref: Reference<ClassDeclaration>): ScopeDirective
|null {
const clazz = ref.node;
const def = this.reflector.getMembersOfClass(clazz).find(
field =>
field.isStatic && (field.name === 'ngComponentDef' || field.name === 'ngDirectiveDef'));
if (def === undefined) {
// No definition could be found.
return null;
} else if (
def.type === null || !ts.isTypeReferenceNode(def.type) ||
def.type.typeArguments === undefined || def.type.typeArguments.length < 2) {
// The type metadata was the wrong shape.
return null;
}
const selector = readStringType(def.type.typeArguments[1]);
if (selector === null) {
return null;
}
return {
ref,
name: clazz.name.text,
isComponent: def.name === 'ngComponentDef', selector,
exportAs: readStringArrayType(def.type.typeArguments[2]),
inputs: readStringMapType(def.type.typeArguments[3]),
outputs: readStringMapType(def.type.typeArguments[4]),
queries: readStringArrayType(def.type.typeArguments[5]),
...extractDirectiveGuards(clazz, this.reflector),
};
}
/**
* Read pipe metadata from a referenced class in a .d.ts file.
*/
private readScopePipeFromClassWithDef(ref: Reference<ClassDeclaration>): ScopePipe|null {
const def = this.reflector.getMembersOfClass(ref.node).find(
field => field.isStatic && field.name === 'ngPipeDef');
if (def === undefined) {
// No definition could be found.
return null;
} else if (
def.type === null || !ts.isTypeReferenceNode(def.type) ||
def.type.typeArguments === undefined || def.type.typeArguments.length < 2) {
// The type metadata was the wrong shape.
return null;
}
const type = def.type.typeArguments[1];
if (!ts.isLiteralTypeNode(type) || !ts.isStringLiteral(type.literal)) {
// The type metadata was the wrong type.
return null;
}
const name = type.literal.text;
return {ref, name};
}
private maybeAlias<T extends ScopeDirective|ScopePipe>(
dirOrPipe: T, maybeAliasFrom: ts.SourceFile): T {
private maybeAlias<T extends DirectiveMeta|PipeMeta>(dirOrPipe: T, maybeAliasFrom: ts.SourceFile):
T {
if (this.aliasGenerator === null) {
return dirOrPipe;
}
@ -244,12 +150,3 @@ export class MetadataDtsModuleScopeResolver implements DtsModuleScopeResolver {
}
}
}
/**
* Raw metadata read from the .d.ts info of an ngModuleDef field on a compiled NgModule class.
*/
interface RawDependencyMetadata {
declarations: Reference<ClassDeclaration>[];
imports: Reference<ClassDeclaration>[];
exports: Reference<ClassDeclaration>[];
}

View File

@ -11,10 +11,11 @@ import * as ts from 'typescript';
import {ErrorCode, makeDiagnostic} from '../../diagnostics';
import {AliasGenerator, Reexport, Reference, ReferenceEmitter} from '../../imports';
import {DirectiveMeta, LocalMetadataRegistry, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata';
import {ClassDeclaration} from '../../reflection';
import {identifierOfNode, nodeNameForError} from '../../util/src/typescript';
import {ExportScope, ScopeData, ScopeDirective, ScopePipe} from './api';
import {ExportScope, ScopeData} from './api';
import {DtsModuleScopeResolver} from './dependency';
export interface LocalNgModuleData {
@ -47,7 +48,7 @@ export interface LocalModuleScope extends ExportScope {
* The `LocalModuleScopeRegistry` is also capable of producing `ts.Diagnostic` errors when Angular
* semantics are violated.
*/
export class LocalModuleScopeRegistry {
export class LocalModuleScopeRegistry implements MetadataRegistry {
/**
* Tracks whether the registry has been asked to produce scopes for a module or component. Once
* this is true, the registry cannot accept registrations of new directives/pipes/modules as it
@ -55,21 +56,6 @@ export class LocalModuleScopeRegistry {
*/
private sealed = false;
/**
* Metadata for each local NgModule registered.
*/
private ngModuleData = new Map<ClassDeclaration, LocalNgModuleData>();
/**
* Metadata for each local directive registered.
*/
private directiveData = new Map<ClassDeclaration, ScopeDirective>();
/**
* Metadata for each local pipe registered.
*/
private pipeData = new Map<ClassDeclaration, ScopePipe>();
/**
* A map of components from the current compilation unit to the NgModule which declared them.
*
@ -79,6 +65,8 @@ export class LocalModuleScopeRegistry {
*/
private declarationToModule = new Map<ClassDeclaration, ClassDeclaration>();
private moduleToRef = new Map<ClassDeclaration, Reference<ClassDeclaration>>();
/**
* A cache of calculated `LocalModuleScope`s for each NgModule declared in the current program.
*
@ -103,29 +91,23 @@ export class LocalModuleScopeRegistry {
private scopeErrors = new Map<ClassDeclaration, ts.Diagnostic[]>();
constructor(
private dependencyScopeReader: DtsModuleScopeResolver, private refEmitter: ReferenceEmitter,
private aliasGenerator: AliasGenerator|null) {}
private localReader: MetadataReader, private dependencyScopeReader: DtsModuleScopeResolver,
private refEmitter: ReferenceEmitter, private aliasGenerator: AliasGenerator|null) {}
/**
* Add an NgModule's data to the registry.
*/
registerNgModule(clazz: ClassDeclaration, data: LocalNgModuleData): void {
registerNgModuleMetadata(data: NgModuleMeta): void {
this.assertCollecting();
this.ngModuleData.set(clazz, data);
this.moduleToRef.set(data.ref.node, data.ref);
for (const decl of data.declarations) {
this.declarationToModule.set(decl.node, clazz);
this.declarationToModule.set(decl.node, data.ref.node);
}
}
registerDirective(directive: ScopeDirective): void {
this.assertCollecting();
this.directiveData.set(directive.ref.node, directive);
}
registerDirectiveMetadata(directive: DirectiveMeta): void {}
registerPipe(pipe: ScopePipe): void {
this.assertCollecting();
this.pipeData.set(pipe.ref.node, pipe);
}
registerPipeMetadata(pipe: PipeMeta): void {}
getScopeForComponent(clazz: ClassDeclaration): LocalModuleScope|null {
if (!this.declarationToModule.has(clazz)) {
@ -143,7 +125,11 @@ export class LocalModuleScopeRegistry {
* available or the scope contains errors.
*/
getScopeOfModule(clazz: ClassDeclaration): LocalModuleScope|null {
const scope = this.getScopeOfModuleInternal(clazz);
if (!this.moduleToRef.has(clazz)) {
return null;
}
const scope = this.getScopeOfModuleInternal(this.moduleToRef.get(clazz) !);
// Translate undefined -> null.
return scope !== undefined ? scope : null;
}
@ -168,21 +154,17 @@ export class LocalModuleScopeRegistry {
* Implementation of `getScopeOfModule` which differentiates between no scope being available
* (returns `null`) and a scope being produced with errors (returns `undefined`).
*/
private getScopeOfModuleInternal(clazz: ClassDeclaration): LocalModuleScope|null|undefined {
private getScopeOfModuleInternal(ref: Reference<ClassDeclaration>): LocalModuleScope|null
|undefined {
// Seal the registry to protect the integrity of the `LocalModuleScope` cache.
this.sealed = true;
// Look for cached data if available.
if (this.cache.has(clazz)) {
return this.cache.get(clazz);
}
// `clazz` should be an NgModule previously added to the registry. If not, a scope for it
// `ref` should be an NgModule previously added to the registry. If not, a scope for it
// cannot be produced.
if (!this.ngModuleData.has(clazz)) {
const ngModule = this.localReader.getNgModuleMetadata(ref);
if (ngModule === null) {
return null;
}
const ngModule = this.ngModuleData.get(clazz) !;
// Errors produced during computation of the scope are recorded here. At the end, if this array
// isn't empty then `undefined` will be cached and returned to indicate this scope is invalid.
@ -193,15 +175,15 @@ export class LocalModuleScopeRegistry {
// - the directives and pipes which are exported to any NgModules which import this one.
// Directives and pipes in the compilation scope.
const compilationDirectives = new Map<ts.Declaration, ScopeDirective>();
const compilationPipes = new Map<ts.Declaration, ScopePipe>();
const compilationDirectives = new Map<ts.Declaration, DirectiveMeta>();
const compilationPipes = new Map<ts.Declaration, PipeMeta>();
const declared = new Set<ts.Declaration>();
const sourceFile = clazz.getSourceFile();
const sourceFile = ref.node.getSourceFile();
// Directives and pipes exported to any importing NgModules.
const exportDirectives = new Map<ts.Declaration, ScopeDirective>();
const exportPipes = new Map<ts.Declaration, ScopePipe>();
const exportDirectives = new Map<ts.Declaration, DirectiveMeta>();
const exportPipes = new Map<ts.Declaration, PipeMeta>();
// The algorithm is as follows:
// 1) Add directives/pipes declared in the NgModule to the compilation scope.
@ -217,11 +199,11 @@ export class LocalModuleScopeRegistry {
// 1) add declarations.
for (const decl of ngModule.declarations) {
if (this.directiveData.has(decl.node)) {
const directive = this.directiveData.get(decl.node) !;
const directive = this.localReader.getDirectiveMetadata(decl);
const pipe = this.localReader.getPipeMetadata(decl);
if (directive !== null) {
compilationDirectives.set(decl.node, {...directive, ref: decl});
} else if (this.pipeData.has(decl.node)) {
const pipe = this.pipeData.get(decl.node) !;
} else if (pipe !== null) {
compilationPipes.set(decl.node, {...pipe, ref: decl});
} else {
// TODO(alxhub): produce a ts.Diagnostic. This can't be an error right now since some
@ -234,16 +216,16 @@ export class LocalModuleScopeRegistry {
// 2) process imports.
for (const decl of ngModule.imports) {
const importScope = this.getExportedScope(decl, diagnostics, clazz, 'import');
const importScope = this.getExportedScope(decl, diagnostics, ref.node, 'import');
if (importScope === null) {
// An import wasn't an NgModule, so record an error.
diagnostics.push(invalidRef(clazz, decl, 'import'));
diagnostics.push(invalidRef(ref.node, decl, 'import'));
continue;
} else if (importScope === undefined) {
// An import was an NgModule but contained errors of its own. Record this as an error too,
// because this scope is always going to be incorrect if one of its imports could not be
// read.
diagnostics.push(invalidTransitiveNgModuleRef(clazz, decl, 'import'));
diagnostics.push(invalidTransitiveNgModuleRef(ref.node, decl, 'import'));
continue;
}
for (const directive of importScope.exported.directives) {
@ -261,12 +243,12 @@ export class LocalModuleScopeRegistry {
// imported types.
for (const decl of ngModule.exports) {
// Attempt to resolve decl as an NgModule.
const importScope = this.getExportedScope(decl, diagnostics, clazz, 'export');
const importScope = this.getExportedScope(decl, diagnostics, ref.node, 'export');
if (importScope === undefined) {
// An export was an NgModule but contained errors of its own. Record this as an error too,
// because this scope is always going to be incorrect if one of its exports could not be
// read.
diagnostics.push(invalidTransitiveNgModuleRef(clazz, decl, 'export'));
diagnostics.push(invalidTransitiveNgModuleRef(ref.node, decl, 'export'));
continue;
} else if (importScope !== null) {
// decl is an NgModule.
@ -286,10 +268,11 @@ export class LocalModuleScopeRegistry {
exportPipes.set(decl.node, pipe);
} else {
// decl is an unknown export.
if (this.directiveData.has(decl.node) || this.pipeData.has(decl.node)) {
diagnostics.push(invalidReexport(clazz, decl));
if (this.localReader.getDirectiveMetadata(decl) !== null ||
this.localReader.getPipeMetadata(decl) !== null) {
diagnostics.push(invalidReexport(ref.node, decl));
} else {
diagnostics.push(invalidRef(clazz, decl, 'export'));
diagnostics.push(invalidRef(ref.node, decl, 'export'));
}
continue;
}
@ -337,10 +320,10 @@ export class LocalModuleScopeRegistry {
// Check if this scope had any errors during production.
if (diagnostics.length > 0) {
// Cache undefined, to mark the fact that the scope is invalid.
this.cache.set(clazz, undefined);
this.cache.set(ref.node, undefined);
// Save the errors for retrieval.
this.scopeErrors.set(clazz, diagnostics);
this.scopeErrors.set(ref.node, diagnostics);
// Return undefined to indicate the scope is invalid.
return undefined;
@ -355,7 +338,7 @@ export class LocalModuleScopeRegistry {
exported,
reexports,
};
this.cache.set(clazz, scope);
this.cache.set(ref.node, scope);
return scope;
}
@ -399,7 +382,7 @@ export class LocalModuleScopeRegistry {
return this.dependencyScopeReader.resolve(ref);
} else {
// The NgModule is declared locally in the current program. Resolve it from the registry.
return this.getScopeOfModuleInternal(ref.node);
return this.getScopeOfModuleInternal(ref);
}
}

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