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:
parent
5f1273ba2e
commit
868047e87f
|
@ -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",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -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};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue