2011-07-17 08:05:43 +00:00
|
|
|
|
'use strict';
|
|
|
|
|
|
|
2013-06-24 21:14:54 +00:00
|
|
|
|
var $parseMinErr = minErr('$parse');
|
2013-10-07 16:58:37 +00:00
|
|
|
|
var promiseWarningCache = {};
|
|
|
|
|
|
var promiseWarning;
|
2013-06-24 21:14:54 +00:00
|
|
|
|
|
|
|
|
|
|
// Sandboxing Angular Expressions
|
|
|
|
|
|
// ------------------------------
|
|
|
|
|
|
// Angular expressions are generally considered safe because these expressions only have direct access to $scope and
|
|
|
|
|
|
// locals. However, one can obtain the ability to execute arbitrary JS code by obtaining a reference to native JS
|
|
|
|
|
|
// functions such as the Function constructor.
|
|
|
|
|
|
//
|
|
|
|
|
|
// As an example, consider the following Angular expression:
|
|
|
|
|
|
//
|
|
|
|
|
|
// {}.toString.constructor(alert("evil JS code"))
|
|
|
|
|
|
//
|
|
|
|
|
|
// We want to prevent this type of access. For the sake of performance, during the lexing phase we disallow any "dotted"
|
|
|
|
|
|
// access to any member named "constructor".
|
|
|
|
|
|
//
|
|
|
|
|
|
// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor while evaluating
|
|
|
|
|
|
// the expression, which is a stronger but more expensive test. Since reflective calls are expensive anyway, this is not
|
|
|
|
|
|
// such a big deal compared to static dereferencing.
|
|
|
|
|
|
//
|
2013-07-22 15:01:00 +00:00
|
|
|
|
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits against the
|
2013-06-24 21:14:54 +00:00
|
|
|
|
// expression language, but not to prevent exploits that were enabled by exposing sensitive JavaScript or browser apis
|
|
|
|
|
|
// on Scope. Exposing such objects on a Scope is never a good practice and therefore we are not even trying to protect
|
|
|
|
|
|
// against interaction with an object explicitly exposed in this way.
|
|
|
|
|
|
//
|
|
|
|
|
|
// A developer could foil the name check by aliasing the Function constructor under a different name on the scope.
|
|
|
|
|
|
//
|
|
|
|
|
|
// In general, it is not possible to access a Window object from an angular expression unless a window or some DOM
|
|
|
|
|
|
// object that has a reference to window is published onto a Scope.
|
|
|
|
|
|
|
|
|
|
|
|
function ensureSafeMemberName(name, fullExpression) {
|
|
|
|
|
|
if (name === "constructor") {
|
|
|
|
|
|
throw $parseMinErr('isecfld',
|
|
|
|
|
|
'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', fullExpression);
|
|
|
|
|
|
}
|
|
|
|
|
|
return name;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function ensureSafeObject(obj, fullExpression) {
|
|
|
|
|
|
// nifty check if obj is Function that is fast and works across iframes and other contexts
|
|
|
|
|
|
if (obj && obj.constructor === obj) {
|
|
|
|
|
|
throw $parseMinErr('isecfn',
|
|
|
|
|
|
'Referencing Function in Angular expressions is disallowed! Expression: {0}', fullExpression);
|
2013-08-09 21:47:13 +00:00
|
|
|
|
} else if (// isWindow(obj)
|
|
|
|
|
|
obj && obj.document && obj.location && obj.alert && obj.setInterval) {
|
|
|
|
|
|
throw $parseMinErr('isecwindow',
|
|
|
|
|
|
'Referencing the Window in Angular expressions is disallowed! Expression: {0}', fullExpression);
|
|
|
|
|
|
} else if (// isElement(obj)
|
|
|
|
|
|
obj && (obj.nodeName || (obj.on && obj.find))) {
|
|
|
|
|
|
throw $parseMinErr('isecdom',
|
|
|
|
|
|
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', fullExpression);
|
2013-06-24 21:14:54 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
return obj;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2010-10-08 23:25:12 +00:00
|
|
|
|
var OPERATORS = {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
'null':function(){return null;},
|
|
|
|
|
|
'true':function(){return true;},
|
|
|
|
|
|
'false':function(){return false;},
|
2012-01-24 10:40:25 +00:00
|
|
|
|
undefined:noop,
|
2012-05-25 20:58:34 +00:00
|
|
|
|
'+':function(self, locals, a,b){
|
|
|
|
|
|
a=a(self, locals); b=b(self, locals);
|
|
|
|
|
|
if (isDefined(a)) {
|
|
|
|
|
|
if (isDefined(b)) {
|
|
|
|
|
|
return a + b;
|
|
|
|
|
|
}
|
|
|
|
|
|
return a;
|
|
|
|
|
|
}
|
|
|
|
|
|
return isDefined(b)?b:undefined;},
|
2012-02-07 05:56:05 +00:00
|
|
|
|
'-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
|
|
|
|
|
|
'*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
|
|
|
|
|
|
'/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
|
|
|
|
|
|
'%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
|
|
|
|
|
|
'^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
|
2010-12-07 19:39:59 +00:00
|
|
|
|
'=':noop,
|
2012-11-27 16:00:46 +00:00
|
|
|
|
'===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
|
|
|
|
|
|
'!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
|
2012-02-07 05:56:05 +00:00
|
|
|
|
'==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
|
|
|
|
|
|
'!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
|
|
|
|
|
|
'<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
|
|
|
|
|
|
'>':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
|
|
|
|
|
|
'<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
|
|
|
|
|
|
'>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
|
|
|
|
|
|
'&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
|
|
|
|
|
|
'||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
|
|
|
|
|
|
'&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
|
|
|
|
|
|
// '|':function(self, locals, a,b){return a|b;},
|
|
|
|
|
|
'|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
|
|
|
|
|
|
'!':function(self, locals, a){return !a(self, locals);}
|
2010-01-06 00:36:58 +00:00
|
|
|
|
};
|
2010-10-08 23:25:12 +00:00
|
|
|
|
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
|
2010-01-06 00:36:58 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
|
|
|
|
|
|
/////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @constructor
|
|
|
|
|
|
*/
|
2013-10-07 16:58:37 +00:00
|
|
|
|
var Lexer = function (options) {
|
|
|
|
|
|
this.options = options;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Lexer.prototype = {
|
|
|
|
|
|
constructor: Lexer,
|
|
|
|
|
|
|
|
|
|
|
|
lex: function (text) {
|
|
|
|
|
|
this.text = text;
|
2013-10-07 16:58:37 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.index = 0;
|
|
|
|
|
|
this.ch = undefined;
|
|
|
|
|
|
this.lastCh = ':'; // can start regexp
|
|
|
|
|
|
|
|
|
|
|
|
this.tokens = [];
|
|
|
|
|
|
|
|
|
|
|
|
var token;
|
|
|
|
|
|
var json = [];
|
|
|
|
|
|
|
|
|
|
|
|
while (this.index < this.text.length) {
|
|
|
|
|
|
this.ch = this.text.charAt(this.index);
|
|
|
|
|
|
if (this.is('"\'')) {
|
|
|
|
|
|
this.readString(this.ch);
|
|
|
|
|
|
} else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) {
|
|
|
|
|
|
this.readNumber();
|
|
|
|
|
|
} else if (this.isIdent(this.ch)) {
|
|
|
|
|
|
this.readIdent();
|
|
|
|
|
|
// identifiers can only be if the preceding char was a { or ,
|
|
|
|
|
|
if (this.was('{,') && json[0] === '{' &&
|
|
|
|
|
|
(token = this.tokens[this.tokens.length - 1])) {
|
|
|
|
|
|
token.json = token.text.indexOf('.') === -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (this.is('(){}[].,;:?')) {
|
|
|
|
|
|
this.tokens.push({
|
|
|
|
|
|
index: this.index,
|
|
|
|
|
|
text: this.ch,
|
|
|
|
|
|
json: (this.was(':[,') && this.is('{[')) || this.is('}]:,')
|
|
|
|
|
|
});
|
|
|
|
|
|
if (this.is('{[')) json.unshift(this.ch);
|
|
|
|
|
|
if (this.is('}]')) json.shift();
|
|
|
|
|
|
this.index++;
|
|
|
|
|
|
} else if (this.isWhitespace(this.ch)) {
|
|
|
|
|
|
this.index++;
|
|
|
|
|
|
continue;
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
var ch2 = this.ch + this.peek();
|
|
|
|
|
|
var ch3 = ch2 + this.peek(2);
|
|
|
|
|
|
var fn = OPERATORS[this.ch];
|
|
|
|
|
|
var fn2 = OPERATORS[ch2];
|
|
|
|
|
|
var fn3 = OPERATORS[ch3];
|
|
|
|
|
|
if (fn3) {
|
|
|
|
|
|
this.tokens.push({index: this.index, text: ch3, fn: fn3});
|
|
|
|
|
|
this.index += 3;
|
|
|
|
|
|
} else if (fn2) {
|
|
|
|
|
|
this.tokens.push({index: this.index, text: ch2, fn: fn2});
|
|
|
|
|
|
this.index += 2;
|
|
|
|
|
|
} else if (fn) {
|
|
|
|
|
|
this.tokens.push({
|
|
|
|
|
|
index: this.index,
|
|
|
|
|
|
text: this.ch,
|
|
|
|
|
|
fn: fn,
|
|
|
|
|
|
json: (this.was('[,:') && this.is('+-'))
|
|
|
|
|
|
});
|
|
|
|
|
|
this.index += 1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.throwError('Unexpected next character ', this.index, this.index + 1);
|
|
|
|
|
|
}
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.lastCh = this.ch;
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
return this.tokens;
|
|
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
is: function(chars) {
|
|
|
|
|
|
return chars.indexOf(this.ch) !== -1;
|
|
|
|
|
|
},
|
2010-10-15 22:28:58 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
was: function(chars) {
|
|
|
|
|
|
return chars.indexOf(this.lastCh) !== -1;
|
|
|
|
|
|
},
|
2010-10-15 22:28:58 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
peek: function(i) {
|
2012-11-27 16:00:46 +00:00
|
|
|
|
var num = i || 1;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isNumber: function(ch) {
|
|
|
|
|
|
return ('0' <= ch && ch <= '9');
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isWhitespace: function(ch) {
|
|
|
|
|
|
return (ch === ' ' || ch === '\r' || ch === '\t' ||
|
|
|
|
|
|
ch === '\n' || ch === '\v' || ch === '\u00A0'); // IE treats non-breaking space as \u00A0
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isIdent: function(ch) {
|
|
|
|
|
|
return ('a' <= ch && ch <= 'z' ||
|
|
|
|
|
|
'A' <= ch && ch <= 'Z' ||
|
|
|
|
|
|
'_' === ch || ch === '$');
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
isExpOperator: function(ch) {
|
|
|
|
|
|
return (ch === '-' || ch === '+' || this.isNumber(ch));
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
throwError: function(error, start, end) {
|
|
|
|
|
|
end = end || this.index;
|
|
|
|
|
|
var colStr = (isDefined(start)
|
|
|
|
|
|
? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']'
|
|
|
|
|
|
: ' ' + end);
|
|
|
|
|
|
throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].',
|
|
|
|
|
|
error, colStr, this.text);
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
readNumber: function() {
|
|
|
|
|
|
var number = '';
|
|
|
|
|
|
var start = this.index;
|
|
|
|
|
|
while (this.index < this.text.length) {
|
|
|
|
|
|
var ch = lowercase(this.text.charAt(this.index));
|
|
|
|
|
|
if (ch == '.' || this.isNumber(ch)) {
|
2010-12-22 01:38:04 +00:00
|
|
|
|
number += ch;
|
|
|
|
|
|
} else {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
var peekCh = this.peek();
|
|
|
|
|
|
if (ch == 'e' && this.isExpOperator(peekCh)) {
|
2010-12-22 01:38:04 +00:00
|
|
|
|
number += ch;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
} else if (this.isExpOperator(ch) &&
|
|
|
|
|
|
peekCh && this.isNumber(peekCh) &&
|
2010-12-22 01:38:04 +00:00
|
|
|
|
number.charAt(number.length - 1) == 'e') {
|
|
|
|
|
|
number += ch;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
} else if (this.isExpOperator(ch) &&
|
|
|
|
|
|
(!peekCh || !this.isNumber(peekCh)) &&
|
2010-12-22 01:38:04 +00:00
|
|
|
|
number.charAt(number.length - 1) == 'e') {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.throwError('Invalid exponent');
|
2010-12-22 01:38:04 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.index++;
|
2010-12-22 01:38:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
number = 1 * number;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.tokens.push({
|
|
|
|
|
|
index: start,
|
|
|
|
|
|
text: number,
|
|
|
|
|
|
json: true,
|
|
|
|
|
|
fn: function() { return number; }
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
readIdent: function() {
|
|
|
|
|
|
var parser = this;
|
|
|
|
|
|
|
|
|
|
|
|
var ident = '';
|
|
|
|
|
|
var start = this.index;
|
|
|
|
|
|
|
|
|
|
|
|
var lastDot, peekIndex, methodName, ch;
|
|
|
|
|
|
|
|
|
|
|
|
while (this.index < this.text.length) {
|
|
|
|
|
|
ch = this.text.charAt(this.index);
|
|
|
|
|
|
if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) {
|
|
|
|
|
|
if (ch === '.') lastDot = this.index;
|
2010-12-22 01:38:04 +00:00
|
|
|
|
ident += ch;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
break;
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.index++;
|
2010-12-22 01:38:04 +00:00
|
|
|
|
}
|
2012-01-24 10:33:35 +00:00
|
|
|
|
|
|
|
|
|
|
//check if this is not a method invocation and if it is back out to last dot
|
|
|
|
|
|
if (lastDot) {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
peekIndex = this.index;
|
|
|
|
|
|
while (peekIndex < this.text.length) {
|
|
|
|
|
|
ch = this.text.charAt(peekIndex);
|
|
|
|
|
|
if (ch === '(') {
|
2012-01-24 10:33:35 +00:00
|
|
|
|
methodName = ident.substr(lastDot - start + 1);
|
|
|
|
|
|
ident = ident.substr(0, lastDot - start);
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.index = peekIndex;
|
2012-01-24 10:33:35 +00:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if (this.isWhitespace(ch)) {
|
2012-01-24 10:33:35 +00:00
|
|
|
|
peekIndex++;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-03-08 01:07:25 +00:00
|
|
|
|
|
|
|
|
|
|
var token = {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
index: start,
|
|
|
|
|
|
text: ident
|
2012-03-08 01:07:25 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2013-10-05 09:49:09 +00:00
|
|
|
|
// OPERATORS is our own object so we don't need to use special hasOwnPropertyFn
|
2012-03-08 01:07:25 +00:00
|
|
|
|
if (OPERATORS.hasOwnProperty(ident)) {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
token.fn = OPERATORS[ident];
|
|
|
|
|
|
token.json = OPERATORS[ident];
|
2012-03-08 01:07:25 +00:00
|
|
|
|
} else {
|
2013-10-07 16:58:37 +00:00
|
|
|
|
var getter = getterFn(ident, this.options, this.text);
|
2012-03-08 01:07:25 +00:00
|
|
|
|
token.fn = extend(function(self, locals) {
|
|
|
|
|
|
return (getter(self, locals));
|
|
|
|
|
|
}, {
|
|
|
|
|
|
assign: function(self, value) {
|
2013-10-07 16:58:37 +00:00
|
|
|
|
return setter(self, ident, value, parser.text, parser.options);
|
2012-03-08 01:07:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.tokens.push(token);
|
2012-01-24 10:33:35 +00:00
|
|
|
|
|
|
|
|
|
|
if (methodName) {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.tokens.push({
|
2012-01-24 10:33:35 +00:00
|
|
|
|
index:lastDot,
|
|
|
|
|
|
text: '.',
|
|
|
|
|
|
json: false
|
|
|
|
|
|
});
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.tokens.push({
|
2012-01-24 10:33:35 +00:00
|
|
|
|
index: lastDot + 1,
|
|
|
|
|
|
text: methodName,
|
|
|
|
|
|
json: false
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2011-01-19 23:42:11 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
readString: function(quote) {
|
|
|
|
|
|
var start = this.index;
|
|
|
|
|
|
this.index++;
|
|
|
|
|
|
var string = '';
|
2010-12-22 01:38:04 +00:00
|
|
|
|
var rawString = quote;
|
|
|
|
|
|
var escape = false;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
while (this.index < this.text.length) {
|
|
|
|
|
|
var ch = this.text.charAt(this.index);
|
2010-12-22 01:38:04 +00:00
|
|
|
|
rawString += ch;
|
|
|
|
|
|
if (escape) {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if (ch === 'u') {
|
|
|
|
|
|
var hex = this.text.substring(this.index + 1, this.index + 5);
|
2010-12-22 01:38:04 +00:00
|
|
|
|
if (!hex.match(/[\da-f]{4}/i))
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.throwError('Invalid unicode escape [\\u' + hex + ']');
|
|
|
|
|
|
this.index += 4;
|
2010-12-22 01:38:04 +00:00
|
|
|
|
string += String.fromCharCode(parseInt(hex, 16));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
var rep = ESCAPE[ch];
|
|
|
|
|
|
if (rep) {
|
|
|
|
|
|
string += rep;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
string += ch;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
escape = false;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
} else if (ch === '\\') {
|
2010-12-22 01:38:04 +00:00
|
|
|
|
escape = true;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
} else if (ch === quote) {
|
|
|
|
|
|
this.index++;
|
|
|
|
|
|
this.tokens.push({
|
|
|
|
|
|
index: start,
|
|
|
|
|
|
text: rawString,
|
|
|
|
|
|
string: string,
|
|
|
|
|
|
json: true,
|
|
|
|
|
|
fn: function() { return string; }
|
2011-11-03 17:28:28 +00:00
|
|
|
|
});
|
2010-12-22 01:38:04 +00:00
|
|
|
|
return;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
string += ch;
|
|
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.index++;
|
2010-12-22 01:38:04 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.throwError('Unterminated quote', start);
|
2010-08-19 00:50:21 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
};
|
2010-01-06 00:36:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @constructor
|
|
|
|
|
|
*/
|
2013-10-07 16:58:37 +00:00
|
|
|
|
var Parser = function (lexer, $filter, options) {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.lexer = lexer;
|
|
|
|
|
|
this.$filter = $filter;
|
2013-10-07 16:58:37 +00:00
|
|
|
|
this.options = options;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
};
|
2011-04-05 18:00:26 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
Parser.ZERO = function () { return 0; };
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
Parser.prototype = {
|
|
|
|
|
|
constructor: Parser,
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
parse: function (text, json) {
|
|
|
|
|
|
this.text = text;
|
|
|
|
|
|
|
|
|
|
|
|
//TODO(i): strip all the obsolte json stuff from this file
|
|
|
|
|
|
this.json = json;
|
|
|
|
|
|
|
2013-10-07 16:58:37 +00:00
|
|
|
|
this.tokens = this.lexer.lex(text);
|
2013-08-21 00:19:49 +00:00
|
|
|
|
|
|
|
|
|
|
if (json) {
|
|
|
|
|
|
// The extra level of aliasing is here, just in case the lexer misses something, so that
|
|
|
|
|
|
// we prevent any accidental execution in JSON.
|
|
|
|
|
|
this.assignment = this.logicalOR;
|
|
|
|
|
|
|
|
|
|
|
|
this.functionCall =
|
|
|
|
|
|
this.fieldAccess =
|
|
|
|
|
|
this.objectIndex =
|
|
|
|
|
|
this.filterChain = function() {
|
|
|
|
|
|
this.throwError('is not valid json', {text: text, index: 0});
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var value = json ? this.primary() : this.statements();
|
|
|
|
|
|
|
|
|
|
|
|
if (this.tokens.length !== 0) {
|
|
|
|
|
|
this.throwError('is an unexpected token', this.tokens[0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
value.literal = !!value.literal;
|
|
|
|
|
|
value.constant = !!value.constant;
|
|
|
|
|
|
|
|
|
|
|
|
return value;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
primary: function () {
|
|
|
|
|
|
var primary;
|
|
|
|
|
|
if (this.expect('(')) {
|
|
|
|
|
|
primary = this.filterChain();
|
|
|
|
|
|
this.consume(')');
|
|
|
|
|
|
} else if (this.expect('[')) {
|
|
|
|
|
|
primary = this.arrayDeclaration();
|
|
|
|
|
|
} else if (this.expect('{')) {
|
|
|
|
|
|
primary = this.object();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
var token = this.expect();
|
|
|
|
|
|
primary = token.fn;
|
|
|
|
|
|
if (!primary) {
|
|
|
|
|
|
this.throwError('not a primary expression', token);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (token.json) {
|
|
|
|
|
|
primary.constant = true;
|
|
|
|
|
|
primary.literal = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var next, context;
|
|
|
|
|
|
while ((next = this.expect('(', '[', '.'))) {
|
|
|
|
|
|
if (next.text === '(') {
|
|
|
|
|
|
primary = this.functionCall(primary, context);
|
|
|
|
|
|
context = null;
|
|
|
|
|
|
} else if (next.text === '[') {
|
|
|
|
|
|
context = primary;
|
|
|
|
|
|
primary = this.objectIndex(primary);
|
|
|
|
|
|
} else if (next.text === '.') {
|
|
|
|
|
|
context = primary;
|
|
|
|
|
|
primary = this.fieldAccess(primary);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.throwError('IMPOSSIBLE');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return primary;
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
throwError: function(msg, token) {
|
|
|
|
|
|
throw $parseMinErr('syntax',
|
|
|
|
|
|
'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].',
|
|
|
|
|
|
token.text, msg, (token.index + 1), this.text, this.text.substring(token.index));
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
peekToken: function() {
|
|
|
|
|
|
if (this.tokens.length === 0)
|
|
|
|
|
|
throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
|
|
|
|
|
|
return this.tokens[0];
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
peek: function(e1, e2, e3, e4) {
|
|
|
|
|
|
if (this.tokens.length > 0) {
|
|
|
|
|
|
var token = this.tokens[0];
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var t = token.text;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if (t === e1 || t === e2 || t === e3 || t === e4 ||
|
2010-01-12 01:32:33 +00:00
|
|
|
|
(!e1 && !e2 && !e3 && !e4)) {
|
|
|
|
|
|
return token;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
expect: function(e1, e2, e3, e4){
|
|
|
|
|
|
var token = this.peek(e1, e2, e3, e4);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
if (token) {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if (this.json && !token.json) {
|
|
|
|
|
|
this.throwError('is not valid json', token);
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.tokens.shift();
|
2010-01-06 00:36:58 +00:00
|
|
|
|
return token;
|
|
|
|
|
|
}
|
2010-01-12 01:32:33 +00:00
|
|
|
|
return false;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
consume: function(e1){
|
|
|
|
|
|
if (!this.expect(e1)) {
|
|
|
|
|
|
this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
unaryFn: function(fn, right) {
|
2012-11-04 13:05:58 +00:00
|
|
|
|
return extend(function(self, locals) {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
return fn(self, locals, right);
|
2012-11-04 13:05:58 +00:00
|
|
|
|
}, {
|
|
|
|
|
|
constant:right.constant
|
|
|
|
|
|
});
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
ternaryFn: function(left, middle, right){
|
2013-04-23 00:42:34 +00:00
|
|
|
|
return extend(function(self, locals){
|
|
|
|
|
|
return left(self, locals) ? middle(self, locals) : right(self, locals);
|
|
|
|
|
|
}, {
|
|
|
|
|
|
constant: left.constant && middle.constant && right.constant
|
|
|
|
|
|
});
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2013-05-24 18:00:14 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
binaryFn: function(left, fn, right) {
|
2012-11-04 13:05:58 +00:00
|
|
|
|
return extend(function(self, locals) {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
return fn(self, locals, left, right);
|
2012-11-04 13:05:58 +00:00
|
|
|
|
}, {
|
|
|
|
|
|
constant:left.constant && right.constant
|
|
|
|
|
|
});
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
statements: function() {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var statements = [];
|
2013-08-21 00:19:49 +00:00
|
|
|
|
while (true) {
|
|
|
|
|
|
if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
|
|
|
|
|
|
statements.push(this.filterChain());
|
|
|
|
|
|
if (!this.expect(';')) {
|
2011-03-23 16:30:08 +00:00
|
|
|
|
// optimize for the common case where there is only one statement.
|
|
|
|
|
|
// TODO(size): maybe we should not support multiple statements?
|
2013-08-21 00:19:49 +00:00
|
|
|
|
return (statements.length === 1)
|
|
|
|
|
|
? statements[0]
|
|
|
|
|
|
: function(self, locals) {
|
|
|
|
|
|
var value;
|
|
|
|
|
|
for (var i = 0; i < statements.length; i++) {
|
|
|
|
|
|
var statement = statements[i];
|
|
|
|
|
|
if (statement) {
|
|
|
|
|
|
value = statement(self, locals);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return value;
|
|
|
|
|
|
};
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
filterChain: function() {
|
|
|
|
|
|
var left = this.expression();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
while (true) {
|
|
|
|
|
|
if ((token = this.expect('|'))) {
|
|
|
|
|
|
left = this.binaryFn(left, token.fn, this.filter());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
return left;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
filter: function() {
|
|
|
|
|
|
var token = this.expect();
|
|
|
|
|
|
var fn = this.$filter(token.text);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var argsFn = [];
|
2013-08-21 00:19:49 +00:00
|
|
|
|
while (true) {
|
|
|
|
|
|
if ((token = this.expect(':'))) {
|
|
|
|
|
|
argsFn.push(this.expression());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
var fnInvoke = function(self, locals, input) {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var args = [input];
|
2013-08-21 00:19:49 +00:00
|
|
|
|
for (var i = 0; i < argsFn.length; i++) {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
args.push(argsFn[i](self, locals));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-05-30 23:11:00 +00:00
|
|
|
|
return fn.apply(self, args);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
};
|
2011-10-07 18:27:49 +00:00
|
|
|
|
return function() {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
return fnInvoke;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
expression: function() {
|
|
|
|
|
|
return this.assignment();
|
|
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
assignment: function() {
|
|
|
|
|
|
var left = this.ternary();
|
2010-12-07 19:39:59 +00:00
|
|
|
|
var right;
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if ((token = this.expect('='))) {
|
2010-12-07 19:39:59 +00:00
|
|
|
|
if (!left.assign) {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.throwError('implies assignment but [' +
|
|
|
|
|
|
this.text.substring(0, token.index) + '] can not be assigned to', token);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
right = this.ternary();
|
|
|
|
|
|
return function(scope, locals) {
|
2013-04-25 15:54:28 +00:00
|
|
|
|
return left.assign(scope, right(scope, locals), locals);
|
2010-12-07 19:39:59 +00:00
|
|
|
|
};
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
return left;
|
|
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
ternary: function() {
|
|
|
|
|
|
var left = this.logicalOR();
|
2013-04-23 00:42:34 +00:00
|
|
|
|
var middle;
|
|
|
|
|
|
var token;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if ((token = this.expect('?'))) {
|
|
|
|
|
|
middle = this.ternary();
|
|
|
|
|
|
if ((token = this.expect(':'))) {
|
|
|
|
|
|
return this.ternaryFn(left, middle, this.ternary());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.throwError('expected :', token);
|
2013-04-23 00:42:34 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
} else {
|
2013-04-23 00:42:34 +00:00
|
|
|
|
return left;
|
|
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2013-05-24 18:00:14 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
logicalOR: function() {
|
|
|
|
|
|
var left = this.logicalAND();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
while (true) {
|
|
|
|
|
|
if ((token = this.expect('||'))) {
|
|
|
|
|
|
left = this.binaryFn(left, token.fn, this.logicalAND());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
return left;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
logicalAND: function() {
|
|
|
|
|
|
var left = this.equality();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if ((token = this.expect('&&'))) {
|
|
|
|
|
|
left = this.binaryFn(left, token.fn, this.logicalAND());
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2010-02-12 22:16:33 +00:00
|
|
|
|
return left;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
equality: function() {
|
|
|
|
|
|
var left = this.relational();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if ((token = this.expect('==','!=','===','!=='))) {
|
|
|
|
|
|
left = this.binaryFn(left, token.fn, this.equality());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-02-12 22:16:33 +00:00
|
|
|
|
return left;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
relational: function() {
|
|
|
|
|
|
var left = this.additive();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if ((token = this.expect('<', '>', '<=', '>='))) {
|
|
|
|
|
|
left = this.binaryFn(left, token.fn, this.relational());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-02-12 22:16:33 +00:00
|
|
|
|
return left;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
additive: function() {
|
|
|
|
|
|
var left = this.multiplicative();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
while ((token = this.expect('+','-'))) {
|
|
|
|
|
|
left = this.binaryFn(left, token.fn, this.multiplicative());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
return left;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
multiplicative: function() {
|
|
|
|
|
|
var left = this.unary();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
while ((token = this.expect('*','/','%'))) {
|
|
|
|
|
|
left = this.binaryFn(left, token.fn, this.unary());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
return left;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
unary: function() {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if (this.expect('+')) {
|
|
|
|
|
|
return this.primary();
|
|
|
|
|
|
} else if ((token = this.expect('-'))) {
|
|
|
|
|
|
return this.binaryFn(Parser.ZERO, token.fn, this.unary());
|
|
|
|
|
|
} else if ((token = this.expect('!'))) {
|
|
|
|
|
|
return this.unaryFn(token.fn, this.unary());
|
2010-01-06 00:36:58 +00:00
|
|
|
|
} else {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
return this.primary();
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
fieldAccess: function(object) {
|
|
|
|
|
|
var parser = this;
|
|
|
|
|
|
var field = this.expect().text;
|
2013-10-07 16:58:37 +00:00
|
|
|
|
var getter = getterFn(field, this.options, this.text);
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
return extend(function(scope, locals, self) {
|
|
|
|
|
|
return getter(self || object(scope, locals), locals);
|
|
|
|
|
|
}, {
|
|
|
|
|
|
assign: function(scope, value, locals) {
|
2013-10-07 16:58:37 +00:00
|
|
|
|
return setter(object(scope, locals), field, value, parser.text, parser.options);
|
2012-11-04 13:05:58 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
});
|
|
|
|
|
|
},
|
2012-01-24 10:33:35 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
objectIndex: function(obj) {
|
|
|
|
|
|
var parser = this;
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
var indexFn = this.expression();
|
|
|
|
|
|
this.consume(']');
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
return extend(function(self, locals) {
|
|
|
|
|
|
var o = obj(self, locals),
|
|
|
|
|
|
i = indexFn(self, locals),
|
|
|
|
|
|
v, p;
|
|
|
|
|
|
|
|
|
|
|
|
if (!o) return undefined;
|
|
|
|
|
|
v = ensureSafeObject(o[i], parser.text);
|
2013-10-07 16:58:37 +00:00
|
|
|
|
if (v && v.then && parser.options.unwrapPromises) {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
p = v;
|
|
|
|
|
|
if (!('$$v' in v)) {
|
|
|
|
|
|
p.$$v = undefined;
|
|
|
|
|
|
p.then(function(val) { p.$$v = val; });
|
2010-12-07 19:39:59 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
v = v.$$v;
|
|
|
|
|
|
}
|
|
|
|
|
|
return v;
|
|
|
|
|
|
}, {
|
|
|
|
|
|
assign: function(self, value, locals) {
|
|
|
|
|
|
var key = indexFn(self, locals);
|
|
|
|
|
|
// prevent overwriting of Function.constructor which would break ensureSafeObject check
|
|
|
|
|
|
var safe = ensureSafeObject(obj(self, locals), parser.text);
|
|
|
|
|
|
return safe[key] = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
functionCall: function(fn, contextGetter) {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var argsFn = [];
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if (this.peekToken().text !== ')') {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
do {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
argsFn.push(this.expression());
|
|
|
|
|
|
} while (this.expect(','));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.consume(')');
|
|
|
|
|
|
|
|
|
|
|
|
var parser = this;
|
2012-01-24 10:33:35 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
return function(scope, locals) {
|
|
|
|
|
|
var args = [];
|
|
|
|
|
|
var context = contextGetter ? contextGetter(scope, locals) : scope;
|
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < argsFn.length; i++) {
|
2013-04-25 15:54:28 +00:00
|
|
|
|
args.push(argsFn[i](scope, locals));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2013-04-25 15:54:28 +00:00
|
|
|
|
var fnPtr = fn(scope, locals, context) || noop;
|
2013-08-09 21:47:13 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
ensureSafeObject(fnPtr, parser.text);
|
2013-08-09 21:47:13 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
// IE stupidity! (IE doesn't have apply for some native functions)
|
2013-08-15 12:14:54 +00:00
|
|
|
|
var v = fnPtr.apply
|
2013-08-21 00:19:49 +00:00
|
|
|
|
? fnPtr.apply(context, args)
|
|
|
|
|
|
: fnPtr(args[0], args[1], args[2], args[3], args[4]);
|
2013-08-15 12:14:54 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
return ensureSafeObject(v, parser.text);
|
2010-01-06 00:36:58 +00:00
|
|
|
|
};
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
|
// This is used with json array declaration
|
2013-08-21 00:19:49 +00:00
|
|
|
|
arrayDeclaration: function () {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var elementFns = [];
|
2012-11-04 13:05:58 +00:00
|
|
|
|
var allConstant = true;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if (this.peekToken().text !== ']') {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
do {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
var elementFn = this.expression();
|
2012-11-04 13:05:58 +00:00
|
|
|
|
elementFns.push(elementFn);
|
|
|
|
|
|
if (!elementFn.constant) {
|
|
|
|
|
|
allConstant = false;
|
|
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
} while (this.expect(','));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.consume(']');
|
|
|
|
|
|
|
|
|
|
|
|
return extend(function(self, locals) {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var array = [];
|
2013-08-21 00:19:49 +00:00
|
|
|
|
for (var i = 0; i < elementFns.length; i++) {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
array.push(elementFns[i](self, locals));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
return array;
|
2012-11-04 13:05:58 +00:00
|
|
|
|
}, {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
literal: true,
|
|
|
|
|
|
constant: allConstant
|
2012-11-04 13:05:58 +00:00
|
|
|
|
});
|
2013-08-21 00:19:49 +00:00
|
|
|
|
},
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
object: function () {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var keyValues = [];
|
2012-11-04 13:05:58 +00:00
|
|
|
|
var allConstant = true;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if (this.peekToken().text !== '}') {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
do {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
var token = this.expect(),
|
2010-10-15 22:28:58 +00:00
|
|
|
|
key = token.string || token.text;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.consume(':');
|
|
|
|
|
|
var value = this.expression();
|
|
|
|
|
|
keyValues.push({key: key, value: value});
|
2012-11-04 13:05:58 +00:00
|
|
|
|
if (!value.constant) {
|
|
|
|
|
|
allConstant = false;
|
|
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
} while (this.expect(','));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
this.consume('}');
|
|
|
|
|
|
|
|
|
|
|
|
return extend(function(self, locals) {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var object = {};
|
2013-08-21 00:19:49 +00:00
|
|
|
|
for (var i = 0; i < keyValues.length; i++) {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var keyValue = keyValues[i];
|
2013-05-07 04:56:51 +00:00
|
|
|
|
object[keyValue.key] = keyValue.value(self, locals);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
return object;
|
2012-11-04 13:05:58 +00:00
|
|
|
|
}, {
|
2013-08-21 00:19:49 +00:00
|
|
|
|
literal: true,
|
|
|
|
|
|
constant: allConstant
|
2012-11-04 13:05:58 +00:00
|
|
|
|
});
|
2013-10-07 16:58:37 +00:00
|
|
|
|
}
|
2013-08-21 00:19:49 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2010-10-15 22:28:58 +00:00
|
|
|
|
|
2011-03-23 16:33:29 +00:00
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
|
|
// Parser helper functions
|
|
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
|
|
|
2013-10-07 16:58:37 +00:00
|
|
|
|
function setter(obj, path, setValue, fullExp, options) {
|
|
|
|
|
|
//needed?
|
|
|
|
|
|
options = options || {};
|
|
|
|
|
|
|
2013-06-24 21:14:54 +00:00
|
|
|
|
var element = path.split('.'), key;
|
2011-03-23 16:33:29 +00:00
|
|
|
|
for (var i = 0; element.length > 1; i++) {
|
2013-06-24 21:14:54 +00:00
|
|
|
|
key = ensureSafeMemberName(element.shift(), fullExp);
|
2011-03-23 16:33:29 +00:00
|
|
|
|
var propertyObj = obj[key];
|
|
|
|
|
|
if (!propertyObj) {
|
|
|
|
|
|
propertyObj = {};
|
|
|
|
|
|
obj[key] = propertyObj;
|
|
|
|
|
|
}
|
|
|
|
|
|
obj = propertyObj;
|
2013-10-07 16:58:37 +00:00
|
|
|
|
if (obj.then && options.unwrapPromises) {
|
|
|
|
|
|
promiseWarning(fullExp);
|
2013-06-03 03:04:12 +00:00
|
|
|
|
if (!("$$v" in obj)) {
|
|
|
|
|
|
(function(promise) {
|
|
|
|
|
|
promise.then(function(val) { promise.$$v = val; }); }
|
|
|
|
|
|
)(obj);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (obj.$$v === undefined) {
|
|
|
|
|
|
obj.$$v = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
obj = obj.$$v;
|
|
|
|
|
|
}
|
2011-03-23 16:33:29 +00:00
|
|
|
|
}
|
2013-06-24 21:14:54 +00:00
|
|
|
|
key = ensureSafeMemberName(element.shift(), fullExp);
|
|
|
|
|
|
obj[key] = setValue;
|
2011-03-23 16:33:29 +00:00
|
|
|
|
return setValue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-01-24 10:42:20 +00:00
|
|
|
|
var getterFnCache = {};
|
2011-03-23 16:33:29 +00:00
|
|
|
|
|
2012-04-27 22:20:54 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Implementation of the "Black Hole" variant from:
|
|
|
|
|
|
* - http://jsperf.com/angularjs-parse-getter/4
|
|
|
|
|
|
* - http://jsperf.com/path-evaluation-simplified/7
|
|
|
|
|
|
*/
|
2013-10-07 16:58:37 +00:00
|
|
|
|
function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
|
2013-06-24 21:14:54 +00:00
|
|
|
|
ensureSafeMemberName(key0, fullExp);
|
|
|
|
|
|
ensureSafeMemberName(key1, fullExp);
|
|
|
|
|
|
ensureSafeMemberName(key2, fullExp);
|
|
|
|
|
|
ensureSafeMemberName(key3, fullExp);
|
|
|
|
|
|
ensureSafeMemberName(key4, fullExp);
|
2013-10-07 16:58:37 +00:00
|
|
|
|
|
|
|
|
|
|
return !options.unwrapPromises
|
|
|
|
|
|
? function cspSafeGetter(scope, locals) {
|
|
|
|
|
|
var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
|
|
|
|
|
|
|
|
|
|
|
|
if (pathVal === null || pathVal === undefined) return pathVal;
|
|
|
|
|
|
pathVal = pathVal[key0];
|
|
|
|
|
|
|
|
|
|
|
|
if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
|
|
|
|
|
|
pathVal = pathVal[key1];
|
|
|
|
|
|
|
|
|
|
|
|
if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
|
|
|
|
|
|
pathVal = pathVal[key2];
|
|
|
|
|
|
|
|
|
|
|
|
if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
|
|
|
|
|
|
pathVal = pathVal[key3];
|
|
|
|
|
|
|
|
|
|
|
|
if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
|
|
|
|
|
|
pathVal = pathVal[key4];
|
|
|
|
|
|
|
|
|
|
|
|
return pathVal;
|
|
|
|
|
|
}
|
|
|
|
|
|
: function cspSafePromiseEnabledGetter(scope, locals) {
|
|
|
|
|
|
var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
|
|
|
|
|
|
promise;
|
|
|
|
|
|
|
|
|
|
|
|
if (pathVal === null || pathVal === undefined) return pathVal;
|
|
|
|
|
|
|
|
|
|
|
|
pathVal = pathVal[key0];
|
|
|
|
|
|
if (pathVal && pathVal.then) {
|
|
|
|
|
|
promiseWarning(fullExp);
|
|
|
|
|
|
if (!("$$v" in pathVal)) {
|
|
|
|
|
|
promise = pathVal;
|
|
|
|
|
|
promise.$$v = undefined;
|
|
|
|
|
|
promise.then(function(val) { promise.$$v = val; });
|
|
|
|
|
|
}
|
|
|
|
|
|
pathVal = pathVal.$$v;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
|
|
|
|
|
|
|
|
|
|
|
|
pathVal = pathVal[key1];
|
|
|
|
|
|
if (pathVal && pathVal.then) {
|
|
|
|
|
|
promiseWarning(fullExp);
|
|
|
|
|
|
if (!("$$v" in pathVal)) {
|
|
|
|
|
|
promise = pathVal;
|
|
|
|
|
|
promise.$$v = undefined;
|
|
|
|
|
|
promise.then(function(val) { promise.$$v = val; });
|
|
|
|
|
|
}
|
|
|
|
|
|
pathVal = pathVal.$$v;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
|
|
|
|
|
|
|
|
|
|
|
|
pathVal = pathVal[key2];
|
|
|
|
|
|
if (pathVal && pathVal.then) {
|
|
|
|
|
|
promiseWarning(fullExp);
|
|
|
|
|
|
if (!("$$v" in pathVal)) {
|
|
|
|
|
|
promise = pathVal;
|
|
|
|
|
|
promise.$$v = undefined;
|
|
|
|
|
|
promise.then(function(val) { promise.$$v = val; });
|
|
|
|
|
|
}
|
|
|
|
|
|
pathVal = pathVal.$$v;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
|
|
|
|
|
|
|
|
|
|
|
|
pathVal = pathVal[key3];
|
|
|
|
|
|
if (pathVal && pathVal.then) {
|
|
|
|
|
|
promiseWarning(fullExp);
|
|
|
|
|
|
if (!("$$v" in pathVal)) {
|
|
|
|
|
|
promise = pathVal;
|
|
|
|
|
|
promise.$$v = undefined;
|
|
|
|
|
|
promise.then(function(val) { promise.$$v = val; });
|
|
|
|
|
|
}
|
|
|
|
|
|
pathVal = pathVal.$$v;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
|
|
|
|
|
|
|
|
|
|
|
|
pathVal = pathVal[key4];
|
|
|
|
|
|
if (pathVal && pathVal.then) {
|
|
|
|
|
|
promiseWarning(fullExp);
|
|
|
|
|
|
if (!("$$v" in pathVal)) {
|
|
|
|
|
|
promise = pathVal;
|
|
|
|
|
|
promise.$$v = undefined;
|
|
|
|
|
|
promise.then(function(val) { promise.$$v = val; });
|
|
|
|
|
|
}
|
|
|
|
|
|
pathVal = pathVal.$$v;
|
|
|
|
|
|
}
|
|
|
|
|
|
return pathVal;
|
|
|
|
|
|
}
|
2013-05-07 04:56:51 +00:00
|
|
|
|
}
|
2012-04-27 22:20:54 +00:00
|
|
|
|
|
2013-10-07 16:58:37 +00:00
|
|
|
|
function getterFn(path, options, fullExp) {
|
2013-10-05 09:49:09 +00:00
|
|
|
|
// Check whether the cache has this getter already.
|
|
|
|
|
|
// We can use hasOwnProperty directly on the cache because we ensure,
|
|
|
|
|
|
// see below, that the cache never stores a path called 'hasOwnProperty'
|
2012-03-08 01:07:25 +00:00
|
|
|
|
if (getterFnCache.hasOwnProperty(path)) {
|
|
|
|
|
|
return getterFnCache[path];
|
|
|
|
|
|
}
|
2011-03-23 16:33:29 +00:00
|
|
|
|
|
2012-04-27 22:20:54 +00:00
|
|
|
|
var pathKeys = path.split('.'),
|
|
|
|
|
|
pathKeysLength = pathKeys.length,
|
|
|
|
|
|
fn;
|
|
|
|
|
|
|
2013-10-07 16:58:37 +00:00
|
|
|
|
if (options.csp) {
|
2012-04-27 22:20:54 +00:00
|
|
|
|
fn = (pathKeysLength < 6)
|
2013-10-07 16:58:37 +00:00
|
|
|
|
? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, options)
|
2012-04-27 22:20:54 +00:00
|
|
|
|
: function(scope, locals) {
|
2013-05-07 04:56:51 +00:00
|
|
|
|
var i = 0, val;
|
2012-04-27 22:20:54 +00:00
|
|
|
|
do {
|
|
|
|
|
|
val = cspSafeGetterFn(
|
2013-10-07 16:58:37 +00:00
|
|
|
|
pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], fullExp, options
|
2012-04-27 22:20:54 +00:00
|
|
|
|
)(scope, locals);
|
2012-04-14 17:39:24 +00:00
|
|
|
|
|
2012-04-27 22:20:54 +00:00
|
|
|
|
locals = undefined; // clear after first iteration
|
2012-04-14 17:39:24 +00:00
|
|
|
|
scope = val;
|
2012-04-27 22:20:54 +00:00
|
|
|
|
} while (i < pathKeysLength);
|
2012-04-14 17:39:24 +00:00
|
|
|
|
return val;
|
|
|
|
|
|
}
|
2012-04-27 22:20:54 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
var code = 'var l, fn, p;\n';
|
|
|
|
|
|
forEach(pathKeys, function(key, index) {
|
2013-06-24 21:14:54 +00:00
|
|
|
|
ensureSafeMemberName(key, fullExp);
|
2012-04-14 17:39:24 +00:00
|
|
|
|
code += 'if(s === null || s === undefined) return s;\n' +
|
2012-04-27 22:20:54 +00:00
|
|
|
|
'l=s;\n' +
|
|
|
|
|
|
's='+ (index
|
|
|
|
|
|
// we simply dereference 's' on any .dot notation
|
|
|
|
|
|
? 's'
|
|
|
|
|
|
// but if we are first then we check locals first, and if so read it first
|
|
|
|
|
|
: '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
|
2013-10-07 16:58:37 +00:00
|
|
|
|
(options.unwrapPromises
|
|
|
|
|
|
? 'if (s && s.then) {\n' +
|
|
|
|
|
|
' pw("' + fullExp.replace(/\"/g, '\\"') + '");\n' +
|
|
|
|
|
|
' if (!("$$v" in s)) {\n' +
|
|
|
|
|
|
' p=s;\n' +
|
|
|
|
|
|
' p.$$v = undefined;\n' +
|
|
|
|
|
|
' p.then(function(v) {p.$$v=v;});\n' +
|
|
|
|
|
|
'}\n' +
|
|
|
|
|
|
' s=s.$$v\n' +
|
|
|
|
|
|
'}\n'
|
|
|
|
|
|
: '');
|
2012-04-27 22:20:54 +00:00
|
|
|
|
});
|
|
|
|
|
|
code += 'return s;';
|
2013-10-07 16:58:37 +00:00
|
|
|
|
|
|
|
|
|
|
var evaledFnGetter = Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning
|
|
|
|
|
|
evaledFnGetter.toString = function() { return code; };
|
|
|
|
|
|
fn = function(scope, locals) {
|
|
|
|
|
|
return evaledFnGetter(scope, locals, promiseWarning);
|
|
|
|
|
|
};
|
2012-04-27 22:20:54 +00:00
|
|
|
|
}
|
2011-03-23 16:33:29 +00:00
|
|
|
|
|
2013-10-05 09:49:09 +00:00
|
|
|
|
// Only cache the value if it's not going to mess up the cache object
|
|
|
|
|
|
// This is more performant that using Object.prototype.hasOwnProperty.call
|
|
|
|
|
|
if (path !== 'hasOwnProperty') {
|
|
|
|
|
|
getterFnCache[path] = fn;
|
|
|
|
|
|
}
|
|
|
|
|
|
return fn;
|
2011-03-23 16:33:29 +00:00
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
|
2011-03-23 16:33:29 +00:00
|
|
|
|
///////////////////////////////////
|
2010-10-15 22:28:58 +00:00
|
|
|
|
|
2012-02-28 22:29:58 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @ngdoc function
|
2012-06-12 06:49:24 +00:00
|
|
|
|
* @name ng.$parse
|
2012-02-28 22:29:58 +00:00
|
|
|
|
* @function
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
*
|
2012-03-01 19:28:50 +00:00
|
|
|
|
* Converts Angular {@link guide/expression expression} into a function.
|
2012-02-28 22:29:58 +00:00
|
|
|
|
*
|
|
|
|
|
|
* <pre>
|
|
|
|
|
|
* var getter = $parse('user.name');
|
|
|
|
|
|
* var setter = getter.assign;
|
|
|
|
|
|
* var context = {user:{name:'angular'}};
|
|
|
|
|
|
* var locals = {user:{name:'local'}};
|
|
|
|
|
|
*
|
|
|
|
|
|
* expect(getter(context)).toEqual('angular');
|
|
|
|
|
|
* setter(context, 'newValue');
|
|
|
|
|
|
* expect(context.user.name).toEqual('newValue');
|
|
|
|
|
|
* expect(getter(context, locals)).toEqual('local');
|
|
|
|
|
|
* </pre>
|
|
|
|
|
|
*
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} expression String expression to compile.
|
|
|
|
|
|
* @returns {function(context, locals)} a function which represents the compiled expression:
|
|
|
|
|
|
*
|
2012-11-04 22:36:35 +00:00
|
|
|
|
* * `context` – `{object}` – an object against which any expressions embedded in the strings
|
2013-03-21 19:09:47 +00:00
|
|
|
|
* are evaluated against (typically a scope object).
|
2012-11-04 22:36:35 +00:00
|
|
|
|
* * `locals` – `{object=}` – local variables context object, useful for overriding values in
|
|
|
|
|
|
* `context`.
|
2012-02-28 22:29:58 +00:00
|
|
|
|
*
|
2012-11-04 13:05:58 +00:00
|
|
|
|
* 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.
|
2012-02-28 22:29:58 +00:00
|
|
|
|
*
|
|
|
|
|
|
*/
|
2013-10-07 16:58:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @ngdoc object
|
|
|
|
|
|
* @name ng.$parseProvider
|
|
|
|
|
|
* @function
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
* `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} service.
|
|
|
|
|
|
*/
|
2011-11-03 20:53:37 +00:00
|
|
|
|
function $ParseProvider() {
|
|
|
|
|
|
var cache = {};
|
2013-10-07 16:58:37 +00:00
|
|
|
|
|
|
|
|
|
|
var $parseOptions = {
|
|
|
|
|
|
csp: false,
|
|
|
|
|
|
unwrapPromises: false,
|
|
|
|
|
|
logPromiseWarnings: true
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @ngdoc method
|
|
|
|
|
|
* @name ng.$parseProvider#unwrapPromises
|
|
|
|
|
|
* @methodOf ng.$parseProvider
|
|
|
|
|
|
* @description
|
|
|
|
|
|
*
|
|
|
|
|
|
* **This feature is deprecated, see deprecation notes below for more info**
|
|
|
|
|
|
*
|
|
|
|
|
|
* If set to true (default is false), $parse will unwrap promises automatically when a promise is found at any part of
|
|
|
|
|
|
* the expression. In other words, if set to true, the expression will always result in a non-promise value.
|
|
|
|
|
|
*
|
|
|
|
|
|
* While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, the fulfillment value
|
|
|
|
|
|
* is used in place of the promise while evaluating the expression.
|
|
|
|
|
|
*
|
|
|
|
|
|
* **Deprecation notice**
|
|
|
|
|
|
*
|
|
|
|
|
|
* This is a feature that didn't prove to be wildly useful or popular, primarily because of the dichotomy between data
|
|
|
|
|
|
* access in templates (accessed as raw values) and controller code (accessed as promises).
|
|
|
|
|
|
*
|
|
|
|
|
|
* In most code we ended up resolving promises manually in controllers anyway and thus unifying the model access there.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Other downsides of automatic promise unwrapping:
|
|
|
|
|
|
*
|
|
|
|
|
|
* - when building components it's often desirable to receive the raw promises
|
|
|
|
|
|
* - adds complexity and slows down expression evaluation
|
|
|
|
|
|
* - makes expression code pre-generation unattractive due to the amount of code that needs to be generated
|
|
|
|
|
|
* - makes IDE auto-completion and tool support hard
|
|
|
|
|
|
*
|
|
|
|
|
|
* **Warning Logs**
|
|
|
|
|
|
*
|
|
|
|
|
|
* If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a promise (to reduce
|
|
|
|
|
|
* the noise, each expression is logged only once). To disable this logging use
|
|
|
|
|
|
* `$parseProvider.logPromiseWarnings(false)` api.
|
|
|
|
|
|
*
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {boolean=} value New value.
|
|
|
|
|
|
* @returns {boolean|self} Returns the current setting when used as getter and self if used as setter.
|
|
|
|
|
|
*/
|
|
|
|
|
|
this.unwrapPromises = function(value) {
|
|
|
|
|
|
if (isDefined(value)) {
|
|
|
|
|
|
$parseOptions.unwrapPromises = !!value;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return $parseOptions.unwrapPromises;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @ngdoc method
|
|
|
|
|
|
* @name ng.$parseProvider#logPromiseWarnings
|
|
|
|
|
|
* @methodOf ng.$parseProvider
|
|
|
|
|
|
* @description
|
|
|
|
|
|
*
|
|
|
|
|
|
* Controls whether Angular should log a warning on any encounter of a promise in an expression.
|
|
|
|
|
|
*
|
|
|
|
|
|
* The default is set to `true`.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {boolean=} value New value.
|
|
|
|
|
|
* @returns {boolean|self} Returns the current setting when used as getter and self if used as setter.
|
|
|
|
|
|
*/
|
|
|
|
|
|
this.logPromiseWarnings = function(value) {
|
|
|
|
|
|
if (isDefined(value)) {
|
|
|
|
|
|
$parseOptions.logPromiseWarnings = value;
|
|
|
|
|
|
return this;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return $parseOptions.logPromiseWarnings;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) {
|
|
|
|
|
|
$parseOptions.csp = $sniffer.csp;
|
|
|
|
|
|
|
|
|
|
|
|
promiseWarning = function promiseWarningFn(fullExp) {
|
|
|
|
|
|
if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return;
|
|
|
|
|
|
promiseWarningCache[fullExp] = true;
|
|
|
|
|
|
$log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' +
|
|
|
|
|
|
'Automatic unwrapping of promises in Angular expressions is deprecated.');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2011-11-03 20:53:37 +00:00
|
|
|
|
return function(exp) {
|
2013-10-05 09:49:09 +00:00
|
|
|
|
var parsedExpression;
|
2013-10-05 10:13:24 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
switch (typeof exp) {
|
2011-11-03 20:53:37 +00:00
|
|
|
|
case 'string':
|
2013-10-07 16:58:37 +00:00
|
|
|
|
|
2013-08-21 00:19:49 +00:00
|
|
|
|
if (cache.hasOwnProperty(exp)) {
|
|
|
|
|
|
return cache[exp];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-10-07 16:58:37 +00:00
|
|
|
|
var lexer = new Lexer($parseOptions);
|
|
|
|
|
|
var parser = new Parser(lexer, $filter, $parseOptions);
|
2013-10-05 09:49:09 +00:00
|
|
|
|
parsedExpression = parser.parse(exp, false);
|
|
|
|
|
|
|
|
|
|
|
|
if (exp !== 'hasOwnProperty') {
|
|
|
|
|
|
// Only cache the value if it's not going to mess up the cache object
|
|
|
|
|
|
// This is more performant that using Object.prototype.hasOwnProperty.call
|
|
|
|
|
|
cache[exp] = parsedExpression;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return parsedExpression;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
|
2011-11-03 20:53:37 +00:00
|
|
|
|
case 'function':
|
|
|
|
|
|
return exp;
|
2013-08-21 00:19:49 +00:00
|
|
|
|
|
2011-11-03 20:53:37 +00:00
|
|
|
|
default:
|
|
|
|
|
|
return noop;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}];
|
2011-03-23 16:33:29 +00:00
|
|
|
|
}
|