Skip to content

Commit 29aa815

Browse files
Merge pull request #31 from angular/master
Update upstream
2 parents 4001c5b + b4651e5 commit 29aa815

File tree

7 files changed

+806
-370
lines changed

7 files changed

+806
-370
lines changed

src/ng/parse.js

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -622,15 +622,44 @@ function isStateless($filter, filterName) {
622622
return !fn.$stateful;
623623
}
624624

625-
function findConstantAndWatchExpressions(ast, $filter) {
625+
// Detect nodes which could depend on non-shallow state of objects
626+
function isPure(node, parentIsPure) {
627+
switch (node.type) {
628+
// Computed members might invoke a stateful toString()
629+
case AST.MemberExpression:
630+
if (node.computed) {
631+
return false;
632+
}
633+
break;
634+
635+
// Unary always convert to primative
636+
case AST.UnaryExpression:
637+
return true;
638+
639+
// The binary + operator can invoke a stateful toString().
640+
case AST.BinaryExpression:
641+
return node.operator !== '+';
642+
643+
// Functions / filters probably read state from within objects
644+
case AST.CallExpression:
645+
return false;
646+
}
647+
648+
return (undefined === parentIsPure) || parentIsPure;
649+
}
650+
651+
function findConstantAndWatchExpressions(ast, $filter, parentIsPure) {
626652
var allConstants;
627653
var argsToWatch;
628654
var isStatelessFilter;
655+
656+
var astIsPure = ast.isPure = isPure(ast, parentIsPure);
657+
629658
switch (ast.type) {
630659
case AST.Program:
631660
allConstants = true;
632661
forEach(ast.body, function(expr) {
633-
findConstantAndWatchExpressions(expr.expression, $filter);
662+
findConstantAndWatchExpressions(expr.expression, $filter, astIsPure);
634663
allConstants = allConstants && expr.expression.constant;
635664
});
636665
ast.constant = allConstants;
@@ -640,26 +669,26 @@ function findConstantAndWatchExpressions(ast, $filter) {
640669
ast.toWatch = [];
641670
break;
642671
case AST.UnaryExpression:
643-
findConstantAndWatchExpressions(ast.argument, $filter);
672+
findConstantAndWatchExpressions(ast.argument, $filter, astIsPure);
644673
ast.constant = ast.argument.constant;
645674
ast.toWatch = ast.argument.toWatch;
646675
break;
647676
case AST.BinaryExpression:
648-
findConstantAndWatchExpressions(ast.left, $filter);
649-
findConstantAndWatchExpressions(ast.right, $filter);
677+
findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
678+
findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
650679
ast.constant = ast.left.constant && ast.right.constant;
651680
ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
652681
break;
653682
case AST.LogicalExpression:
654-
findConstantAndWatchExpressions(ast.left, $filter);
655-
findConstantAndWatchExpressions(ast.right, $filter);
683+
findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
684+
findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
656685
ast.constant = ast.left.constant && ast.right.constant;
657686
ast.toWatch = ast.constant ? [] : [ast];
658687
break;
659688
case AST.ConditionalExpression:
660-
findConstantAndWatchExpressions(ast.test, $filter);
661-
findConstantAndWatchExpressions(ast.alternate, $filter);
662-
findConstantAndWatchExpressions(ast.consequent, $filter);
689+
findConstantAndWatchExpressions(ast.test, $filter, astIsPure);
690+
findConstantAndWatchExpressions(ast.alternate, $filter, astIsPure);
691+
findConstantAndWatchExpressions(ast.consequent, $filter, astIsPure);
663692
ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
664693
ast.toWatch = ast.constant ? [] : [ast];
665694
break;
@@ -668,9 +697,9 @@ function findConstantAndWatchExpressions(ast, $filter) {
668697
ast.toWatch = [ast];
669698
break;
670699
case AST.MemberExpression:
671-
findConstantAndWatchExpressions(ast.object, $filter);
700+
findConstantAndWatchExpressions(ast.object, $filter, astIsPure);
672701
if (ast.computed) {
673-
findConstantAndWatchExpressions(ast.property, $filter);
702+
findConstantAndWatchExpressions(ast.property, $filter, astIsPure);
674703
}
675704
ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
676705
ast.toWatch = [ast];
@@ -680,7 +709,7 @@ function findConstantAndWatchExpressions(ast, $filter) {
680709
allConstants = isStatelessFilter;
681710
argsToWatch = [];
682711
forEach(ast.arguments, function(expr) {
683-
findConstantAndWatchExpressions(expr, $filter);
712+
findConstantAndWatchExpressions(expr, $filter, astIsPure);
684713
allConstants = allConstants && expr.constant;
685714
if (!expr.constant) {
686715
argsToWatch.push.apply(argsToWatch, expr.toWatch);
@@ -690,16 +719,16 @@ function findConstantAndWatchExpressions(ast, $filter) {
690719
ast.toWatch = isStatelessFilter ? argsToWatch : [ast];
691720
break;
692721
case AST.AssignmentExpression:
693-
findConstantAndWatchExpressions(ast.left, $filter);
694-
findConstantAndWatchExpressions(ast.right, $filter);
722+
findConstantAndWatchExpressions(ast.left, $filter, astIsPure);
723+
findConstantAndWatchExpressions(ast.right, $filter, astIsPure);
695724
ast.constant = ast.left.constant && ast.right.constant;
696725
ast.toWatch = [ast];
697726
break;
698727
case AST.ArrayExpression:
699728
allConstants = true;
700729
argsToWatch = [];
701730
forEach(ast.elements, function(expr) {
702-
findConstantAndWatchExpressions(expr, $filter);
731+
findConstantAndWatchExpressions(expr, $filter, astIsPure);
703732
allConstants = allConstants && expr.constant;
704733
if (!expr.constant) {
705734
argsToWatch.push.apply(argsToWatch, expr.toWatch);
@@ -712,13 +741,13 @@ function findConstantAndWatchExpressions(ast, $filter) {
712741
allConstants = true;
713742
argsToWatch = [];
714743
forEach(ast.properties, function(property) {
715-
findConstantAndWatchExpressions(property.value, $filter);
744+
findConstantAndWatchExpressions(property.value, $filter, astIsPure);
716745
allConstants = allConstants && property.value.constant && !property.computed;
717746
if (!property.value.constant) {
718747
argsToWatch.push.apply(argsToWatch, property.value.toWatch);
719748
}
720749
if (property.computed) {
721-
findConstantAndWatchExpressions(property.key, $filter);
750+
findConstantAndWatchExpressions(property.key, $filter, astIsPure);
722751
if (!property.key.constant) {
723752
argsToWatch.push.apply(argsToWatch, property.key.toWatch);
724753
}
@@ -803,7 +832,7 @@ ASTCompiler.prototype = {
803832
var intoId = self.nextId();
804833
self.recurse(watch, intoId);
805834
self.return_(intoId);
806-
self.state.inputs.push(fnKey);
835+
self.state.inputs.push({name: fnKey, isPure: watch.isPure});
807836
watch.watchId = key;
808837
});
809838
this.state.computing = 'fn';
@@ -839,13 +868,16 @@ ASTCompiler.prototype = {
839868

840869
watchFns: function() {
841870
var result = [];
842-
var fns = this.state.inputs;
871+
var inputs = this.state.inputs;
843872
var self = this;
844-
forEach(fns, function(name) {
845-
result.push('var ' + name + '=' + self.generateFunction(name, 's'));
873+
forEach(inputs, function(input) {
874+
result.push('var ' + input.name + '=' + self.generateFunction(input.name, 's'));
875+
if (input.isPure) {
876+
result.push(input.name, '.isPure=true;');
877+
}
846878
});
847-
if (fns.length) {
848-
result.push('fn.inputs=[' + fns.join(',') + '];');
879+
if (inputs.length) {
880+
result.push('fn.inputs=[' + inputs.map(function(i) { return i.name; }).join(',') + '];');
849881
}
850882
return result.join('');
851883
},
@@ -1251,6 +1283,7 @@ ASTInterpreter.prototype = {
12511283
inputs = [];
12521284
forEach(toWatch, function(watch, key) {
12531285
var input = self.recurse(watch);
1286+
input.isPure = watch.isPure;
12541287
watch.input = input;
12551288
inputs.push(input);
12561289
watch.watchId = key;
@@ -1817,7 +1850,7 @@ function $ParseProvider() {
18171850
inputExpressions = inputExpressions[0];
18181851
return scope.$watch(function expressionInputWatch(scope) {
18191852
var newInputValue = inputExpressions(scope);
1820-
if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, parsedExpression.literal)) {
1853+
if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf, inputExpressions.isPure)) {
18211854
lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
18221855
oldInputValueOf = newInputValue && getValueOf(newInputValue);
18231856
}
@@ -1837,7 +1870,7 @@ function $ParseProvider() {
18371870

18381871
for (var i = 0, ii = inputExpressions.length; i < ii; i++) {
18391872
var newInputValue = inputExpressions[i](scope);
1840-
if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], parsedExpression.literal))) {
1873+
if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i], inputExpressions[i].isPure))) {
18411874
oldInputValues[i] = newInputValue;
18421875
oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue);
18431876
}

test/auto/injectorSpec.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -283,32 +283,32 @@ describe('injector', function() {
283283

284284

285285
describe('es6', function() {
286-
if (support.ES6Function) {
286+
if (support.shorthandMethods) {
287287
// The functions are generated using `eval` as just having the ES6 syntax can break some browsers.
288-
it('should be possible to annotate functions that are declared using ES6 syntax', function() {
288+
it('should be possible to annotate shorthand methods', function() {
289289
// eslint-disable-next-line no-eval
290290
expect(annotate(eval('({ fn(x) { return; } })').fn)).toEqual(['x']);
291291
});
292292
}
293293

294294

295-
if (support.fatArrow) {
295+
if (support.fatArrows) {
296296
it('should create $inject for arrow functions', function() {
297297
// eslint-disable-next-line no-eval
298298
expect(annotate(eval('(a, b) => a'))).toEqual(['a', 'b']);
299299
});
300300
}
301301

302302

303-
if (support.fatArrow) {
303+
if (support.fatArrows) {
304304
it('should create $inject for arrow functions with no parenthesis', function() {
305305
// eslint-disable-next-line no-eval
306306
expect(annotate(eval('a => a'))).toEqual(['a']);
307307
});
308308
}
309309

310310

311-
if (support.fatArrow) {
311+
if (support.fatArrows) {
312312
it('should take args before first arrow', function() {
313313
// eslint-disable-next-line no-eval
314314
expect(annotate(eval('a => b => b'))).toEqual(['a']);

test/helpers/support.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
var supportTests = {
4+
classes: '/^class\\b/.test((class C {}).toString())',
5+
fatArrows: 'a => a',
6+
shorthandMethods: '({ fn(x) { return; } })'
7+
};
8+
9+
var support = {};
10+
11+
for (var prop in supportTests) {
12+
if (supportTests.hasOwnProperty(prop)) {
13+
try {
14+
// eslint-disable-next-line no-eval
15+
support[prop] = !!eval(supportTests[prop]);
16+
} catch (e) {
17+
support[prop] = false;
18+
}
19+
}
20+
}

test/helpers/supportSpec.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
'use strict';
2+
3+
describe('support test results', function() {
4+
var expected, version, testName;
5+
var userAgent = window.navigator.userAgent;
6+
7+
// Support: iOS 8 only
8+
if (/iPhone OS 10_1\d(?:_\d+)? /.test(userAgent)) {
9+
// iOS 8 official simulators have broken user agent (containing something like `iPhone OS 10_12_5`,
10+
// i.e. the macOS version in place of the iOS version) so they'd fall into an incorrect bucket.
11+
// Fix the user agent there.
12+
// NOTE: Make sure the above check doesn't catch the real iOS 10!
13+
userAgent = userAgent.replace(/iPhone OS 10(?:_\d+)+/, 'iPhone OS 8_1');
14+
}
15+
16+
if (/edge\//i.test(userAgent)) {
17+
expected = {
18+
classes: true,
19+
fatArrows: true,
20+
shorthandMethods: true
21+
};
22+
} else if (/msie|trident/i.test(userAgent)) {
23+
expected = {
24+
classes: false,
25+
fatArrows: false,
26+
shorthandMethods: false
27+
};
28+
} else if (/iphone os [78]_/i.test(userAgent)) {
29+
expected = {
30+
classes: false,
31+
fatArrows: false,
32+
shorthandMethods: false
33+
};
34+
} else if (/iphone os 9_/i.test(userAgent)) {
35+
expected = {
36+
classes: true,
37+
fatArrows: false,
38+
shorthandMethods: true
39+
};
40+
} else if (/iphone os/i.test(userAgent)) {
41+
expected = {
42+
classes: true,
43+
fatArrows: true,
44+
shorthandMethods: true
45+
};
46+
} else if (/android 4\.[0-3]/i.test(userAgent)) {
47+
expected = {
48+
classes: false,
49+
fatArrows: false,
50+
shorthandMethods: false
51+
};
52+
} else if (/chrome/i.test(userAgent)) {
53+
// Catches Chrome on Android as well (i.e. the default
54+
// Android browser on Android >= 4.4).
55+
expected = {
56+
classes: true,
57+
fatArrows: true,
58+
shorthandMethods: true
59+
};
60+
} else if (/firefox/i.test(userAgent)) {
61+
version = parseInt(userAgent.match(/firefox\/(\d+)/i)[1], 10);
62+
expected = {
63+
classes: version >= 55,
64+
fatArrows: true,
65+
shorthandMethods: true
66+
};
67+
} else if (/\b8(?:\.\d+)+ safari/i.test(userAgent)) {
68+
expected = {
69+
classes: false,
70+
fatArrows: false,
71+
shorthandMethods: false
72+
};
73+
} else if (/\b9(?:\.\d+)+ safari/i.test(userAgent)) {
74+
expected = {
75+
classes: true,
76+
fatArrows: false,
77+
shorthandMethods: true
78+
};
79+
} else if (/\b\d+(?:\.\d+)+ safari/i.test(userAgent)) {
80+
expected = {
81+
classes: true,
82+
fatArrows: true,
83+
shorthandMethods: true
84+
};
85+
}
86+
87+
it('should have expected values specified', function() {
88+
expect(expected).not.toBe(null);
89+
expect(typeof expected).toBe('object');
90+
});
91+
92+
for (testName in expected) {
93+
it('should report support.' + testName + ' to be ' + expected[testName], function() {
94+
expect(support[testName]).toBe(expected[testName]);
95+
});
96+
}
97+
});

test/helpers/testabilityPatch.js

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,6 @@
33

44
if (window.bindJQuery) bindJQuery();
55

6-
var supportTests = {
7-
classes: '/^class\\b/.test((class C {}).toString())',
8-
fatArrow: 'a => a',
9-
ES6Function: '({ fn(x) { return; } })'
10-
};
11-
12-
var support = {};
13-
14-
for (var prop in supportTests) {
15-
if (supportTests.hasOwnProperty(prop)) {
16-
try {
17-
// eslint-disable-next-line no-eval
18-
support[prop] = !!eval(supportTests[prop]);
19-
} catch (e) {
20-
support[prop] = false;
21-
}
22-
}
23-
}
24-
25-
266
beforeEach(function() {
277

288
// all this stuff is not needed for module tests, where jqlite and publishExternalAPI and jqLite are not global vars

0 commit comments

Comments
 (0)