fix(ivy): Support selector-less directive as base classes (#32125)
Following #31379, this adds support for directives without a selector to Ivy. PR Close #32125
This commit is contained in:
@ -72,6 +72,10 @@ export class DirectiveDecoratorHandler implements
|
||||
});
|
||||
}
|
||||
|
||||
if (analysis && !analysis.selector) {
|
||||
this.metaRegistry.registerAbstractDirective(node);
|
||||
}
|
||||
|
||||
if (analysis === undefined) {
|
||||
return {};
|
||||
}
|
||||
@ -102,7 +106,10 @@ export class DirectiveDecoratorHandler implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to extract metadata from a `Directive` or `Component`.
|
||||
* Helper function to extract metadata from a `Directive` or `Component`. `Directive`s without a
|
||||
* selector are allowed to be used for abstract base classes. These abstract directives should not
|
||||
* appear in the declarations of an `NgModule` and additional verification is done when processing
|
||||
* the module.
|
||||
*/
|
||||
export function extractDirectiveMetadata(
|
||||
clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
|
||||
@ -112,17 +119,22 @@ export function extractDirectiveMetadata(
|
||||
metadata: R3DirectiveMetadata,
|
||||
decoratedElements: ClassMember[],
|
||||
}|undefined {
|
||||
if (decorator.args === null || decorator.args.length !== 1) {
|
||||
let directive: Map<string, ts.Expression>;
|
||||
if (decorator.args === null || decorator.args.length === 0) {
|
||||
directive = new Map<string, ts.Expression>();
|
||||
} else if (decorator.args.length !== 1) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DECORATOR_ARITY_WRONG, decorator.node,
|
||||
`Incorrect number of arguments to @${decorator.name} decorator`);
|
||||
} else {
|
||||
const meta = unwrapExpression(decorator.args[0]);
|
||||
if (!ts.isObjectLiteralExpression(meta)) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta,
|
||||
`@${decorator.name} argument must be literal.`);
|
||||
}
|
||||
directive = reflectObjectLiteral(meta);
|
||||
}
|
||||
const meta = unwrapExpression(decorator.args[0]);
|
||||
if (!ts.isObjectLiteralExpression(meta)) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DECORATOR_ARG_NOT_LITERAL, meta, `@${decorator.name} argument must be literal.`);
|
||||
}
|
||||
const directive = reflectObjectLiteral(meta);
|
||||
|
||||
if (directive.has('jit')) {
|
||||
// The only allowed value is true, so there's no need to expand further.
|
||||
@ -188,9 +200,11 @@ export function extractDirectiveMetadata(
|
||||
}
|
||||
// use default selector in case selector is an empty string
|
||||
selector = resolved === '' ? defaultSelector : resolved;
|
||||
}
|
||||
if (!selector) {
|
||||
throw new Error(`Directive ${clazz.name.text} has no selector, please add it!`);
|
||||
if (!selector) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DIRECTIVE_MISSING_SELECTOR, expr,
|
||||
`Directive ${clazz.name.text} has no selector, please add it!`);
|
||||
}
|
||||
}
|
||||
|
||||
const host = extractHostBindings(decoratedElements, evaluator, coreModule, directive);
|
||||
|
@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {MetadataRegistry} from '../../metadata';
|
||||
import {MetadataReader, MetadataRegistry} from '../../metadata';
|
||||
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
|
||||
import {NgModuleRouteAnalyzer} from '../../routing';
|
||||
@ -40,7 +40,8 @@ export interface NgModuleAnalysis {
|
||||
export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalysis, Decorator> {
|
||||
constructor(
|
||||
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
||||
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
|
||||
private metaReader: MetadataReader, private metaRegistry: MetadataRegistry,
|
||||
private scopeRegistry: LocalModuleScopeRegistry,
|
||||
private referencesRegistry: ReferencesRegistry, private isCore: boolean,
|
||||
private routeAnalyzer: NgModuleRouteAnalyzer|null, private refEmitter: ReferenceEmitter,
|
||||
private defaultImportRecorder: DefaultImportRecorder, private localeId?: string) {}
|
||||
@ -210,15 +211,23 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||
const scope = this.scopeRegistry.getScopeOfModule(node);
|
||||
const diagnostics = this.scopeRegistry.getDiagnosticsOfModule(node) || undefined;
|
||||
|
||||
// Using the scope information, extend the injector's imports using the modules that are
|
||||
// specified as module exports.
|
||||
if (scope !== null) {
|
||||
// Using the scope information, extend the injector's imports using the modules that are
|
||||
// specified as module exports.
|
||||
const context = getSourceFile(node);
|
||||
for (const exportRef of analysis.exports) {
|
||||
if (isNgModule(exportRef.node, scope.compilation)) {
|
||||
analysis.ngInjectorDef.imports.push(this.refEmitter.emit(exportRef, context));
|
||||
}
|
||||
}
|
||||
|
||||
for (const decl of analysis.declarations) {
|
||||
if (this.metaReader.isAbstractDirective(decl)) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DIRECTIVE_MISSING_SELECTOR, decl.node,
|
||||
`Directive ${decl.node.name.text} has no selector, please add it!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scope === null || scope.reexports === null) {
|
||||
|
@ -8,10 +8,11 @@
|
||||
import {WrappedNodeExpr} from '@angular/compiler';
|
||||
import {R3Reference} from '@angular/compiler/src/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||
import {DtsMetadataReader, LocalMetadataRegistry} from '../../metadata';
|
||||
import {CompoundMetadataReader, DtsMetadataReader, LocalMetadataRegistry} from '../../metadata';
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
|
||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||
@ -59,6 +60,7 @@ runInEachFileSystem(() => {
|
||||
const evaluator = new PartialEvaluator(reflectionHost, checker);
|
||||
const referencesRegistry = new NoopReferencesRegistry();
|
||||
const metaRegistry = new LocalMetadataRegistry();
|
||||
const metaReader = new CompoundMetadataReader([metaRegistry]);
|
||||
const dtsReader = new DtsMetadataReader(checker, reflectionHost);
|
||||
const scopeRegistry = new LocalModuleScopeRegistry(
|
||||
metaRegistry, new MetadataDtsModuleScopeResolver(dtsReader, null),
|
||||
@ -66,8 +68,8 @@ runInEachFileSystem(() => {
|
||||
const refEmitter = new ReferenceEmitter([new LocalIdentifierStrategy()]);
|
||||
|
||||
const handler = new NgModuleDecoratorHandler(
|
||||
reflectionHost, evaluator, metaRegistry, scopeRegistry, referencesRegistry, false, null,
|
||||
refEmitter, NOOP_DEFAULT_IMPORT_RECORDER);
|
||||
reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry,
|
||||
false, null, refEmitter, NOOP_DEFAULT_IMPORT_RECORDER);
|
||||
const TestModule =
|
||||
getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration);
|
||||
const detected =
|
||||
|
Reference in New Issue
Block a user