feat(ivy): augment selector scopes to extract additional metadata (#26203)

Before type checking can be turned on in ngtsc, appropriate metadata for
each component and directive must be determined. This commit adds tracking
of the extra metadata in *DefWithMeta types to the selector scope handling,
allowing for later extraction for type-checking purposes.

PR Close #26203
This commit is contained in:
Alex Rickabaugh 2018-09-21 13:34:09 -07:00 committed by Jason Aden
parent 5f1273ba2e
commit 868047e87f
7 changed files with 191 additions and 64 deletions

View File

@ -15,5 +15,6 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/host", "//packages/compiler-cli/src/ngtsc/host",
"//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/transform", "//packages/compiler-cli/src/ngtsc/transform",
"//packages/compiler-cli/src/ngtsc/typecheck",
], ],
) )

View File

@ -134,7 +134,14 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
// If the component has a selector, it should be registered with the `SelectorScopeRegistry` so // If the component has a selector, it should be registered with the `SelectorScopeRegistry` so
// when this component appears in an `@NgModule` scope, its selector can be determined. // when this component appears in an `@NgModule` scope, its selector can be determined.
if (metadata.selector !== null) { if (metadata.selector !== null) {
this.scopeRegistry.registerSelector(node, metadata.selector); this.scopeRegistry.registerDirective(node, {
selector: metadata.selector,
exportAs: metadata.exportAs,
inputs: metadata.inputs,
outputs: metadata.outputs,
queries: metadata.queries.map(query => query.propertyName),
isComponent: true,
});
} }
// Construct the list of view queries. // Construct the list of view queries.
@ -198,7 +205,9 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
// Replace the empty components and directives from the analyze() step with a fully expanded // Replace the empty components and directives from the analyze() step with a fully expanded
// scope. This is possible now because during compile() the whole compilation unit has been // scope. This is possible now because during compile() the whole compilation unit has been
// fully analyzed. // fully analyzed.
const {directives, pipes, containsForwardDecls} = scope; const {pipes, containsForwardDecls} = scope;
const directives = new Map<string, Expression>();
scope.directives.forEach((meta, selector) => directives.set(selector, meta.directive));
const wrapDirectivesInClosure: boolean = !!containsForwardDecls; const wrapDirectivesInClosure: boolean = !!containsForwardDecls;
analysis = {...analysis, directives, pipes, wrapDirectivesInClosure}; analysis = {...analysis, directives, pipes, wrapDirectivesInClosure};
} }

View File

@ -40,7 +40,14 @@ export class DirectiveDecoratorHandler implements DecoratorHandler<R3DirectiveMe
// If the directive has a selector, it should be registered with the `SelectorScopeRegistry` so // If the directive has a selector, it should be registered with the `SelectorScopeRegistry` so
// when this directive appears in an `@NgModule` scope, its selector can be determined. // when this directive appears in an `@NgModule` scope, its selector can be determined.
if (analysis && analysis.selector !== null) { if (analysis && analysis.selector !== null) {
this.scopeRegistry.registerSelector(node, analysis.selector); this.scopeRegistry.registerDirective(node, {
selector: analysis.selector,
exportAs: analysis.exportAs,
inputs: analysis.inputs,
outputs: analysis.outputs,
queries: analysis.queries.map(query => query.propertyName),
isComponent: false,
});
} }
return {analysis}; return {analysis};

View File

