mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(ivy): verify Host Bindings and Host Listeners before compiling them (#28356)
Prior to this change we may encounter some errors (like pipes being used where they should not be used) while compiling Host Bindings and Listeners. With this update we move validation logic to the analyze phase and throw an error if something is wrong. This also aligns error messages between Ivy and VE. PR Close #28356
This commit is contained in:
committed by
Jason Aden
parent
ad499628cb
commit
76cedb8bf3
@@ -38,6 +38,8 @@ export interface CompilerFacade {
|
||||
compileComponent(
|
||||
angularCoreEnv: CoreEnvironment, sourceMapUrl: string, meta: R3ComponentMetadataFacade): any;
|
||||
|
||||
createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan;
|
||||
|
||||
R3ResolvedDependencyType: typeof R3ResolvedDependencyType;
|
||||
}
|
||||
|
||||
@@ -109,7 +111,7 @@ export interface R3DirectiveMetadataFacade {
|
||||
name: string;
|
||||
type: any;
|
||||
typeArgumentCount: number;
|
||||
typeSourceSpan: null;
|
||||
typeSourceSpan: ParseSourceSpan;
|
||||
deps: R3DependencyMetadataFacade[]|null;
|
||||
selector: string|null;
|
||||
queries: R3QueryMetadataFacade[];
|
||||
@@ -148,3 +150,9 @@ export interface R3QueryMetadataFacade {
|
||||
descendants: boolean;
|
||||
read: any|null;
|
||||
}
|
||||
|
||||
export interface ParseSourceSpan {
|
||||
start: any;
|
||||
end: any;
|
||||
details: any;
|
||||
}
|
||||
@@ -54,8 +54,11 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
|
||||
throw new Error(error.join('\n'));
|
||||
}
|
||||
|
||||
const sourceMapUrl = `ng://${renderStringify(type)}/template.html`;
|
||||
const meta: R3ComponentMetadataFacade = {
|
||||
...directiveMetadata(type, metadata),
|
||||
typeSourceSpan:
|
||||
compiler.createParseSourceSpan('Component', renderStringify(type), sourceMapUrl),
|
||||
template: metadata.template || '',
|
||||
preserveWhitespaces: metadata.preserveWhitespaces || false,
|
||||
styles: metadata.styles || EMPTY_ARRAY,
|
||||
@@ -68,8 +71,7 @@ export function compileComponent(type: Type<any>, metadata: Component): void {
|
||||
interpolation: metadata.interpolation,
|
||||
viewProviders: metadata.viewProviders || null,
|
||||
};
|
||||
ngComponentDef = compiler.compileComponent(
|
||||
angularCoreEnv, `ng://${renderStringify(type)}/template.html`, meta);
|
||||
ngComponentDef = compiler.compileComponent(angularCoreEnv, sourceMapUrl, meta);
|
||||
|
||||
// When NgModule decorator executed, we enqueued the module definition such that
|
||||
// it would only dequeue and add itself as module scope to all of its declarations,
|
||||
@@ -111,9 +113,13 @@ export function compileDirective(type: Type<any>, directive: Directive): void {
|
||||
Object.defineProperty(type, NG_DIRECTIVE_DEF, {
|
||||
get: () => {
|
||||
if (ngDirectiveDef === null) {
|
||||
const name = type && type.name;
|
||||
const sourceMapUrl = `ng://${name}/ngDirectiveDef.js`;
|
||||
const compiler = getCompilerFacade();
|
||||
const facade = directiveMetadata(type as ComponentType<any>, directive);
|
||||
ngDirectiveDef = getCompilerFacade().compileDirective(
|
||||
angularCoreEnv, `ng://${type && type.name}/ngDirectiveDef.js`, facade);
|
||||
facade.typeSourceSpan =
|
||||
compiler.createParseSourceSpan('Directive', renderStringify(type), sourceMapUrl);
|
||||
ngDirectiveDef = compiler.compileDirective(angularCoreEnv, sourceMapUrl, facade);
|
||||
}
|
||||
return ngDirectiveDef;
|
||||
},
|
||||
|
||||
@@ -943,18 +943,17 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
expect(tc.properties['title']).toBe(undefined);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-725: Pipes in host bindings fail with a cryptic error')
|
||||
.it('should not allow pipes in hostProperties', () => {
|
||||
@Directive({selector: '[host-properties]', host: {'[id]': 'id | uppercase'}})
|
||||
class DirectiveWithHostProps {
|
||||
}
|
||||
it('should not allow pipes in hostProperties', () => {
|
||||
@Directive({selector: '[host-properties]', host: {'[id]': 'id | uppercase'}})
|
||||
class DirectiveWithHostProps {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]});
|
||||
const template = '<div host-properties></div>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
expect(() => TestBed.createComponent(MyComp))
|
||||
.toThrowError(/Host binding expression cannot contain pipes/);
|
||||
});
|
||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]});
|
||||
const template = '<div host-properties></div>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
expect(() => TestBed.createComponent(MyComp))
|
||||
.toThrowError(/Host binding expression cannot contain pipes/);
|
||||
});
|
||||
|
||||
it('should not use template variables for expressions in hostListeners', () => {
|
||||
@Directive({selector: '[host-listener]', host: {'(click)': 'doIt(id, unknownProp)'}})
|
||||
@@ -979,18 +978,17 @@ function declareTests(config?: {useJit: boolean}) {
|
||||
expect(dir.receivedArgs).toEqual(['one', undefined]);
|
||||
});
|
||||
|
||||
fixmeIvy('FW-742: Pipes in host listeners should throw a descriptive error')
|
||||
.it('should not allow pipes in hostListeners', () => {
|
||||
@Directive({selector: '[host-listener]', host: {'(click)': 'doIt() | somePipe'}})
|
||||
class DirectiveWithHostListener {
|
||||
}
|
||||
it('should not allow pipes in hostListeners', () => {
|
||||
@Directive({selector: '[host-listener]', host: {'(click)': 'doIt() | somePipe'}})
|
||||
class DirectiveWithHostListener {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostListener]});
|
||||
const template = '<div host-listener></div>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
expect(() => TestBed.createComponent(MyComp))
|
||||
.toThrowError(/Cannot have a pipe in an action expression/);
|
||||
});
|
||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostListener]});
|
||||
const template = '<div host-listener></div>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
expect(() => TestBed.createComponent(MyComp))
|
||||
.toThrowError(/Cannot have a pipe in an action expression/);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user