diff --git a/src/ng/parse.js b/src/ng/parse.js index 37567b405485..90ecd447d79d 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -463,8 +463,10 @@ AST.prototype = { primary = this.arrayDeclaration(); } else if (this.expect('{')) { primary = this.object(); - } else if (this.constants.hasOwnProperty(this.peek().text)) { - primary = copy(this.constants[this.consume().text]); + } else if (this.selfReferential.hasOwnProperty(this.peek().text)) { + primary = copy(this.selfReferential[this.consume().text]); + } else if (this.options.literals.hasOwnProperty(this.peek().text)) { + primary = { type: AST.Literal, value: this.options.literals[this.consume().text]}; } else if (this.peek().identifier) { primary = this.identifier(); } else if (this.peek().constant) { @@ -616,15 +618,7 @@ AST.prototype = { return false; }, - - /* `undefined` is not a constant, it is an identifier, - * but using it as an identifier is not supported - */ - constants: { - 'true': { type: AST.Literal, value: true }, - 'false': { type: AST.Literal, value: false }, - 'null': { type: AST.Literal, value: null }, - 'undefined': {type: AST.Literal, value: undefined }, + selfReferential: { 'this': {type: AST.ThisExpression }, '$locals': {type: AST.LocalsExpression } } @@ -1669,7 +1663,7 @@ var Parser = function(lexer, $filter, options) { this.lexer = lexer; this.$filter = $filter; this.options = options; - this.ast = new AST(this.lexer); + this.ast = new AST(lexer, options); this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : new ASTCompiler(this.ast, $filter); }; @@ -1746,16 +1740,39 @@ function getValueOf(value) { function $ParseProvider() { var cacheDefault = createMap(); var cacheExpensive = createMap(); + var literals = { + 'true': true, + 'false': false, + 'null': null, + 'undefined': undefined + }; + + /** + * @ngdoc method + * @name $parseProvider#addLiteral + * @description + * + * Configure $parse service to add literal values that will be present as literal at expressions. + * + * @param {string} literalName Token for the literal value. The literal name value must be a valid literal name. + * @param {*} literalValue Value for this literal. All literal values must be primitives or `undefined`. + * + **/ + this.addLiteral = function(literalName, literalValue) { + literals[literalName] = literalValue; + }; this.$get = ['$filter', function($filter) { var noUnsafeEval = csp().noUnsafeEval; var $parseOptions = { csp: noUnsafeEval, - expensiveChecks: false + expensiveChecks: false, + literals: copy(literals) }, $parseOptionsExpensive = { csp: noUnsafeEval, - expensiveChecks: true + expensiveChecks: true, + literals: copy(literals) }; var runningChecksEnabled = false; diff --git a/test/ng/parseSpec.js b/test/ng/parseSpec.js index 1011c1c2983c..77d5d9fa2079 100644 --- a/test/ng/parseSpec.js +++ b/test/ng/parseSpec.js @@ -223,7 +223,7 @@ describe('parser', function() { /* global AST: false */ createAst = function() { var lexer = new Lexer({csp: false}); - var ast = new AST(lexer, {csp: false}); + var ast = new AST(lexer, {csp: false, literals: {'true': true, 'false': false, 'undefined': undefined, 'null': null}}); return ast.ast.apply(ast, arguments); }; }); @@ -1681,6 +1681,19 @@ describe('parser', function() { $filterProvider = filterProvider; }])); + forEach([true, false], function(cspEnabled) { + beforeEach(module(['$parseProvider', function(parseProvider) { + parseProvider.addLiteral('Infinity', Infinity); + }])); + + it('should allow extending literals with csp ' + cspEnabled, inject(function($rootScope) { + expect($rootScope.$eval("Infinity")).toEqual(Infinity); + expect($rootScope.$eval("-Infinity")).toEqual(-Infinity); + expect(function() {$rootScope.$eval("Infinity = 1");}).toThrow(); + expect($rootScope.$eval("Infinity")).toEqual(Infinity); + })); + }); + forEach([true, false], function(cspEnabled) { describe('csp: ' + cspEnabled, function() {