@ -65,13 +65,13 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
} }
// Extract the module declarations, imports, and exports. // Extract the module declarations, imports, and exports.
let declarations: Reference[] = []; let declarations: Reference<ts.Declaration>[] = [];
if (ngModule.has('declarations')) { if (ngModule.has('declarations')) {
const expr = ngModule.get('declarations') !; const expr = ngModule.get('declarations') !;
const declarationMeta = staticallyResolve(expr, this.reflector, this.checker); const declarationMeta = staticallyResolve(expr, this.reflector, this.checker);
declarations = this.resolveTypeList(expr, declarationMeta, 'declarations'); declarations = this.resolveTypeList(expr, declarationMeta, 'declarations');
} }
let imports: Reference[] = []; let imports: Reference<ts.Declaration>[] = [];
if (ngModule.has('imports')) { if (ngModule.has('imports')) {
const expr = ngModule.get('imports') !; const expr = ngModule.get('imports') !;
const importsMeta = staticallyResolve( const importsMeta = staticallyResolve(
@ -79,7 +79,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
ref => this._extractModuleFromModuleWithProvidersFn(ref.node)); ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
imports = this.resolveTypeList(expr, importsMeta, 'imports'); imports = this.resolveTypeList(expr, importsMeta, 'imports');
} }
let exports: Reference[] = []; let exports: Reference<ts.Declaration>[] = [];
if (ngModule.has('exports')) { if (ngModule.has('exports')) {
const expr = ngModule.get('exports') !; const expr = ngModule.get('exports') !;
const exportsMeta = staticallyResolve( const exportsMeta = staticallyResolve(
@ -87,7 +87,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
ref => this._extractModuleFromModuleWithProvidersFn(ref.node)); ref => this._extractModuleFromModuleWithProvidersFn(ref.node));
exports = this.resolveTypeList(expr, exportsMeta, 'exports'); exports = this.resolveTypeList(expr, exportsMeta, 'exports');
} }
let bootstrap: Reference[] = []; let bootstrap: Reference<ts.Declaration>[] = [];
if (ngModule.has('bootstrap')) { if (ngModule.has('bootstrap')) {
const expr = ngModule.get('bootstrap') !; const expr = ngModule.get('bootstrap') !;
const bootstrapMeta = staticallyResolve(expr, this.reflector, this.checker); const bootstrapMeta = staticallyResolve(expr, this.reflector, this.checker);
@ -198,8 +198,9 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
/** /**
* Compute a list of `Reference`s from a resolved metadata value. * Compute a list of `Reference`s from a resolved metadata value.
*/ */
private resolveTypeList(expr: ts.Node, resolvedList: ResolvedValue, name: string): Reference[] { private resolveTypeList(expr: ts.Node, resolvedList: ResolvedValue, name: string):
const refList: Reference[] = []; Reference<ts.Declaration>[] {
const refList: Reference<ts.Declaration>[] = [];
if (!Array.isArray(resolvedList)) { if (!Array.isArray(resolvedList)) {
throw new FatalDiagnosticError( throw new FatalDiagnosticError(
ErrorCode.VALUE_HAS_WRONG_TYPE, expr, `Expected array when reading property ${name}`); ErrorCode.VALUE_HAS_WRONG_TYPE, expr, `Expected array when reading property ${name}`);
@ -215,7 +216,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
if (Array.isArray(entry)) { if (Array.isArray(entry)) {
// Recurse into nested arrays. // Recurse into nested arrays.
refList.push(...this.resolveTypeList(expr, entry, name)); refList.push(...this.resolveTypeList(expr, entry, name));
} else if (entry instanceof Reference) { } else if (isDeclarationReference(entry)) {
if (!entry.expressable) { if (!entry.expressable) {
throw new FatalDiagnosticError( throw new FatalDiagnosticError(
ErrorCode.VALUE_HAS_WRONG_TYPE, expr, `One entry in ${name} is not a type`); ErrorCode.VALUE_HAS_WRONG_TYPE, expr, `One entry in ${name} is not a type`);
@ -234,3 +235,9 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
return refList; return refList;
} }
} }
function isDeclarationReference(ref: any): ref is Reference<ts.Declaration> {
return ref instanceof Reference &&
(ts.isClassDeclaration(ref.node) || ts.isFunctionDeclaration(ref.node) ||
ts.isVariableDeclaration(ref.node));
}

View File

@ -12,18 +12,18 @@ import * as ts from 'typescript';
import {ReflectionHost} from '../../host'; import {ReflectionHost} from '../../host';
import {AbsoluteReference, Reference, ResolvedReference, reflectTypeEntityToDeclaration} from '../../metadata'; import {AbsoluteReference, Reference, ResolvedReference, reflectTypeEntityToDeclaration} from '../../metadata';
import {reflectIdentifierOfDeclaration, reflectNameOfDeclaration} from '../../metadata/src/reflector'; import {reflectIdentifierOfDeclaration, reflectNameOfDeclaration} from '../../metadata/src/reflector';
import {TypeCheckableDirectiveMeta} from '../../typecheck';
import {toR3Reference} from './util'; import {extractDirectiveGuards, toR3Reference} from './util';
/** /**
* Metadata extracted for a given NgModule that can be used to compute selector scopes. * Metadata extracted for a given NgModule that can be used to compute selector scopes.
*/ */
export interface ModuleData { export interface ModuleData {
declarations: Reference[]; declarations: Reference<ts.Declaration>[];
imports: Reference[]; imports: Reference<ts.Declaration>[];
exports: Reference[]; exports: Reference<ts.Declaration>[];
} }
/** /**
@ -31,11 +31,16 @@ export interface ModuleData {
* context of some module. * context of some module.
*/ */
export interface CompilationScope<T> { export interface CompilationScope<T> {
directives: Map<string, T>; directives: Map<string, ScopeDirective<T>>;
pipes: Map<string, T>; pipes: Map<string, T>;
containsForwardDecls?: boolean; containsForwardDecls?: boolean;
} }
export interface ScopeDirective<T> extends TypeCheckableDirectiveMeta {
selector: string;
directive: T;
}
/** /**
* Both transitively expanded scopes for a given NgModule. * Both transitively expanded scopes for a given NgModule.
*/ */
@ -44,13 +49,13 @@ interface SelectorScopes {
* Set of components, directives, and pipes visible to all components being compiled in the * Set of components, directives, and pipes visible to all components being compiled in the
* context of some module. * context of some module.
*/ */
compilation: Reference[]; compilation: Reference<ts.Declaration>[];
/** /**
* Set of components, directives, and pipes added to the compilation scope of any module importing * Set of components, directives, and pipes added to the compilation scope of any module importing
* some module. * some module.
*/ */
exported: Reference[]; exported: Reference<ts.Declaration>[];
} }
/** /**
@ -71,9 +76,9 @@ export class SelectorScopeRegistry {
private _compilationScopeCache = new Map<ts.Declaration, CompilationScope<Reference>>(); private _compilationScopeCache = new Map<ts.Declaration, CompilationScope<Reference>>();
/** /**
* Map of components/directives to their selector. * Map of components/directives to their metadata.
*/ */
private _directiveToSelector = new Map<ts.Declaration, string>(); private _directiveToMetadata = new Map<ts.Declaration, ScopeDirective<Reference>>();
/** /**
* Map of pipes to their name. * Map of pipes to their name.
@ -105,15 +110,16 @@ export class SelectorScopeRegistry {
} }
/** /**
* Register the selector of a component or directive with the registry. * Register the metadata of a component or directive with the registry.
*/ */
registerSelector(node: ts.Declaration, selector: string): void { registerDirective(node: ts.Declaration, metadata: ScopeDirective<Reference>): void {
node = ts.getOriginalNode(node) as ts.Declaration; node = ts.getOriginalNode(node) as ts.Declaration;
if (this._directiveToSelector.has(node)) { if (this._directiveToMetadata.has(node)) {
throw new Error(`Selector already registered: ${reflectNameOfDeclaration(node)} ${selector}`); throw new Error(
`Selector already registered: ${reflectNameOfDeclaration(node)} ${metadata.selector}`);
} }
this._directiveToSelector.set(node, selector); this._directiveToMetadata.set(node, metadata);
} }
/** /**
@ -125,11 +131,7 @@ export class SelectorScopeRegistry {
this._pipeToName.set(node, name); this._pipeToName.set(node, name);
} }
/** lookupCompilationScopeAsRefs(node: ts.Declaration): CompilationScope<Reference>|null {
* Produce the compilation scope of a component, which is determined by the module that declares
* it.
*/
lookupCompilationScope(node: ts.Declaration): CompilationScope<Expression>|null {
node = ts.getOriginalNode(node) as ts.Declaration; node = ts.getOriginalNode(node) as ts.Declaration;
// If the component has no associated module, then it has no compilation scope. // If the component has no associated module, then it has no compilation scope.
@ -147,11 +149,11 @@ export class SelectorScopeRegistry {
// The scope as cached is in terms of References, not Expressions. Converting between them // The scope as cached is in terms of References, not Expressions. Converting between them
// requires knowledge of the context file (in this case, the component node's source file). // requires knowledge of the context file (in this case, the component node's source file).
return convertScopeToExpressions(scope, node); return scope;
} }
// This is the first time the scope for this module is being computed. // This is the first time the scope for this module is being computed.
const directives = new Map<string, Reference>(); const directives = new Map<string, ScopeDirective<Reference<ts.Declaration>>>();
const pipes = new Map<string, Reference>(); const pipes = new Map<string, Reference>();
// Process the declaration scope of the module, and lookup the selector of every declared type. // Process the declaration scope of the module, and lookup the selector of every declared type.
@ -161,10 +163,10 @@ export class SelectorScopeRegistry {
const node = ts.getOriginalNode(ref.node) as ts.Declaration; const node = ts.getOriginalNode(ref.node) as ts.Declaration;
// Either the node represents a directive or a pipe. Look for both. // Either the node represents a directive or a pipe. Look for both.
const selector = this.lookupDirectiveSelector(node); const metadata = this.lookupDirectiveMetadata(ref);
// Only directives/components with selectors get added to the scope. // Only directives/components with selectors get added to the scope.
if (selector != null) { if (metadata != null) {
directives.set(selector, ref); directives.set(metadata.selector, {...metadata, directive: ref});
return; return;
} }
@ -180,7 +182,16 @@ export class SelectorScopeRegistry {
this._compilationScopeCache.set(node, scope); this._compilationScopeCache.set(node, scope);
// Convert References to Expressions in the context of the component's source file. // Convert References to Expressions in the context of the component's source file.
return convertScopeToExpressions(scope, node); return scope;
}
/**
* Produce the compilation scope of a component, which is determined by the module that declares
* it.
*/
lookupCompilationScope(node: ts.Declaration): CompilationScope<Expression>|null {
const scope = this.lookupCompilationScopeAsRefs(node);
return scope !== null ? convertScopeToExpressions(scope, node) : null;
} }
private lookupScopesOrDie(node: ts.Declaration, ngModuleImportedFrom: string|null): private lookupScopesOrDie(node: ts.Declaration, ngModuleImportedFrom: string|null):
@ -210,7 +221,7 @@ export class SelectorScopeRegistry {
} else { } else {
// The module wasn't analyzed before, and probably has a precompiled ngModuleDef with a type // The module wasn't analyzed before, and probably has a precompiled ngModuleDef with a type
// annotation that specifies the needed metadata. // annotation that specifies the needed metadata.
data = this._readMetadataFromCompiledClass(node, ngModuleImportedFrom); data = this._readModuleDataFromCompiledClass(node, ngModuleImportedFrom);
// Note that data here could still be null, if the class didn't have a precompiled // Note that data here could still be null, if the class didn't have a precompiled
// ngModuleDef. // ngModuleDef.
} }
@ -245,17 +256,18 @@ export class SelectorScopeRegistry {
} }
/** /**
* Lookup the selector of a component or directive class. * Lookup the metadata of a component or directive class.
* *
* Potentially this class is declared in a .d.ts file or otherwise has a manually created * Potentially this class is declared in a .d.ts file or otherwise has a manually created
* ngComponentDef/ngDirectiveDef. In this case, the type metadata of that definition is read * ngComponentDef/ngDirectiveDef. In this case, the type metadata of that definition is read
* to determine the selector. * to determine the metadata.
*/ */
private lookupDirectiveSelector(node: ts.Declaration): string|null { private lookupDirectiveMetadata(ref: Reference<ts.Declaration>): ScopeDirective<Reference>|null {
if (this._directiveToSelector.has(node)) { const node = ts.getOriginalNode(ref.node) as ts.Declaration;
return this._directiveToSelector.get(node) !; if (this._directiveToMetadata.has(node)) {
return this._directiveToMetadata.get(node) !;
} else { } else {
return this._readSelectorFromCompiledClass(node); return this._readMetadataFromCompiledClass(ref as Reference<ts.ClassDeclaration>);
} }
} }
@ -275,8 +287,8 @@ export class SelectorScopeRegistry {
* @param ngModuleImportedFrom module specifier of the import path to assume for all declarations * @param ngModuleImportedFrom module specifier of the import path to assume for all declarations
* stemming from this module. * stemming from this module.
*/ */
private _readMetadataFromCompiledClass(clazz: ts.Declaration, ngModuleImportedFrom: string|null): private _readModuleDataFromCompiledClass(
ModuleData|null { clazz: ts.Declaration, ngModuleImportedFrom: string|null): ModuleData|null {
// This operation is explicitly not memoized, as it depends on `ngModuleImportedFrom`. // This operation is explicitly not memoized, as it depends on `ngModuleImportedFrom`.
// TODO(alxhub): investigate caching of .d.ts module metadata. // TODO(alxhub): investigate caching of .d.ts module metadata.
const ngModuleDef = this.reflector.getMembersOfClass(clazz).find( const ngModuleDef = this.reflector.getMembersOfClass(clazz).find(
@ -304,7 +316,9 @@ export class SelectorScopeRegistry {
* Get the selector from type metadata for a class with a precompiled ngComponentDef or * Get the selector from type metadata for a class with a precompiled ngComponentDef or
* ngDirectiveDef. * ngDirectiveDef.
*/ */
private _readSelectorFromCompiledClass(clazz: ts.Declaration): string|null { private _readMetadataFromCompiledClass(ref: Reference<ts.ClassDeclaration>):
ScopeDirective<Reference>|null {
const clazz = ts.getOriginalNode(ref.node) as ts.ClassDeclaration;
const def = this.reflector.getMembersOfClass(clazz).find( const def = this.reflector.getMembersOfClass(clazz).find(
field => field =>
field.isStatic && (field.name === 'ngComponentDef' || field.name === 'ngDirectiveDef')); field.isStatic && (field.name === 'ngComponentDef' || field.name === 'ngDirectiveDef'));
@ -317,12 +331,22 @@ export class SelectorScopeRegistry {
// The type metadata was the wrong shape. // The type metadata was the wrong shape.
return null; return null;
} }
const type = def.type.typeArguments[1]; const selector = readStringType(def.type.typeArguments[1]);
if (!ts.isLiteralTypeNode(type) || !ts.isStringLiteral(type.literal)) { if (selector === null) {
// The type metadata was the wrong type.
return null; return null;
} }
return type.literal.text;
return {
ref,
name: clazz.name !.text,
directive: ref,
isComponent: def.name === 'ngComponentDef', selector,
exportAs: readStringType(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),
};
} }
/** /**
@ -357,7 +381,7 @@ export class SelectorScopeRegistry {
* they themselves were imported from another absolute path. * they themselves were imported from another absolute path.
*/ */
private _extractReferencesFromType(def: ts.TypeNode, ngModuleImportedFrom: string|null): private _extractReferencesFromType(def: ts.TypeNode, ngModuleImportedFrom: string|null):
Reference[] { Reference<ts.Declaration>[] {
if (!ts.isTupleTypeNode(def)) { if (!ts.isTupleTypeNode(def)) {
return []; return [];
} }
@ -394,23 +418,33 @@ function absoluteModuleName(ref: Reference): string|null {
return ref.moduleName; return ref.moduleName;
} }
function convertReferenceMap( function convertDirectiveReferenceMap(
map: Map<string, ScopeDirective<Reference>>,
context: ts.SourceFile): Map<string, ScopeDirective<Expression>> {
const newMap = new Map<string, ScopeDirective<Expression>>();
map.forEach((meta, selector) => {
newMap.set(selector, {...meta, directive: toR3Reference(meta.directive, context).value});
});
return newMap;
}
function convertPipeReferenceMap(
map: Map<string, Reference>, context: ts.SourceFile): Map<string, Expression> { map: Map<string, Reference>, context: ts.SourceFile): Map<string, Expression> {
return new Map<string, Expression>(Array.from(map.entries()).map(([selector, ref]): [ const newMap = new Map<string, Expression>();
string, Expression map.forEach((meta, selector) => { newMap.set(selector, toR3Reference(meta, context).value); });
] => [selector, toR3Reference(ref, context).value])); return newMap;
} }
function convertScopeToExpressions( function convertScopeToExpressions(
scope: CompilationScope<Reference>, context: ts.Declaration): CompilationScope<Expression> { scope: CompilationScope<Reference>, context: ts.Declaration): CompilationScope<Expression> {
const sourceContext = ts.getOriginalNode(context).getSourceFile(); const sourceContext = ts.getOriginalNode(context).getSourceFile();
const directives = convertReferenceMap(scope.directives, sourceContext); const directives = convertDirectiveReferenceMap(scope.directives, sourceContext);
const pipes = convertReferenceMap(scope.pipes, sourceContext); const pipes = convertPipeReferenceMap(scope.pipes, sourceContext);
const declPointer = maybeUnwrapNameOfDeclaration(context); const declPointer = maybeUnwrapNameOfDeclaration(context);
let containsForwardDecls = false; let containsForwardDecls = false;
directives.forEach(expr => { directives.forEach(expr => {
containsForwardDecls = containsForwardDecls = containsForwardDecls ||
containsForwardDecls || isExpressionForwardReference(expr, declPointer, sourceContext); isExpressionForwardReference(expr.directive, declPointer, sourceContext);
}); });
!containsForwardDecls && pipes.forEach(expr => { !containsForwardDecls && pipes.forEach(expr => {
containsForwardDecls = containsForwardDecls =
@ -439,3 +473,43 @@ function maybeUnwrapNameOfDeclaration(decl: ts.Declaration): ts.Declaration|ts.I
} }
return decl; return decl;
} }
function readStringType(type: ts.TypeNode): string|null {
if (!ts.isLiteralTypeNode(type) || !ts.isStringLiteral(type.literal)) {
return null;
}
return type.literal.text;
}
function readStringMapType(type: ts.TypeNode): {[key: string]: string} {
if (!ts.isTypeLiteralNode(type)) {
return {};
}
const obj: {[key: string]: string} = {};
type.members.forEach(member => {
if (!ts.isPropertySignature(member) || member.type === undefined || member.name === undefined ||
!ts.isStringLiteral(member.name)) {
return;
}
const value = readStringType(member.type);
if (value === null) {
return null;
}
obj[member.name.text] = value;
});
return obj;
}
function readStringArrayType(type: ts.TypeNode): string[] {
if (!ts.isTupleTypeNode(type)) {
return [];
}
const res: string[] = [];
type.elementTypes.forEach(el => {
if (!ts.isLiteralTypeNode(el) || !ts.isStringLiteral(el.literal)) {
return;
}
res.push(el.literal.text);
});
return res;
}

View File

@ -63,6 +63,8 @@ describe('SelectorScopeRegistry', () => {
expect(ProgramModule).toBeDefined(); expect(ProgramModule).toBeDefined();
expect(SomeModule).toBeDefined(); expect(SomeModule).toBeDefined();
const ProgramCmpRef = new ResolvedReference(ProgramCmp, ProgramCmp.name !);
const registry = new SelectorScopeRegistry(checker, host); const registry = new SelectorScopeRegistry(checker, host);
registry.registerModule(ProgramModule, { registry.registerModule(ProgramModule, {
@ -71,7 +73,20 @@ describe('SelectorScopeRegistry', () => {
imports: [new AbsoluteReference(SomeModule, SomeModule.name !, 'some_library', 'SomeModule')], imports: [new AbsoluteReference(SomeModule, SomeModule.name !, 'some_library', 'SomeModule')],
}); });
registry.registerSelector(ProgramCmp, 'program-cmp'); const ref = new ResolvedReference(ProgramCmp, ProgramCmp.name !);
registry.registerDirective(ProgramCmp, {
name: 'ProgramCmp',
ref: ProgramCmpRef,
directive: ProgramCmpRef,
selector: 'program-cmp',
isComponent: true,
exportAs: null,
inputs: {},
outputs: {},
queries: [],
hasNgTemplateContextGuard: false,
ngTemplateGuards: [],
});
const scope = registry.lookupCompilationScope(ProgramCmp) !; const scope = registry.lookupCompilationScope(ProgramCmp) !;
expect(scope).toBeDefined(); expect(scope).toBeDefined();
@ -120,6 +135,8 @@ describe('SelectorScopeRegistry', () => {
expect(ProgramModule).toBeDefined(); expect(ProgramModule).toBeDefined();
expect(SomeModule).toBeDefined(); expect(SomeModule).toBeDefined();
const ProgramCmpRef = new ResolvedReference(ProgramCmp, ProgramCmp.name !);
const registry = new SelectorScopeRegistry(checker, host); const registry = new SelectorScopeRegistry(checker, host);
registry.registerModule(ProgramModule, { registry.registerModule(ProgramModule, {
@ -128,7 +145,19 @@ describe('SelectorScopeRegistry', () => {
imports: [], imports: [],
}); });
registry.registerSelector(ProgramCmp, 'program-cmp'); registry.registerDirective(ProgramCmp, {
name: 'ProgramCmp',
ref: ProgramCmpRef,
directive: ProgramCmpRef,
selector: 'program-cmp',
isComponent: true,
exportAs: null,
inputs: {},
outputs: {},
queries: [],
hasNgTemplateContextGuard: false,
ngTemplateGuards: [],
});
const scope = registry.lookupCompilationScope(ProgramCmp) !; const scope = registry.lookupCompilationScope(ProgramCmp) !;
expect(scope).toBeDefined(); expect(scope).toBeDefined();

View File

@ -183,10 +183,10 @@ export class ResolvedReference<T extends ts.Node = ts.Node> extends Reference<T>
* An `AbsoluteReference` can be resolved to an `Expression`, and if that expression is an import * An `AbsoluteReference` can be resolved to an `Expression`, and if that expression is an import
* the module specifier will be an absolute module name, not a relative path. * the module specifier will be an absolute module name, not a relative path.
*/ */
export class AbsoluteReference extends Reference { export class AbsoluteReference<T extends ts.Node> extends Reference<T> {
private identifiers: ts.Identifier[] = []; private identifiers: ts.Identifier[] = [];
constructor( constructor(
node: ts.Node, private primaryIdentifier: ts.Identifier, readonly moduleName: string, node: T, private primaryIdentifier: ts.Identifier, readonly moduleName: string,
readonly symbolName: string) { readonly symbolName: string) {
super(node); super(node);
} }