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:
atscott
2019-08-12 14:56:30 -07:00
committed by Andrew Kushnir
parent bb3c684b98
commit cfed0c0cf1
19 changed files with 169 additions and 79 deletions

View File

@ -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);

View File

@ -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) {

View File

@ -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 =