diff options
| author | Daniel Luz | 2012-11-04 11:05:58 -0200 | 
|---|---|---|
| committer | Misko Hevery | 2013-02-14 14:43:56 -0800 | 
| commit | 1ed638582d2f2c7f89384d9712f4cfac52cc5b70 (patch) | |
| tree | 2e2868b6939f2db28ed0cdb9692a32a3b8b85b8d | |
| parent | 3b14092135ab02a4b08c0ba21d40726684acf83e (diff) | |
| download | angular.js-1ed638582d2f2c7f89384d9712f4cfac52cc5b70.tar.bz2 | |
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.
| -rw-r--r-- | src/ng/parse.js | 51 | ||||
| -rw-r--r-- | test/ng/parseSpec.js | 68 | 
2 files changed, 108 insertions, 11 deletions
| diff --git a/src/ng/parse.js b/src/ng/parse.js index 60181f96..4651fc10 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -303,6 +303,8 @@ function parser(text, json, $filter, csp){    if (tokens.length !== 0) {      throwError("is an unexpected token", tokens[0]);    } +  value.literal = !!value.literal; +  value.constant = !!value.constant;    return value;    /////////////////////////////////// @@ -350,15 +352,19 @@ function parser(text, json, $filter, csp){    }    function unaryFn(fn, right) { -    return function(self, locals) { +    return extend(function(self, locals) {        return fn(self, locals, right); -    }; +    }, { +      constant:right.constant +    });    }    function binaryFn(left, fn, right) { -    return function(self, locals) { +    return extend(function(self, locals) {        return fn(self, locals, left, right); -    }; +    }, { +      constant:left.constant && right.constant +    });    }    function statements() { @@ -526,6 +532,9 @@ function parser(text, json, $filter, csp){        if (!primary) {          throwError("not a primary expression", token);        } +      if (token.json) { +        primary.constant = primary.literal = true; +      }      }      var next, context; @@ -614,23 +623,32 @@ function parser(text, json, $filter, csp){    // This is used with json array declaration    function arrayDeclaration () {      var elementFns = []; +    var allConstant = true;      if (peekToken().text != ']') {        do { -        elementFns.push(expression()); +        var elementFn = expression(); +        elementFns.push(elementFn); +        if (!elementFn.constant) { +          allConstant = false; +        }        } while (expect(','));      }      consume(']'); -    return function(self, locals){ +    return extend(function(self, locals){        var array = [];        for ( var i = 0; i < elementFns.length; i++) {          array.push(elementFns[i](self, locals));        }        return array; -    }; +    }, { +      literal:true, +      constant:allConstant +    });    }    function object () {      var keyValues = []; +    var allConstant = true;      if (peekToken().text != '}') {        do {          var token = expect(), @@ -638,10 +656,13 @@ function parser(text, json, $filter, csp){          consume(":");          var value = expression();          keyValues.push({key:key, value:value}); +        if (!value.constant) { +          allConstant = false; +        }        } while (expect(','));      }      consume('}'); -    return function(self, locals){ +    return extend(function(self, locals){        var object = {};        for ( var i = 0; i < keyValues.length; i++) {          var keyValue = keyValues[i]; @@ -649,7 +670,10 @@ function parser(text, json, $filter, csp){          object[keyValue.key] = value;        }        return object; -    }; +    }, { +      literal:true, +      constant:allConstant +    });    }  } @@ -853,8 +877,13 @@ function getterFn(path, csp) {   *    * `locals` – `{object=}` – local variables context object, useful for overriding values in   *      `context`.   * - *    The return function also has an `assign` property, if the expression is assignable, which - *    allows one to set values to expressions. + *    The returned function also has the following properties: + *      * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript + *        literal. + *      * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript + *        constant literals. + *      * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be + *        set to a function to change its value on the given context.   *   */  function $ParseProvider() { diff --git a/test/ng/parseSpec.js b/test/ng/parseSpec.js index a6f43282..020a1d6f 100644 --- a/test/ng/parseSpec.js +++ b/test/ng/parseSpec.js @@ -671,6 +671,74 @@ describe('parser', function() {            expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);          }));        }); + +      describe('literal', function() { +        it('should mark scalar value expressions as literal', inject(function($parse) { +          expect($parse('0').literal).toBe(true); +          expect($parse('"hello"').literal).toBe(true); +          expect($parse('true').literal).toBe(true); +          expect($parse('false').literal).toBe(true); +          expect($parse('null').literal).toBe(true); +          expect($parse('undefined').literal).toBe(true); +        })); + +        it('should mark array expressions as literal', inject(function($parse) { +          expect($parse('[]').literal).toBe(true); +          expect($parse('[1, 2, 3]').literal).toBe(true); +          expect($parse('[1, identifier]').literal).toBe(true); +        })); + +        it('should mark object expressions as literal', inject(function($parse) { +          expect($parse('{}').literal).toBe(true); +          expect($parse('{x: 1}').literal).toBe(true); +          expect($parse('{foo: bar}').literal).toBe(true); +        })); + +        it('should not mark function calls or operator expressions as literal', inject(function($parse) { +          expect($parse('1 + 1').literal).toBe(false); +          expect($parse('call()').literal).toBe(false); +          expect($parse('[].length').literal).toBe(false); +        })); +      }); + +      describe('constant', function() { +        it('should mark scalar value expressions as constant', inject(function($parse) { +          expect($parse('12.3').constant).toBe(true); +          expect($parse('"string"').constant).toBe(true); +          expect($parse('true').constant).toBe(true); +          expect($parse('false').constant).toBe(true); +          expect($parse('null').constant).toBe(true); +          expect($parse('undefined').constant).toBe(true); +        })); + +        it('should mark arrays as constant if they only contain constant elements', inject(function($parse) { +          expect($parse('[]').constant).toBe(true); +          expect($parse('[1, 2, 3]').constant).toBe(true); +          expect($parse('["string", null]').constant).toBe(true); +          expect($parse('[[]]').constant).toBe(true); +          expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true); +        })); + +        it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) { +          expect($parse('[foo]').constant).toBe(false); +          expect($parse('[x + 1]').constant).toBe(false); +          expect($parse('[bar[0]]').constant).toBe(false); +        })); + +        it('should mark complex expressions involving constant values as constant', inject(function($parse) { +          expect($parse('!true').constant).toBe(true); +          expect($parse('1 - 1').constant).toBe(true); +          expect($parse('"foo" + "bar"').constant).toBe(true); +          expect($parse('5 != null').constant).toBe(true); +          expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true); +        })); + +        it('should not mark any expression involving variables or function calls as constant', inject(function($parse) { +          expect($parse('true.toString()').constant).toBe(false); +          expect($parse('foo(1, 2, 3)').constant).toBe(false); +          expect($parse('"name" + id').constant).toBe(false); +        })); +      });      });    });  }); | 
