Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 93a4d75

Browse files
committed
refactor($compile): detect and log bad directive 'restrict' values on directive factory init
1 parent 0ecf109 commit 93a4d75

File tree

7 files changed

+337
-1
lines changed

7 files changed

+337
-1
lines changed

benchmarks/js-bp/app.js

+256
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
var app = angular.module('jsBenchmark', []);
2+
3+
function runner(func, n) {
4+
n = n || 5000;
5+
return function theRunner() {
6+
for (var i=0; i<n; i++) {
7+
func();
8+
}
9+
};
10+
}
11+
12+
/* copy */
13+
app.controller('DataController', ['$compile', function($compile) {
14+
var COPY_BASIC = {
15+
i: 123,
16+
str: "foo",
17+
date: new Date(),
18+
sub: {event: "foo"}
19+
};
20+
var COPY_ARRAY_BASIC = [123, "foo", new Date(), {event: "foo"}];
21+
var COPY_ARRAY_SIMPLE = [123, "foo", new Date(), /foo/];
22+
23+
var BIG_ARRAY_OBJECTS = new Array(50).join(" ").split(" ").map(function(s, i) {
24+
return {
25+
i: i,
26+
s: s + i,
27+
o: {foo: "bar", date: new Date()},
28+
a: [/foo/, new Int32Array()]
29+
};
30+
});
31+
32+
var BIG_COMPLICATED_ARRAY_OBJECTS = new Array(50).join(" ").split(" ").reduce(function(a, s, i) {
33+
var o = {};
34+
35+
o.i = i;
36+
o.s = s + i;
37+
o.d = (i % 5 === 1) ? a[i-1].d : new Date();
38+
o.re = (i % 5 === 2) ? a[i-2].re : /foobar/i;
39+
o.a = (i % 5 === 3) ? a[i-3].a : ['foo', 123];
40+
41+
if (i % 5 === 0) {
42+
o.o = o;
43+
} else {
44+
o.o = {foo: "bar"};
45+
}
46+
if (i % 4 === 1) {
47+
o.p = a[i-1];
48+
}
49+
50+
a.push(o);
51+
if (i % 20 === 0) {
52+
a.push(o);
53+
}
54+
55+
return a;
56+
}, []);
57+
58+
function copy(what) {
59+
for (var i=0; i<1000; i++) {
60+
angular.copy(what);
61+
}
62+
}
63+
64+
benchmarkSteps.push({
65+
name: 'large array of objects',
66+
fn: copy.bind(null, BIG_ARRAY_OBJECTS)
67+
});
68+
benchmarkSteps.push({
69+
name: 'large array of over complicated objects',
70+
fn: copy.bind(null, BIG_COMPLICATED_ARRAY_OBJECTS)
71+
});
72+
benchmarkSteps.push({
73+
name: 'basic + date + 1 recurse',
74+
fn: copy.bind(null, COPY_BASIC)
75+
});
76+
benchmarkSteps.push({
77+
name: 'basic array + date + 1 recurse',
78+
fn: copy.bind(null, COPY_ARRAY_BASIC)
79+
});
80+
benchmarkSteps.push({
81+
name: 'simple',
82+
fn: copy.bind(null, COPY_ARRAY_SIMPLE)
83+
});
84+
}]);
85+
// */
86+
87+
/* Q * /
88+
app.controller('DataController', ['$q', function($q) {
89+
var P = $q.resolve(42);
90+
var rP = function() { return P; };
91+
92+
benchmarkSteps.push({
93+
name: 'resolve(1)',
94+
fn: runner(function() {
95+
return $q.resolve(1);
96+
})
97+
});
98+
benchmarkSteps.push({
99+
name: 'reject(1)',
100+
fn: runner(function() {
101+
return $q.reject(1);
102+
})
103+
});
104+
benchmarkSteps.push({
105+
name: 'defer.resolve(1)',
106+
fn: runner(function() {
107+
return $q.defer().resolve(1);
108+
})
109+
});
110+
benchmarkSteps.push({
111+
name: 'defer.reject(1)',
112+
fn: runner(function() {
113+
return $q.defer().reject(1); //3206 => 3285 (because notify gets wrapped twice now??)
114+
})
115+
});
116+
117+
benchmarkSteps.push({
118+
name: 'resolve(P)',
119+
fn: runner(function() {
120+
return $q.resolve(P); //way down, but still high retained memory
121+
})
122+
});
123+
benchmarkSteps.push({
124+
name: 'defer.resolve(P)',
125+
fn: runner(function() {
126+
return $q.defer().resolve(P); //way down, but still high retained memory
127+
})
128+
});
129+
130+
benchmarkSteps.push({
131+
name: '.then()',
132+
fn: runner(function() {
133+
return P.then();
134+
})
135+
});
136+
benchmarkSteps.push({
137+
name: '.then(rP)',
138+
fn: runner(function() {
139+
return P.then(rP);
140+
})
141+
});
142+
143+
benchmarkSteps.push({
144+
name: 'Q(noop)',
145+
fn: runner(function() {
146+
return $q(angular.noop);
147+
})
148+
});
149+
}]);
150+
// */
151+
152+
/* $compile * /
153+
app.controller('DataController', ['$compile', '$rootScope', function($compile, $rootScope) {
154+
var HTML = document.querySelector(".container-fluid").innerHTML;
155+
156+
157+
var i=0;
158+
HTML = HTML.replace(/(<[a-z]+)/g, function(a, r) {
159+
if (r % 5) {
160+
r += ' ng-if="B"';
161+
} else if (r % 11) {
162+
r += ' ng-repeat="b in A"';
163+
} else if (r % 2) {
164+
r += ' ng-model="b"'
165+
}
166+
167+
i++;
168+
return r;
169+
});
170+
171+
HTML = HTML.replace(/></g, " ng-bind='C'><");
172+
173+
174+
var step;
175+
benchmarkSteps.push({
176+
name: '$compile',
177+
fn: function() {
178+
step = $compile(HTML);
179+
}
180+
});
181+
benchmarkSteps.push({
182+
name: 'link',
183+
fn: function() {
184+
step = step(angular.extend(scope = $rootScope.$new(), {
185+
A: [1,2,3,4,5],
186+
C: 123
187+
}));
188+
}
189+
});
190+
benchmarkSteps.push({
191+
name: 'remove',
192+
fn: function() {
193+
step.remove();
194+
step = null;
195+
196+
scope.$destroy();
197+
scope = null;
198+
}
199+
});
200+
}]);
201+
// */
202+
203+
204+
/* compiler * /
205+
app.directive('templateUrl', ['$templateCache', function($templateCache) {
206+
$templateCache.put('template', '<span>');
207+
208+
var VAL = 0;
209+
210+
return {
211+
templateUrl: 'template',
212+
compile: function($el, $at) {
213+
$el.text("{{val}}");
214+
return function($scope, $el) {
215+
$scope.val = VAL++;
216+
};
217+
}
218+
};
219+
}]);
220+
221+
app.controller('DataController', ['$compile', '$rootScope', function($compile, $rootScope) {
222+
var HTML = '<aside><div ng-repeat="i in a track by $index"><template-url/></div></aside>';
223+
224+
var step, scope;
225+
226+
benchmarkSteps.push({
227+
name: '$compile',
228+
fn: function() {
229+
step = angular.element(HTML);
230+
angular.element(document.body).append(step);
231+
step = $compile(step);
232+
}
233+
});
234+
235+
benchmarkSteps.push({
236+
name: 'link',
237+
fn: function() {
238+
scope = $rootScope.$new();
239+
scope.a = new Array(5000).join(" ").split(" ");
240+
step = step(scope);
241+
$rootScope.$apply();
242+
}
243+
});
244+
245+
benchmarkSteps.push({
246+
name: '$destroy',
247+
fn: function() {
248+
scope.$destroy();
249+
step.remove();
250+
scope = step = null;
251+
}
252+
});
253+
}]);
254+
// */
255+
256+
angular.bootstrap(document.querySelector("[ng-app-foo]"), ["jsBenchmark"]);

