Skip to content

Commit 81f3f32

Browse files
refactor(DirectiveParser): remove checks for missing directives
Based on the discussion in angular#776 we can't reliably check if a given element has a particular property at the compilation time. As such the existing algorithm detecting "missing" directives can't be used. We need to see if there is a different / better algorithm or maybe those checks need to be moved later in the process (runtime). Leaving integration tests in place (disabled) so we can come back to the topic after unblocking the situation. This commit effectivelly reverts 94e203b
1 parent b35f288 commit 81f3f32

File tree

3 files changed

+62
-122
lines changed

3 files changed

+62
-122
lines changed
Lines changed: 4 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {isPresent, isBlank, BaseException, assertionsEnabled, RegExpWrapper} from 'angular2/src/facade/lang';
2-
import {List, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
2+
import {List, MapWrapper} from 'angular2/src/facade/collection';
33
import {DOM} from 'angular2/src/dom/dom_adapter';
44
import {SelectorMatcher} from '../selector';
55
import {CssSelector} from '../selector';
@@ -10,9 +10,6 @@ import {CompileStep} from './compile_step';
1010
import {CompileElement} from './compile_element';
1111
import {CompileControl} from './compile_control';
1212

13-
import {isSpecialProperty} from './element_binder_builder';
14-
import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
15-
1613
var PROPERTY_BINDING_REGEXP = RegExpWrapper.create('^ *([^\\s\\|]+)');
1714

1815
/**
@@ -60,53 +57,15 @@ export class DirectiveParser extends CompileStep {
6057
});
6158

6259
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
63-
// only be present on <template> elements any more!
60+
// only be present on <template> elements!
6461
var isTemplateElement = DOM.isTemplateElement(current.element);
65-
var matchedProperties; // StringMap - used in dev mode to store all properties that have been matched
6662

6763
this._selectorMatcher.match(cssSelector, (selector, directive) => {
68-
matchedProperties = updateMatchedProperties(matchedProperties, selector, directive);
69-
checkDirectiveValidity(directive, current, isTemplateElement);
70-
current.addDirective(directive);
64+
current.addDirective(checkDirectiveValidity(directive, current, isTemplateElement));
7165
});
72-
73-
// raise error if some directives are missing
74-
checkMissingDirectives(current, matchedProperties, isTemplateElement);
7566
}
7667
}
7768

78-
// calculate all the properties that are used or interpreted by all directives
79-
// those properties correspond to the directive selectors and the directive bindings
80-
function updateMatchedProperties(matchedProperties, selector, directive) {
81-
if (assertionsEnabled()) {
82-
var attrs = selector.attrs;
83-
if (!isPresent(matchedProperties)) {
84-
matchedProperties = StringMapWrapper.create();
85-
}
86-
if (isPresent(attrs)) {
87-
for (var idx = 0; idx<attrs.length; idx+=2) {
88-
// attribute name is stored on even indexes
89-
StringMapWrapper.set(matchedProperties, dashCaseToCamelCase(attrs[idx]), true);
90-
}
91-
}
92-
// some properties can be used by the directive, so we need to register them
93-
if (isPresent(directive.annotation) && isPresent(directive.annotation.bind)) {
94-
var bindMap = directive.annotation.bind;
95-
StringMapWrapper.forEach(bindMap, (value, key) => {
96-
// value is the name of the property that is interpreted
97-
// e.g. 'myprop' or 'myprop | double' when a pipe is used to transform the property
98-
99-
// keep the property name and remove the pipe
100-
var bindProp = RegExpWrapper.firstMatch(PROPERTY_BINDING_REGEXP, value);
101-
if (isPresent(bindProp) && isPresent(bindProp[1])) {
102-
StringMapWrapper.set(matchedProperties, dashCaseToCamelCase(bindProp[1]), true);
103-
}
104-
});
105-
}
106-
}
107-
return matchedProperties;
108-
}
109-
11069
// check if the directive is compatible with the current element
11170
function checkDirectiveValidity(directive, current, isTemplateElement) {
11271
var isComponent = directive.annotation instanceof Component || directive.annotation instanceof DynamicComponent;
@@ -125,26 +84,6 @@ function checkDirectiveValidity(directive, current, isTemplateElement) {
12584
} else if (isComponent && alreadyHasComponent) {
12685
throw new BaseException(`Multiple component directives not allowed on the same element - check ${current.elementDescription}`);
12786
}
128-
}
12987

130-
// validates that there is no missing directive - dev mode only
131-
function checkMissingDirectives(current, matchedProperties, isTemplateElement) {
132-
if (assertionsEnabled()) {
133-
var ppBindings=current.propertyBindings;
134-
if (isPresent(ppBindings)) {
135-
// check that each property corresponds to a real property or has been matched by a directive
136-
MapWrapper.forEach(ppBindings, (expression, prop) => {
137-
if (!DOM.hasProperty(current.element, prop) && !isSpecialProperty(prop)) {
138-
if (!isPresent(matchedProperties) || !isPresent(StringMapWrapper.get(matchedProperties, prop))) {
139-
throw new BaseException(`Missing directive to handle '${camelCaseToDashCase(prop)}' in ${current.elementDescription}`);
140-
}
141-
}
142-
});
143-
}
144-
// template only store directives as attribute when they are not bound to expressions
145-
// so we have to validate the expression case too (e.g. !if="condition")
146-
if (isTemplateElement && !current.isViewRoot && !isPresent(current.viewportDirective)) {
147-
throw new BaseException(`Missing directive to handle: ${current.elementDescription}`);
148-
}
149-
}
88+
return directive;
15089
}

modules/angular2/src/core/compiler/pipeline/element_binder_builder.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,16 @@ function styleSetterFactory(styleName:string, stylesuffix:string) {
9393
return setterFn;
9494
}
9595

96-
// tells if an attribute is handled by the ElementBinderBuilder step
97-
export function isSpecialProperty(propName:string) {
98-
return StringWrapper.startsWith(propName, ATTRIBUTE_PREFIX)
99-
|| StringWrapper.startsWith(propName, CLASS_PREFIX)
100-
|| StringWrapper.startsWith(propName, STYLE_PREFIX)
101-
|| StringMapWrapper.contains(DOM.attrToPropMap, propName);
96+
const ROLE_ATTR = 'role';
97+
function roleSetter(element, value) {
98+
if (isString(value)) {
99+
DOM.setAttribute(element, ROLE_ATTR, value);
100+
} else {
101+
DOM.removeAttribute(element, ROLE_ATTR);
102+
if (isPresent(value)) {
103+
throw new BaseException("Invalid role attribute, only string values are allowed, got '" + stringify(value) + "'");
104+
}
105+
}
102106
}
103107

104108
/**

modules/angular2/test/core/compiler/integration_spec.js

Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
AsyncTestCompleter,
33
beforeEach,
44
ddescribe,
5+
xdescribe,
56
describe,
67
el,
78
expect,
@@ -566,61 +567,57 @@ export function main() {
566567
}));
567568
});
568569

569-
if (assertionsEnabled()) {
570-
571-
function expectCompileError(inlineTpl, errMessage, done) {
572-
tplResolver.setTemplate(MyComp, new Template({inline: inlineTpl}));
573-
PromiseWrapper.then(compiler.compile(MyComp),
574-
(value) => {
575-
throw new BaseException("Test failure: should not have come here as an exception was expected");
576-
},
577-
(err) => {
578-
expect(err.message).toEqual(errMessage);
579-
done();
580-
}
581-
);
582-
}
583-
584-
it('should raise an error if no directive is registered for an unsupported DOM property', inject([AsyncTestCompleter], (async) => {
585-
expectCompileError(
586-
'<div [some-prop]="foo"></div>',
587-
'Missing directive to handle \'some-prop\' in MyComp: <div [some-prop]="foo">',
588-
() => async.done()
589-
);
590-
}));
570+
xdescribe('Missing directive checks', () => {
571+
572+
if (assertionsEnabled()) {
573+
574+
function expectCompileError(inlineTpl, errMessage, done) {
575+
tplResolver.setTemplate(MyComp, new Template({inline: inlineTpl}));
576+
PromiseWrapper.then(compiler.compile(MyComp),
577+
(value) => {
578+
throw new BaseException("Test failure: should not have come here as an exception was expected");
579+
},
580+
(err) => {
581+
expect(err.message).toEqual(errMessage);
582+
done();
583+
}
584+
);
585+
}
586+
587+
it('should raise an error if no directive is registered for a template with template bindings', inject([AsyncTestCompleter], (async) => {
588+
expectCompileError(
589+
'<div><div template="if: foo"></div></div>',
590+
'Missing directive to handle \'if\' in <div template="if: foo">',
591+
() => async.done()
592+
);
593+
}));
591594

592-
it('should raise an error if no directive is registered for a template with template bindings', inject([AsyncTestCompleter], (async) => {
593-
expectCompileError(
594-
'<div><div template="if: foo"></div></div>',
595-
'Missing directive to handle \'if\' in <div template="if: foo">',
596-
() => async.done()
597-
);
598-
}));
595+
it('should raise an error for missing template directive (1)', inject([AsyncTestCompleter], (async) => {
596+
expectCompileError(
597+
'<div><template foo></template></div>',
598+
'Missing directive to handle: <template foo>',
599+
() => async.done()
600+
);
601+
}));
599602

600-
it('should raise an error for missing template directive (1)', inject([AsyncTestCompleter], (async) => {
601-
expectCompileError(
602-
'<div><template foo></template></div>',
603-
'Missing directive to handle: <template foo>',
604-
() => async.done()
605-
);
606-
}));
603+
it('should raise an error for missing template directive (2)', inject([AsyncTestCompleter], (async) => {
604+
expectCompileError(
605+
'<div><template *if="condition"></template></div>',
606+
'Missing directive to handle: <template *if="condition">',
607+
() => async.done()
608+
);
609+
}));
607610

608-
it('should raise an error for missing template directive (2)', inject([AsyncTestCompleter], (async) => {
609-
expectCompileError(
610-
'<div><template *if="condition"></template></div>',
611-
'Missing directive to handle: <template *if="condition">',
612-
() => async.done()
613-
);
614-
}));
611+
it('should raise an error for missing template directive (3)', inject([AsyncTestCompleter], (async) => {
612+
expectCompileError(
613+
'<div *if="condition"></div>',
614+
'Missing directive to handle \'if\' in MyComp: <div *if="condition">',
615+
() => async.done()
616+
);
617+
}));
618+
}
619+
});
615620

616-
it('should raise an error for missing template directive (3)', inject([AsyncTestCompleter], (async) => {
617-
expectCompileError(
618-
'<div *if="condition"></div>',
619-
'Missing directive to handle \'if\' in MyComp: <div *if="condition">',
620-
() => async.done()
621-
);
622-
}));
623-
}
624621
});
625622
}
626623

0 commit comments

Comments
 (0)