Skip to content

Commit 4f75c0d

Browse files
committed
refactor($parse): use the visitor pattern for determining inputs/constant
1 parent 144d899 commit 4f75c0d

File tree

1 file changed

+101
-132
lines changed

1 file changed

+101
-132
lines changed

Diff for: src/ng/parse.js

+101-132
Original file line numberDiff line numberDiff line change
@@ -597,111 +597,103 @@ function plusFn(l, r) {
597597
return l + r;
598598
}
599599

600-
function isStateless($filter, filterName) {
601-
var fn = $filter(filterName);
602-
return !fn.$stateful;
603-
}
604-
605-
function findConstantAndWatchExpressions(ast, $filter) {
606-
var allConstants;
607-
var argsToWatch;
608-
switch (ast.type) {
609-
case AST.Literal:
610-
ast.constant = true;
611-
ast.toWatch = [];
612-
break;
613-
case AST.UnaryExpression:
614-
findConstantAndWatchExpressions(ast.argument, $filter);
615-
ast.constant = ast.argument.constant;
616-
ast.toWatch = ast.argument.toWatch;
617-
break;
618-
case AST.BinaryExpression:
619-
findConstantAndWatchExpressions(ast.left, $filter);
620-
findConstantAndWatchExpressions(ast.right, $filter);
621-
ast.constant = ast.left.constant && ast.right.constant;
622-
ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch);
623-
break;
624-
case AST.LogicalExpression:
625-
findConstantAndWatchExpressions(ast.left, $filter);
626-
findConstantAndWatchExpressions(ast.right, $filter);
627-
ast.constant = ast.left.constant && ast.right.constant;
628-
ast.toWatch = ast.constant ? [] : [ast];
629-
break;
630-
case AST.ConditionalExpression:
631-
findConstantAndWatchExpressions(ast.test, $filter);
632-
findConstantAndWatchExpressions(ast.alternate, $filter);
633-
findConstantAndWatchExpressions(ast.consequent, $filter);
634-
ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant;
635-
ast.toWatch = ast.constant ? [] : [ast];
636-
break;
637-
case AST.Identifier:
638-
ast.constant = false;
639-
ast.toWatch = [ast];
640-
break;
641-
case AST.MemberExpression:
642-
findConstantAndWatchExpressions(ast.object, $filter);
643-
if (ast.computed) {
644-
findConstantAndWatchExpressions(ast.property, $filter);
600+
function traverse(ast, visitor) {
601+
if (false !== visitor(ast)) {
602+
switch (ast.type) {
603+
case AST.UnaryExpression:
604+
traverse(ast.argument, visitor);
605+
break;
606+
case AST.ExpressionStatement:
607+
traverse(ast.expression, visitor);
608+
break;
609+
case AST.BinaryExpression:
610+
case AST.LogicalExpression:
611+
case AST.AssignmentExpression:
612+
traverse(ast.left, visitor);
613+
traverse(ast.right, visitor);
614+
break;
615+
case AST.ConditionalExpression:
616+
traverse(ast.test, visitor);
617+
traverse(ast.alternate, visitor);
618+
traverse(ast.consequent, visitor);
619+
break;
620+
case AST.MemberExpression:
621+
traverse(ast.object, visitor);
622+
traverse(ast.property, visitor);
623+
break;
624+
case AST.CallExpression:
625+
if (!ast.filter) {
626+
traverse(ast.callee, visitor);
627+
}
628+
/* jshint -W086 */
629+
//fall through to the overly complicated child exp loop
630+
case AST.Program:
631+
case AST.ArrayExpression:
632+
case AST.ObjectExpression:
633+
for (var a = ast.arguments || ast.body || ast.elements || ast.properties, i = 0, ii = a.length; i < ii; i++) {
634+
traverse(a[i].value || a[i].expression || a[i], visitor);
635+
}
636+
break;
645637
}
646-
ast.constant = ast.object.constant && (!ast.computed || ast.property.constant);
647-
ast.toWatch = [ast];
648-
break;
649-
case AST.CallExpression:
650-
allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false;
651-
argsToWatch = [];
652-
forEach(ast.arguments, function(expr) {
653-
findConstantAndWatchExpressions(expr, $filter);
654-
allConstants = allConstants && expr.constant;
655-
argsToWatch.push.apply(argsToWatch, expr.toWatch);
656-
});
657-
ast.constant = allConstants;
658-
ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast];
659-
break;
660-
case AST.AssignmentExpression:
661-
findConstantAndWatchExpressions(ast.left, $filter);
662-
findConstantAndWatchExpressions(ast.right, $filter);
663-
ast.constant = ast.left.constant && ast.right.constant;
664-
ast.toWatch = ast.right.toWatch;
665-
break;
666-
case AST.ArrayExpression:
667-
allConstants = true;
668-
argsToWatch = [];
669-
forEach(ast.elements, function(expr) {
670-
findConstantAndWatchExpressions(expr, $filter);
671-
allConstants = allConstants && expr.constant;
672-
argsToWatch.push.apply(argsToWatch, expr.toWatch);
673-
});
674-
ast.constant = allConstants;
675-
ast.toWatch = argsToWatch;
676-
break;
677-
case AST.ObjectExpression:
678-
allConstants = true;
679-
argsToWatch = [];
680-
forEach(ast.properties, function(property) {
681-
findConstantAndWatchExpressions(property.value, $filter);
682-
allConstants = allConstants && property.value.constant;
683-
argsToWatch.push.apply(argsToWatch, property.value.toWatch);
684-
});
685-
ast.constant = allConstants;
686-
ast.toWatch = argsToWatch;
687-
break;
688-
case AST.ThisExpression:
689-
ast.constant = false;
690-
ast.toWatch = [];
691-
break;
692638
}
693639
}
694640

695-
function useInputs(body) {
696-
if (!body.length) return false;
697-
var lastExpression = body[body.length - 1];
698-
if (lastExpression.expression.toWatch.length !== 1) return true;
699-
return lastExpression.expression.toWatch[0] !== lastExpression.expression;
641+
function astAny(ast, check) {
642+
var res = false;
643+
traverse(ast, function(e) {
644+
res = res || check(e);
645+
return !res;
646+
});
647+
return res;
648+
}
649+
function isConstant(ast, $filter) {
650+
return !astAny(ast, function(e) { return isMutator(e) || isStateful(e, $filter); });
651+
}
652+
function hasMutator(ast) {
653+
return astAny(ast, isMutator);
654+
}
655+
function findInputs(ast, $filter) {
656+
//Recurse into the body if htere is only one for the check in the return condition
657+
if (ast.body && ast.body.length === 1) {
658+
ast = ast.body[0].expression;
659+
}
660+
661+
var inputs = [];
662+
traverse(ast, function(e) {
663+
if (isStateful(e, $filter) || isBranching(e) && hasMutator(e)) {
664+
if (!isConstant(e, $filter)) {
665+
inputs.push(e);
666+
}
667+
return false;
668+
}
669+
});
670+
671+
//Only return non-empty and non-equevelent inputs
672+
return inputs.length && !(inputs.length === 1 && inputs[0] === ast) ? inputs : false;
700673
}
701674

702675
function isAssignable(ast) {
703676
return ast.type === AST.Identifier || ast.type === AST.MemberExpression;
704677
}
678+
function isMutator(ast) {
679+
return ast.type === AST.AssignmentExpression ||
680+
ast.type === AST.CallExpression && !ast.filter;
681+
}
682+
function isBranching(ast) {
683+
return ast.type === AST.ConditionalExpression || ast.type === AST.LogicalExpression;
684+
}
685+
function isStateful(ast, $filter) {
686+
return ast.type === AST.Identifier ||
687+
ast.type === AST.MemberExpression ||
688+
ast.type === AST.CallExpression && (!ast.filter || $filter(ast.callee.name).$stateful);
689+
}
690+
function isLiteral(ast) {
691+
if (ast.body && ast.body.length === 1) {
692+
ast = ast.body[0].expression;
693+
}
694+
return ast.type === AST.Literal || ast.type === AST.ArrayExpression || ast.type === AST.ObjectExpression;
695+
}
696+
705697

706698
function ASTCompiler(astBuilder, $filter) {
707699
this.astBuilder = astBuilder;
@@ -720,13 +712,7 @@ ASTCompiler.prototype = {
720712
assign: {vars: [], body: [], own: {}},
721713
inputs: []
722714
};
723-
var lastExpression;
724-
var i;
725-
for (i = 0; i < ast.body.length; ++i) {
726-
findConstantAndWatchExpressions(ast.body[i].expression, this.$filter);
727-
}
728-
var toWatch = useInputs(ast.body) ? ast.body[ast.body.length - 1].expression.toWatch : [];
729-
forEach(toWatch, function(watch, key) {
715+
forEach(findInputs(ast, this.$filter) || [], function(watch, key) {
730716
var fnKey = 'fn' + key;
731717
self.state[fnKey] = {vars: [], body: [], own: {}};
732718
self.state.computing = fnKey;
@@ -737,7 +723,8 @@ ASTCompiler.prototype = {
737723
watch.watchId = key;
738724
});
739725
this.state.computing = 'fn';
740-
for (i = 0; i < ast.body.length; ++i) {
726+
var lastExpression;
727+
for (var i = 0; i < ast.body.length; ++i) {
741728
if (lastExpression) this.current().body.push(lastExpression, ';');
742729
this.recurse(ast.body[i].expression, undefined, undefined, function(expr) { lastExpression = expr; });
743730
}
@@ -747,7 +734,7 @@ ASTCompiler.prototype = {
747734
this.state.computing = 'assign';
748735
var result = this.nextId();
749736
this.recurse({type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}, result);
750-
extra = 'fn.assign=function(s,v,l){' +
737+
extra = 'fn.assign=function(s,v,l,i){' +
751738
this.varsPrefix('assign') +
752739
this.body('assign') +
753740
'};';
@@ -763,14 +750,8 @@ ASTCompiler.prototype = {
763750
'};' +
764751
extra +
765752
this.watchFns() +
766-
'fn.literal=literal;fn.constant=constant;' +
767753
'return fn;';
768754

769-
var isLiteral = ast.body.length === 1 && (
770-
ast.body[0].expression.type === AST.Literal ||
771-
ast.body[0].expression.type === AST.ArrayExpression ||
772-
ast.body[0].expression.type === AST.ObjectExpression);
773-
var isConstant = ast.body.length === 1 && ast.body[0].expression.constant;
774755
/* jshint -W054 */
775756
var fn = (new Function('$filter',
776757
'ensureSafeMemberName',
@@ -780,8 +761,6 @@ ASTCompiler.prototype = {
780761
'ifDefined',
781762
'plus',
782763
'text',
783-
'literal',
784-
'constant',
785764
fnString))(
786765
this.$filter,
787766
ensureSafeMemberName,
@@ -790,9 +769,9 @@ ASTCompiler.prototype = {
790769
isPossiblyDangerousMemberName,
791770
ifDefined,
792771
plusFn,
793-
expression,
794-
isLiteral,
795-
isConstant);
772+
expression);
773+
fn.constant = isConstant(ast, this.$filter);
774+
fn.literal = isLiteral(ast);
796775
/* jshint +W054 */
797776
this.state = undefined;
798777
return fn;
@@ -1219,9 +1198,6 @@ ASTInterpreter.prototype = {
12191198
var ast = this.astBuilder.ast(expression);
12201199
this.expression = expression;
12211200
this.expensiveChecks = expensiveChecks;
1222-
forEach(ast.body, function(expression) {
1223-
findConstantAndWatchExpressions(expression.expression, self.$filter);
1224-
});
12251201
var expressions = [];
12261202
forEach(ast.body, function(expression) {
12271203
expressions.push(self.recurse(expression.expression));
@@ -1235,25 +1211,18 @@ ASTInterpreter.prototype = {
12351211
});
12361212
return lastValue;
12371213
};
1238-
var toWatch = useInputs(ast.body) ? ast.body[ast.body.length - 1].expression.toWatch : [];
1239-
if (toWatch.length) {
1240-
var inputs = [];
1241-
forEach(toWatch, function(watch, key) {
1242-
inputs.push(self.recurse(watch));
1243-
});
1244-
fn.inputs = inputs;
1245-
}
12461214
if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) {
12471215
var assign = this.recurse({type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='});
12481216
fn.assign = function(scope, value, locals) {
12491217
return assign(scope, locals, value);
12501218
};
12511219
}
1252-
fn.literal = ast.body.length === 1 && (
1253-
ast.body[0].expression.type === AST.Literal ||
1254-
ast.body[0].expression.type === AST.ArrayExpression ||
1255-
ast.body[0].expression.type === AST.ObjectExpression);
1256-
fn.constant = ast.body.length === 1 && ast.body[0].expression.constant;
1220+
fn.literal = isLiteral(ast);
1221+
fn.constant = isConstant(ast, this.$filter);
1222+
fn.inputs = findInputs(ast, this.$filter);
1223+
if (fn.inputs) {
1224+
fn.inputs = fn.inputs.map(function(input) { return self.recurse(input); });
1225+
}
12571226
return fn;
12581227
},
12591228

0 commit comments

Comments
 (0)