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

Commit b5118ac

Browse files
committed
fix($parse): always re-evaluate filters within literals when an input is an object
Fixes #15964 Closes #15990
1 parent 33cd29b commit b5118ac

File tree

3 files changed

+355
-28
lines changed

3 files changed

+355
-28
lines changed

src/ng/parse.js

+59-26
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/ng/directive/ngClassSpec.js

+20
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,26 @@ describe('ngClass', function() {
567567
})
568568
);
569569

570+
//https://github.com/angular/angular.js/issues/15960#issuecomment-299109412
571+
it('should always reevaluate filters with non-primitive inputs within literals', function() {
572+
module(function($filterProvider) {
573+
$filterProvider.register('foo', valueFn(function(o) {
574+
return o.a || o.b;
575+
}));
576+
});
577+
578+
inject(function($rootScope, $compile) {
579+
$rootScope.testObj = {};
580+
element = $compile('<div ng-class="{x: (testObj | foo)}">')($rootScope);
581+
582+
$rootScope.$apply();
583+
expect(element).not.toHaveClass('x');
584+
585+
$rootScope.$apply('testObj.a = true');
586+
expect(element).toHaveClass('x');
587+
});
588+
});
589+
570590
describe('large objects', function() {
571591
var getProp;
572592
var veryLargeObj;

0 commit comments

Comments
 (0)