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

Commit 1ed6385

Browse files
mernenmhevery
authored andcommitted
feat($parse): added constant and literal properties
* `literal` is set to true if the expression's top-level is a JavaScript literal (number, string, boolean, null/undefined, array, object), even if it contains non-literals inside. * `constant` is set to true if the expression is known to be made entirely of constant values, i.e., evaluating it will always yield the same result. A consequence is that a JSON expression is guaranteed to be both literal and constant.
1 parent 3b14092 commit 1ed6385

File tree

2 files changed

+108
-11
lines changed

2 files changed

+108
-11
lines changed

src/ng/parse.js

+40-11
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ function parser(text, json, $filter, csp){
303303
if (tokens.length !== 0) {
304304
throwError("is an unexpected token", tokens[0]);
305305
}
306+
value.literal = !!value.literal;
307+
value.constant = !!value.constant;
306308
return value;
307309

308310
///////////////////////////////////
@@ -350,15 +352,19 @@ function parser(text, json, $filter, csp){
350352
}
351353

352354
function unaryFn(fn, right) {
353-
return function(self, locals) {
355+
return extend(function(self, locals) {
354356
return fn(self, locals, right);
355-
};
357+
}, {
358+
constant:right.constant
359+
});
356360
}
357361

358362
function binaryFn(left, fn, right) {
359-
return function(self, locals) {
363+
return extend(function(self, locals) {
360364
return fn(self, locals, left, right);
361-
};
365+
}, {
366+
constant:left.constant && right.constant
367+
});
362368
}
363369

364370
function statements() {
@@ -526,6 +532,9 @@ function parser(text, json, $filter, csp){
526532
if (!primary) {
527533
throwError("not a primary expression", token);
528534
}
535+
if (token.json) {
536+
primary.constant = primary.literal = true;
537+
}
529538
}
530539

531540
var next, context;
@@ -614,42 +623,57 @@ function parser(text, json, $filter, csp){
614623
// This is used with json array declaration
615624
function arrayDeclaration () {
616625
var elementFns = [];
626+
var allConstant = true;
617627
if (peekToken().text != ']') {
618628
do {
619-
elementFns.push(expression());
629+
var elementFn = expression();
630+
elementFns.push(elementFn);
631+
if (!elementFn.constant) {
632+
allConstant = false;
633+
}
620634
} while (expect(','));
621635
}
622636
consume(']');
623-
return function(self, locals){
637+
return extend(function(self, locals){
624638
var array = [];
625639
for ( var i = 0; i < elementFns.length; i++) {
626640
array.push(elementFns[i](self, locals));
627641
}
628642
return array;
629-
};
643+
}, {
644+
literal:true,
645+
constant:allConstant
646+
});
630647
}
631648

632649
function object () {
633650
var keyValues = [];
651+
var allConstant = true;
634652
if (peekToken().text != '}') {
635653
do {
636654
var token = expect(),
637655
key = token.string || token.text;
638656
consume(":");
639657
var value = expression();
640658
keyValues.push({key:key, value:value});
659+
if (!value.constant) {
660+
allConstant = false;
661+
}
641662
} while (expect(','));
642663
}
643664
consume('}');
644-
return function(self, locals){
665+
return extend(function(self, locals){
645666
var object = {};
646667
for ( var i = 0; i < keyValues.length; i++) {
647668
var keyValue = keyValues[i];
648669
var value = keyValue.value(self, locals);
649670
object[keyValue.key] = value;
650671
}
651672
return object;
652-
};
673+
}, {
674+
literal:true,
675+
constant:allConstant
676+
});
653677
}
654678
}
655679

@@ -853,8 +877,13 @@ function getterFn(path, csp) {
853877
* * `locals` – `{object=}` – local variables context object, useful for overriding values in
854878
* `context`.
855879
*
856-
* The return function also has an `assign` property, if the expression is assignable, which
857-
* allows one to set values to expressions.
880+
* The returned function also has the following properties:
881+
* * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
882+
* literal.
883+
* * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
884+
* constant literals.
885+
* * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
886+
* set to a function to change its value on the given context.
858887
*
859888
*/
860889
function $ParseProvider() {

test/ng/parseSpec.js

+68
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,74 @@ describe('parser', function() {
671671
expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
672672
}));
673673
});
674+
675+
describe('literal', function() {
676+
it('should mark scalar value expressions as literal', inject(function($parse) {
677+
expect($parse('0').literal).toBe(true);
678+
expect($parse('"hello"').literal).toBe(true);
679+
expect($parse('true').literal).toBe(true);
680+
expect($parse('false').literal).toBe(true);
681+
expect($parse('null').literal).toBe(true);
682+
expect($parse('undefined').literal).toBe(true);
683+
}));
684+
685+
it('should mark array expressions as literal', inject(function($parse) {
686+
expect($parse('[]').literal).toBe(true);
687+
expect($parse('[1, 2, 3]').literal).toBe(true);
688+
expect($parse('[1, identifier]').literal).toBe(true);
689+
}));
690+
691+
it('should mark object expressions as literal', inject(function($parse) {
692+
expect($parse('{}').literal).toBe(true);
693+
expect($parse('{x: 1}').literal).toBe(true);
694+
expect($parse('{foo: bar}').literal).toBe(true);
695+
}));
696+
697+
it('should not mark function calls or operator expressions as literal', inject(function($parse) {
698+
expect($parse('1 + 1').literal).toBe(false);
699+
expect($parse('call()').literal).toBe(false);
700+
expect($parse('[].length').literal).toBe(false);
701+
}));
702+
});
703+
704+
describe('constant', function() {
705+
it('should mark scalar value expressions as constant', inject(function($parse) {
706+
expect($parse('12.3').constant).toBe(true);
707+
expect($parse('"string"').constant).toBe(true);
708+
expect($parse('true').constant).toBe(true);
709+
expect($parse('false').constant).toBe(true);
710+
expect($parse('null').constant).toBe(true);
711+
expect($parse('undefined').constant).toBe(true);
712+
}));
713+
714+
it('should mark arrays as constant if they only contain constant elements', inject(function($parse) {
715+
expect($parse('[]').constant).toBe(true);
716+
expect($parse('[1, 2, 3]').constant).toBe(true);
717+
expect($parse('["string", null]').constant).toBe(true);
718+
expect($parse('[[]]').constant).toBe(true);
719+
expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true);
720+
}));
721+
722+
it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) {
723+
expect($parse('[foo]').constant).toBe(false);
724+
expect($parse('[x + 1]').constant).toBe(false);
725+
expect($parse('[bar[0]]').constant).toBe(false);
726+
}));
727+
728+
it('should mark complex expressions involving constant values as constant', inject(function($parse) {
729+
expect($parse('!true').constant).toBe(true);
730+
expect($parse('1 - 1').constant).toBe(true);
731+
expect($parse('"foo" + "bar"').constant).toBe(true);
732+
expect($parse('5 != null').constant).toBe(true);
733+
expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true);
734+
}));
735+
736+
it('should not mark any expression involving variables or function calls as constant', inject(function($parse) {
737+
expect($parse('true.toString()').constant).toBe(false);
738+
expect($parse('foo(1, 2, 3)').constant).toBe(false);
739+
expect($parse('"name" + id').constant).toBe(false);
740+
}));
741+
});
674742
});
675743
});
676744
});

0 commit comments

Comments
 (0)