feat(compiler): detect dangling property bindings
BREAKING CHANGE: compiler will throw on binding to non-existing properties. Till now it was possible to have a binding to a non-existing property, ex.: `<div [foo]="exp">`. From now on this is compilation error - any property binding needs to have at least one associated property: eaither on an HTML element or on any directive associated with a given element (directives' properites need to be declared using the `properties` field in the `@Directive` / `@Component` annotation). Closes #2598
This commit is contained in:
@ -149,6 +149,7 @@ export class DirectiveParser implements CompileStep {
|
||||
if (isPresent(bindingAst)) {
|
||||
directiveBinderBuilder.bindProperty(dirProperty, bindingAst);
|
||||
}
|
||||
compileElement.bindElement().bindPropertyToDirective(dashCaseToCamelCase(elProp));
|
||||
}
|
||||
|
||||
_bindDirectiveEvent(eventName, action, compileElement, directiveBinderBuilder) {
|
||||
|
@ -18,7 +18,7 @@ const CLASS_PREFIX = 'class.';
|
||||
const STYLE_PREFIX = 'style.';
|
||||
|
||||
export class PropertySetterFactory {
|
||||
private static _noopSetter(el, value) {}
|
||||
static noopSetter(el, value) {}
|
||||
|
||||
private _lazyPropertySettersCache: StringMap<string, Function> = StringMapWrapper.create();
|
||||
private _eagerPropertySettersCache: StringMap<string, Function> = StringMapWrapper.create();
|
||||
@ -69,7 +69,7 @@ export class PropertySetterFactory {
|
||||
if (DOM.hasProperty(protoElement, property)) {
|
||||
setterFn = reflector.setter(property);
|
||||
} else {
|
||||
setterFn = PropertySetterFactory._noopSetter;
|
||||
setterFn = PropertySetterFactory.noopSetter;
|
||||
}
|
||||
StringMapWrapper.set(this._eagerPropertySettersCache, property, setterFn);
|
||||
}
|
||||
|
@ -75,10 +75,17 @@ export class ProtoViewBuilder {
|
||||
});
|
||||
|
||||
MapWrapper.forEach(ebb.propertyBindings, (_, propertyName) => {
|
||||
var propSetter =
|
||||
setterFactory.createSetter(ebb.element, isPresent(ebb.componentId), propertyName);
|
||||
|
||||
propertySetters.set(
|
||||
propertyName,
|
||||
setterFactory.createSetter(ebb.element, isPresent(ebb.componentId), propertyName));
|
||||
if (propSetter === PropertySetterFactory.noopSetter) {
|
||||
if (!SetWrapper.has(ebb.propertyBindingsToDirectives, propertyName)) {
|
||||
throw new BaseException(
|
||||
`Can't bind to '${propertyName}' since it isn't a know property of the '${DOM.tagName(ebb.element).toLowerCase()}' element and there are no matching directives with a corresponding property`);
|
||||
}
|
||||
}
|
||||
|
||||
propertySetters.set(propertyName, propSetter);
|
||||
});
|
||||
|
||||
var nestedProtoView =
|
||||
@ -170,6 +177,7 @@ export class ElementBinderBuilder {
|
||||
nestedProtoView: ProtoViewBuilder = null;
|
||||
propertyBindings: Map<string, ASTWithSource> = new Map();
|
||||
variableBindings: Map<string, string> = new Map();
|
||||
propertyBindingsToDirectives: Set<string> = new Set();
|
||||
eventBindings: List<api.EventBinding> = [];
|
||||
eventBuilder: EventBuilder = new EventBuilder();
|
||||
textBindingNodes: List</*node*/ any> = [];
|
||||
@ -210,6 +218,12 @@ export class ElementBinderBuilder {
|
||||
|
||||
bindProperty(name, expression) { this.propertyBindings.set(name, expression); }
|
||||
|
||||
bindPropertyToDirective(name: string) {
|
||||
// we are filling in a set of property names that are bound to a property
|
||||
// of at least one directive. This allows us to report "dangling" bindings.
|
||||
this.propertyBindingsToDirectives.add(name);
|
||||
}
|
||||
|
||||
bindVariable(name, value) {
|
||||
// When current is a view root, the variable bindings are set to the *nested* proto view.
|
||||
// The root view conceptually signifies a new "block scope" (the nested view), to which
|
||||
|
Reference in New Issue
Block a user