Skip to content
Permalink
Browse files

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
  • Loading branch information...
atscott authored and AndrewKushnir committed Aug 12, 2019
1 parent bb3c684 commit cfed0c0cf1238e5ad931598ce04c32f8c9a75143
@@ -86,9 +86,9 @@ export class DecorationAnalyzer {
this.reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, this.isCore, this.reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, this.isCore,
/* strictCtorDeps */ false), /* strictCtorDeps */ false),
new NgModuleDecoratorHandler( new NgModuleDecoratorHandler(
this.reflectionHost, this.evaluator, this.fullRegistry, this.scopeRegistry, this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry,
this.referencesRegistry, this.isCore, /* routeAnalyzer */ null, this.refEmitter, this.scopeRegistry, this.referencesRegistry, this.isCore, /* routeAnalyzer */ null,
NOOP_DEFAULT_IMPORT_RECORDER), this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER),
new PipeDecoratorHandler( new PipeDecoratorHandler(
this.reflectionHost, this.evaluator, this.metaRegistry, NOOP_DEFAULT_IMPORT_RECORDER, this.reflectionHost, this.evaluator, this.metaRegistry, NOOP_DEFAULT_IMPORT_RECORDER,
this.isCore), this.isCore),
@@ -72,6 +72,10 @@ export class DirectiveDecoratorHandler implements
}); });
} }


if (analysis && !analysis.selector) {
this.metaRegistry.registerAbstractDirective(node);
}