benchmarks/js-bp/bp.conf.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module.exports = function(config) {
2+
config.set({
3+
scripts: [{
4+
id: 'jquery',
5+
src: 'jquery-noop.js'
6+
},
7+
{
8+
id: 'angular',
9+
src: '/build/angular.js'
10+
},
11+
{
12+
src: 'app.js',
13+
}]
14+
});
15+
};

benchmarks/js-bp/jquery-noop.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//Override me with ?jquery=/bower_components/jquery/dist/jquery.js

benchmarks/js-bp/main.html

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div ng-app-foo>
2+
<div ng-controller="DataController as ctrl"></div>
3+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@ngdoc error
2+
@name $compile:badrestrict
3+
@fullName Invalid Directive Restrict
4+
@description
5+
6+
This error occurs when the restrict property of a directive is not valid.
7+
8+
The directive restrict property must be a string with possible character values being:
9+
* E (element)
10+
* A (attribute)
11+
* C (class)
12+
* M (comment)

src/ng/compile.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
969969
return require;
970970
}
971971

972+
function getDirectiveRestrict(directive, name) {
973+
if (directive.restrict != null && !(isString(directive.restrict) && /[EACM]/.test(directive.restrict))) {
974+
throw $compileMinErr('badrestrict',
975+
"Restrict property '{0}' of directive '{1}' is invalid",
976+
directive.restrict,
977+
name);
978+
}
979+
980+
return directive.restrict || 'EA';
981+
}
982+
972983
/**
973984
* @ngdoc method
974985
* @name $compileProvider#directive
@@ -989,6 +1000,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
9891000
if (isString(name)) {
9901001
assertValidDirectiveName(name);
9911002
assertArg(directiveFactory, 'directiveFactory');
1003+
9921004
if (!hasDirectives.hasOwnProperty(name)) {
9931005
hasDirectives[name] = [];
9941006
$provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
@@ -1006,7 +1018,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
10061018
directive.index = index;
10071019
directive.name = directive.name || name;
10081020
directive.require = getDirectiveRequire(directive);
1009-
directive.restrict = directive.restrict || 'EA';
1021+
directive.restrict = getDirectiveRestrict(directive, name);
10101022
directive.$$moduleName = directiveFactory.$$moduleName;
10111023
directives.push(directive);
10121024
} catch (e) {

test/ng/compileSpec.js

+37
Original file line numberDiff line numberDiff line change
@@ -5558,6 +5558,43 @@ describe('$compile', function() {
55585558
});
55595559

55605560

5561+
it('should throw badrestrict on first compilation when restrict is invalid', function() {
5562+
module(function($compileProvider, $exceptionHandlerProvider) {
5563+
$compileProvider.directive('invalidRestrictBadString', valueFn({restrict: '"'}));
5564+
$compileProvider.directive('invalidRestrictFalse', valueFn({restrict: false}));
5565+
$compileProvider.directive('invalidRestrictTrue', valueFn({restrict: true}));
5566+
$compileProvider.directive('invalidRestrictObject', valueFn({restrict: {}}));
5567+
$compileProvider.directive('invalidRestrictZero', valueFn({restrict: 0}));
5568+
5569+
// We need to test with the exceptionHandler not rethrowing...
5570+
$exceptionHandlerProvider.mode('log');
5571+
});
5572+
5573+
inject(function($exceptionHandler, $compile, $rootScope) {
5574+
$compile('<div invalid-restrict-false>')($rootScope);
5575+
expect($exceptionHandler.errors.length).toBe(1);
5576+
expect($exceptionHandler.errors[0]).toMatch(/\$compile.*badrestrict.*'false'/);
5577+
5578+
$compile('<div invalid-restrict-true>')($rootScope);
5579+
expect($exceptionHandler.errors.length).toBe(2);
5580+
expect($exceptionHandler.errors[1]).toMatch(/\$compile.*badrestrict.*'true'/);
5581+
5582+
$compile('<div invalid-restrict-bad-string>')($rootScope);
5583+
$compile('<div invalid-restrict-bad-string>')($rootScope);
5584+
expect($exceptionHandler.errors.length).toBe(3);
5585+
expect($exceptionHandler.errors[2]).toMatch(/\$compile.*badrestrict.*'\"'/);
5586+
5587+
$compile('<div invalid-restrict-bad-string invalid-restrict-object>')($rootScope);
5588+
expect($exceptionHandler.errors.length).toBe(4);
5589+
expect($exceptionHandler.errors[3]).toMatch(/\$compile.*badrestrict.*'{}'/);
5590+
5591+
$compile('<div invalid-restrict-object invalid-restrict-zero>')($rootScope);
5592+
expect($exceptionHandler.errors.length).toBe(5);
5593+
expect($exceptionHandler.errors[4]).toMatch(/\$compile.*badrestrict.*'0'/);
5594+
});
5595+
});
5596+
5597+
55615598
it('should throw noident when missing controllerAs directive property', function() {
55625599
module(function($compileProvider) {
55635600
$compileProvider.directive('noIdent', valueFn({

0 commit comments

Comments
 (0)