if (analysis === undefined) { if (analysis === undefined) {
return {}; 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( export function extractDirectiveMetadata(
clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost, clazz: ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
@@ -112,17 +119,22 @@ export function extractDirectiveMetadata(
metadata: R3DirectiveMetadata, metadata: R3DirectiveMetadata,
decoratedElements: ClassMember[], decoratedElements: ClassMember[],
}|undefined { }|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( throw new FatalDiagnosticError(
ErrorCode.DECORATOR_ARITY_WRONG, decorator.node, ErrorCode.DECORATOR_ARITY_WRONG, decorator.node,
`Incorrect number of arguments to @${decorator.name} decorator`); `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')) { if (directive.has('jit')) {
// The only allowed value is true, so there's no need to expand further. // 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 // use default selector in case selector is an empty string
selector = resolved === '' ? defaultSelector : resolved; selector = resolved === '' ? defaultSelector : resolved;
} if (!selector) {
if (!selector) { throw new FatalDiagnosticError(
throw new Error(`Directive ${clazz.name.text} has no selector, please add it!`); ErrorCode.DIRECTIVE_MISSING_SELECTOR, expr,
`Directive ${clazz.name.text} has no selector, please add it!`);
}
} }


const host = extractHostBindings(decoratedElements, evaluator, coreModule, directive); const host = extractHostBindings(decoratedElements, evaluator, coreModule, directive);
@@ -11,7 +11,7 @@ import * as ts from 'typescript';


import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports'; import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports';
import {MetadataRegistry} from '../../metadata'; import {MetadataReader, MetadataRegistry} from '../../metadata';
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator'; import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection'; import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral, typeNodeToValueExpr} from '../../reflection';
import {NgModuleRouteAnalyzer} from '../../routing'; import {NgModuleRouteAnalyzer} from '../../routing';
@@ -40,7 +40,8 @@ export interface NgModuleAnalysis {
export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalysis, Decorator> { export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalysis, Decorator> {
constructor( constructor(
private reflector: ReflectionHost, private evaluator: PartialEvaluator, 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 referencesRegistry: ReferencesRegistry, private isCore: boolean,
private routeAnalyzer: NgModuleRouteAnalyzer|null, private refEmitter: ReferenceEmitter, private routeAnalyzer: NgModuleRouteAnalyzer|null, private refEmitter: ReferenceEmitter,
private defaultImportRecorder: DefaultImportRecorder, private localeId?: string) {} private defaultImportRecorder: DefaultImportRecorder, private localeId?: string) {}
@@ -210,15 +211,23 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
const scope = this.scopeRegistry.getScopeOfModule(node); const scope = this.scopeRegistry.getScopeOfModule(node);
const diagnostics = this.scopeRegistry.getDiagnosticsOfModule(node) || undefined; 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) { 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); const context = getSourceFile(node);
for (const exportRef of analysis.exports) { for (const exportRef of analysis.exports) {
if (isNgModule(exportRef.node, scope.compilation)) { if (isNgModule(exportRef.node, scope.compilation)) {
analysis.ngInjectorDef.imports.push(this.refEmitter.emit(exportRef, context)); 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) { if (scope === null || scope.reexports === null) {
@@ -8,10 +8,11 @@
import {WrappedNodeExpr} from '@angular/compiler'; import {WrappedNodeExpr} from '@angular/compiler';
import {R3Reference} from '@angular/compiler/src/compiler'; import {R3Reference} from '@angular/compiler/src/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';

import {absoluteFrom} from '../../file_system'; import {absoluteFrom} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing'; import {runInEachFileSystem} from '../../file_system/testing';
import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports'; 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 {PartialEvaluator} from '../../partial_evaluator';
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection'; import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope'; import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
@@ -59,15 +60,16 @@ runInEachFileSystem(() => {
const evaluator = new PartialEvaluator(reflectionHost, checker); const evaluator = new PartialEvaluator(reflectionHost, checker);
const referencesRegistry = new NoopReferencesRegistry(); const referencesRegistry = new NoopReferencesRegistry();
const metaRegistry = new LocalMetadataRegistry(); const metaRegistry = new LocalMetadataRegistry();
const metaReader = new CompoundMetadataReader([metaRegistry]);
const dtsReader = new DtsMetadataReader(checker, reflectionHost); const dtsReader = new DtsMetadataReader(checker, reflectionHost);
const scopeRegistry = new LocalModuleScopeRegistry( const scopeRegistry = new LocalModuleScopeRegistry(
metaRegistry, new MetadataDtsModuleScopeResolver(dtsReader, null), metaRegistry, new MetadataDtsModuleScopeResolver(dtsReader, null),
new ReferenceEmitter([]), null); new ReferenceEmitter([]), null);
const refEmitter = new ReferenceEmitter([new LocalIdentifierStrategy()]); const refEmitter = new ReferenceEmitter([new LocalIdentifierStrategy()]);


const handler = new NgModuleDecoratorHandler( const handler = new NgModuleDecoratorHandler(
reflectionHost, evaluator, metaRegistry, scopeRegistry, referencesRegistry, false, null, reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry,
refEmitter, NOOP_DEFAULT_IMPORT_RECORDER); false, null, refEmitter, NOOP_DEFAULT_IMPORT_RECORDER);
const TestModule = const TestModule =
getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration); getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration);
const detected = const detected =
@@ -24,6 +24,7 @@ export enum ErrorCode {
COMPONENT_MISSING_TEMPLATE = 2001, COMPONENT_MISSING_TEMPLATE = 2001,
PIPE_MISSING_NAME = 2002, PIPE_MISSING_NAME = 2002,
PARAM_MISSING_TOKEN = 2003, PARAM_MISSING_TOKEN = 2003,
DIRECTIVE_MISSING_SELECTOR = 2004,


SYMBOL_NOT_EXPORTED = 3001, SYMBOL_NOT_EXPORTED = 3001,
SYMBOL_EXPORTED_UNDER_DIFFERENT_NAME = 3002, SYMBOL_EXPORTED_UNDER_DIFFERENT_NAME = 3002,
@@ -93,6 +93,19 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta
metadata.ngModuleMeta.set(meta.ref.node, meta); metadata.ngModuleMeta.set(meta.ref.node, meta);
} }


isAbstractDirective(ref: Reference<ClassDeclaration>): boolean {
if (!this.metadata.has(ref.node.getSourceFile())) {
return false;
}
const metadata = this.metadata.get(ref.node.getSourceFile()) !;
return metadata.abstractDirectives.has(ref.node);
}

registerAbstractDirective(clazz: ClassDeclaration): void {
const metadata = this.ensureMetadata(clazz.getSourceFile());
metadata.abstractDirectives.add(clazz);
}

getDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta|null { getDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta|null {
if (!this.metadata.has(ref.node.getSourceFile())) { if (!this.metadata.has(ref.node.getSourceFile())) {
return null; return null;
@@ -187,6 +200,7 @@ class FileMetadata {
/** A set of source files that this file depends upon. */ /** A set of source files that this file depends upon. */
fileDependencies = new Set<ts.SourceFile>(); fileDependencies = new Set<ts.SourceFile>();
resourcePaths = new Set<string>(); resourcePaths = new Set<string>();
abstractDirectives = new Set<ClassDeclaration>();
directiveMeta = new Map<ClassDeclaration, DirectiveMeta>(); directiveMeta = new Map<ClassDeclaration, DirectiveMeta>();
ngModuleMeta = new Map<ClassDeclaration, NgModuleMeta>(); ngModuleMeta = new Map<ClassDeclaration, NgModuleMeta>();
pipeMeta = new Map<ClassDeclaration, PipeMeta>(); pipeMeta = new Map<ClassDeclaration, PipeMeta>();
@@ -75,6 +75,7 @@ export interface PipeMeta {
* or a registry. * or a registry.
*/ */
export interface MetadataReader { export interface MetadataReader {
isAbstractDirective(node: Reference<ClassDeclaration>): boolean;
getDirectiveMetadata(node: Reference<ClassDeclaration>): DirectiveMeta|null; getDirectiveMetadata(node: Reference<ClassDeclaration>): DirectiveMeta|null;
getNgModuleMetadata(node: Reference<ClassDeclaration>): NgModuleMeta|null; getNgModuleMetadata(node: Reference<ClassDeclaration>): NgModuleMeta|null;
getPipeMetadata(node: Reference<ClassDeclaration>): PipeMeta|null; getPipeMetadata(node: Reference<ClassDeclaration>): PipeMeta|null;
@@ -84,6 +85,7 @@ export interface MetadataReader {
* Registers new metadata for directives, pipes, and modules. * Registers new metadata for directives, pipes, and modules.
*/ */
export interface MetadataRegistry { export interface MetadataRegistry {
registerAbstractDirective(clazz: ClassDeclaration): void;
registerDirectiveMetadata(meta: DirectiveMeta): void; registerDirectiveMetadata(meta: DirectiveMeta): void;
registerNgModuleMetadata(meta: NgModuleMeta): void; registerNgModuleMetadata(meta: NgModuleMeta): void;
registerPipeMetadata(meta: PipeMeta): void; registerPipeMetadata(meta: PipeMeta): void;
@@ -21,6 +21,11 @@ import {extractDirectiveGuards, extractReferencesFromType, readStringArrayType,
export class DtsMetadataReader implements MetadataReader { export class DtsMetadataReader implements MetadataReader {
constructor(private checker: ts.TypeChecker, private reflector: ReflectionHost) {} constructor(private checker: ts.TypeChecker, private reflector: ReflectionHost) {}


isAbstractDirective(ref: Reference<ClassDeclaration>): boolean {
const meta = this.getDirectiveMetadata(ref);
return meta !== null && meta.selector === null;
}

/** /**
* Read the metadata from a class that has already been compiled somehow (either it's in a .d.ts * 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). * file, or in a .ts file with a handwritten definition).
@@ -16,10 +16,14 @@ import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta}
* unit, which supports both reading and registering. * unit, which supports both reading and registering.
*/ */
export class LocalMetadataRegistry implements MetadataRegistry, MetadataReader { export class LocalMetadataRegistry implements MetadataRegistry, MetadataReader {
private abstractDirectives = new Set<ClassDeclaration>();
private directives = new Map<ClassDeclaration, DirectiveMeta>(); private directives = new Map<ClassDeclaration, DirectiveMeta>();
private ngModules = new Map<ClassDeclaration, NgModuleMeta>(); private ngModules = new Map<ClassDeclaration, NgModuleMeta>();
private pipes = new Map<ClassDeclaration, PipeMeta>(); private pipes = new Map<ClassDeclaration, PipeMeta>();


isAbstractDirective(ref: Reference<ClassDeclaration>): boolean {
return this.abstractDirectives.has(ref.node);
}
getDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta|null { getDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta|null {
return this.directives.has(ref.node) ? this.directives.get(ref.node) ! : null; return this.directives.has(ref.node) ? this.directives.get(ref.node) ! : null;
} }
@@ -30,6 +34,7 @@ export class LocalMetadataRegistry implements MetadataRegistry, MetadataReader {
return this.pipes.has(ref.node) ? this.pipes.get(ref.node) ! : null; return this.pipes.has(ref.node) ? this.pipes.get(ref.node) ! : null;
} }


registerAbstractDirective(clazz: ClassDeclaration): void { this.abstractDirectives.add(clazz); }
registerDirectiveMetadata(meta: DirectiveMeta): void { this.directives.set(meta.ref.node, meta); } registerDirectiveMetadata(meta: DirectiveMeta): void { this.directives.set(meta.ref.node, meta); }
registerNgModuleMetadata(meta: NgModuleMeta): void { this.ngModules.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); } registerPipeMetadata(meta: PipeMeta): void { this.pipes.set(meta.ref.node, meta); }
@@ -41,6 +46,12 @@ export class LocalMetadataRegistry implements MetadataRegistry, MetadataReader {
export class CompoundMetadataRegistry implements MetadataRegistry { export class CompoundMetadataRegistry implements MetadataRegistry {
constructor(private registries: MetadataRegistry[]) {} constructor(private registries: MetadataRegistry[]) {}


registerAbstractDirective(clazz: ClassDeclaration) {
for (const registry of this.registries) {
registry.registerAbstractDirective(clazz);
}
}

registerDirectiveMetadata(meta: DirectiveMeta): void { registerDirectiveMetadata(meta: DirectiveMeta): void {
for (const registry of this.registries) { for (const registry of this.registries) {
registry.registerDirectiveMetadata(meta); registry.registerDirectiveMetadata(meta);
@@ -125,6 +125,10 @@ function extractTemplateGuard(member: ClassMember): TemplateGuardMeta|null {
export class CompoundMetadataReader implements MetadataReader { export class CompoundMetadataReader implements MetadataReader {
constructor(private readers: MetadataReader[]) {} constructor(private readers: MetadataReader[]) {}


isAbstractDirective(node: Reference<ClassDeclaration>): boolean {
return this.readers.some(r => r.isAbstractDirective(node));
}

getDirectiveMetadata(node: Reference<ClassDeclaration<ts.Declaration>>): DirectiveMeta|null { getDirectiveMetadata(node: Reference<ClassDeclaration<ts.Declaration>>): DirectiveMeta|null {
for (const reader of this.readers) { for (const reader of this.readers) {
const meta = reader.getDirectiveMetadata(node); const meta = reader.getDirectiveMetadata(node);
@@ -514,9 +514,9 @@ export class NgtscProgram implements api.Program {
this.reflector, this.defaultImportTracker, this.isCore, this.reflector, this.defaultImportTracker, this.isCore,
this.options.strictInjectionParameters || false), this.options.strictInjectionParameters || false),
new NgModuleDecoratorHandler( new NgModuleDecoratorHandler(
this.reflector, evaluator, metaRegistry, scopeRegistry, referencesRegistry, this.isCore, this.reflector, evaluator, this.metaReader, metaRegistry, scopeRegistry,
this.routeAnalyzer, this.refEmitter, this.defaultImportTracker, referencesRegistry, this.isCore, this.routeAnalyzer, this.refEmitter,
this.options.i18nInLocale), this.defaultImportTracker, this.options.i18nInLocale),
new PipeDecoratorHandler( new PipeDecoratorHandler(
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore), this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore),
]; ];
@@ -117,6 +117,8 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
} }
} }


registerAbstractDirective(clazz: ClassDeclaration): void {}

registerDirectiveMetadata(directive: DirectiveMeta): void {} registerDirectiveMetadata(directive: DirectiveMeta): void {}


registerPipeMetadata(pipe: PipeMeta): void {} registerPipeMetadata(pipe: PipeMeta): void {}
@@ -669,44 +669,6 @@ describe('compiler compliance', () => {
source, EmptyOutletComponentDefinition, 'Incorrect EmptyOutletComponent.ngComponentDef'); source, EmptyOutletComponentDefinition, 'Incorrect EmptyOutletComponent.ngComponentDef');
}); });


it('should not support directives without selector', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Directive({})
export class EmptyOutletDirective {}
@NgModule({declarations: [EmptyOutletDirective]})
export class MyModule{}
`
}
};

expect(() => compile(files, angularFiles))
.toThrowError('Directive EmptyOutletDirective has no selector, please add it!');
});

it('should not support directives with empty selector', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Directive({selector: ''})
export class EmptyOutletDirective {}
@NgModule({declarations: [EmptyOutletDirective]})
export class MyModule{}
`
}
};

expect(() => compile(files, angularFiles))
.toThrowError('Directive EmptyOutletDirective has no selector, please add it!');
});

it('should not treat ElementRef, ViewContainerRef, or ChangeDetectorRef specially when injecting', it('should not treat ElementRef, ViewContainerRef, or ChangeDetectorRef specially when injecting',
() => { () => {
const files = { const files = {
@@ -1008,19 +1008,43 @@ runInEachFileSystem(os => {
expect(jsContents).toContain('selectors: [["ng-component"]]'); expect(jsContents).toContain('selectors: [["ng-component"]]');
}); });


it('should throw if selector is missing in Directive decorator params', () => { it('should allow directives with no selector that are not in NgModules', () => {
env.write('test.ts', ` env.write('main.ts', `
import {Directive} from '@angular/core'; import {Directive} from '@angular/core';
@Directive({ @Directive({})
inputs: ['a', 'b'] export class BaseDir {}
})
export class TestDir {} @Directive({})
`); export abstract class AbstractBaseDir {}
@Directive()
export abstract class EmptyDir {}
@Directive({
inputs: ['a', 'b']
})
export class TestDirWithInputs {}
`);
const errors = env.driveDiagnostics();
expect(errors.length).toBe(0);
});

it('should not allow directives with no selector that are in NgModules', () => {
env.write('main.ts', `
import {Directive, NgModule} from '@angular/core';
@Directive({})
export class BaseDir {}
@NgModule({
declarations: [BaseDir],
})
export class MyModule {}
`);
const errors = env.driveDiagnostics(); const errors = env.driveDiagnostics();
expect(trim(errors[0].messageText as string)) expect(trim(errors[0].messageText as string))
.toContain('Directive TestDir has no selector, please add it!'); .toContain('Directive BaseDir has no selector, please add it!');
}); });


it('should throw if Directive selector is an empty string', () => { it('should throw if Directive selector is an empty string', () => {
@@ -132,10 +132,6 @@ export function compileDirectiveFromMetadata(
addFeatures(definitionMap, meta); addFeatures(definitionMap, meta);
const expression = o.importExpr(R3.defineDirective).callFn([definitionMap.toLiteralMap()]); const expression = o.importExpr(R3.defineDirective).callFn([definitionMap.toLiteralMap()]);


if (!meta.selector) {
throw new Error(`Directive ${meta.name} has no selector, please add it!`);
}

const type = createTypeForDef(meta, R3.DirectiveDefWithMeta); const type = createTypeForDef(meta, R3.DirectiveDefWithMeta);
return {expression, type, statements}; return {expression, type, statements};
} }

0 comments on commit cfed0c0

Please sign in to comment.
You can’t perform that action at this time.