Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 64 additions & 19 deletions src/ng/parse.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

var $parseMinErr = minErr('$parse');
var log;
var promiseWarningCache = {};
var promiseWarning;

Expand Down Expand Up @@ -313,7 +314,7 @@ Lexer.prototype = {
} else {
var getter = getterFn(ident, this.options, this.text);
token.fn = extend(function(self, locals) {
return (getter(self, locals));
return (getter(self, locals, log));
}, {
assign: function(self, value) {
return setter(self, ident, value, parser.text, parser.options);
Expand Down Expand Up @@ -707,7 +708,7 @@ Parser.prototype = {
var getter = getterFn(field, this.options, this.text);

return extend(function(scope, locals, self) {
return getter(self || object(scope, locals));
return getter(self || object(scope, locals, log));
}, {
assign: function(scope, value, locals) {
return setter(object(scope, locals), field, value, parser.text, parser.options);
Expand Down Expand Up @@ -875,6 +876,17 @@ function setter(obj, path, setValue, fullExp, options) {

var getterFnCache = {};

/**
* Logs a warning when a key cannot be resolved in an expression because the parent object is undefined.
* @param key
* @param fullExp
* @returns {undefined}
*/
function logAndReturnUndefined(key, fullExp){
log.warn('[$parse] Key `' + key + '` cannot be resolved in expression `' + fullExp + '` because parent object is null.');
return undefined;
}

/**
* Implementation of the "Black Hole" variant from:
* - http://jsperf.com/angularjs-parse-getter/4
Expand All @@ -893,21 +905,32 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {

if (pathVal == null) return pathVal;
pathVal = pathVal[key0];
if (pathVal == null) {
return logAndReturnUndefined(key0, fullExp);
}

if (!key1) return pathVal;
if (pathVal == null) return undefined;
if (pathVal == null) {
return logAndReturnUndefined(key1, fullExp);
}
pathVal = pathVal[key1];

if (!key2) return pathVal;
if (pathVal == null) return undefined;
if (pathVal == null) {
return logAndReturnUndefined(key2, fullExp);
}
pathVal = pathVal[key2];

if (!key3) return pathVal;
if (pathVal == null) return undefined;
if (pathVal == null) {
return logAndReturnUndefined(key3, fullExp);
}
pathVal = pathVal[key3];

if (!key4) return pathVal;
if (pathVal == null) return undefined;
if (pathVal == null) {
return logAndReturnUndefined(key4, fullExp);
}
pathVal = pathVal[key4];

return pathVal;
Expand All @@ -916,7 +939,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
promise;

if (pathVal == null) return pathVal;
if (pathVal == null) {
return logAndReturnUndefined(key0, fullExp);
}

pathVal = pathVal[key0];
if (pathVal && pathVal.then) {
Expand All @@ -930,7 +955,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
}

if (!key1) return pathVal;
if (pathVal == null) return undefined;
if (pathVal == null) {
return logAndReturnUndefined(key1, fullExp);
}
pathVal = pathVal[key1];
if (pathVal && pathVal.then) {
promiseWarning(fullExp);
Expand All @@ -943,7 +970,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
}

if (!key2) return pathVal;
if (pathVal == null) return undefined;
if (pathVal == null) {
return logAndReturnUndefined(key2, fullExp);
}
pathVal = pathVal[key2];
if (pathVal && pathVal.then) {
promiseWarning(fullExp);
Expand All @@ -956,7 +985,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
}

if (!key3) return pathVal;
if (pathVal == null) return undefined;
if (pathVal == null) {
return logAndReturnUndefined(key3, fullExp);
}
pathVal = pathVal[key3];
if (pathVal && pathVal.then) {
promiseWarning(fullExp);
Expand All @@ -969,7 +1000,9 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) {
}

if (!key4) return pathVal;
if (pathVal == null) return undefined;
if (pathVal == null) {
return logAndReturnUndefined(key4, fullExp);
}
pathVal = pathVal[key4];
if (pathVal && pathVal.then) {
promiseWarning(fullExp);
Expand All @@ -988,8 +1021,10 @@ function simpleGetterFn1(key0, fullExp) {
ensureSafeMemberName(key0, fullExp);

return function simpleGetterFn1(scope, locals) {
if (scope == null) return undefined;
return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0];
if (scope == null) {
return logAndReturnUndefined(key0, fullExp);
}
return((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0];
};
}

Expand All @@ -998,9 +1033,14 @@ function simpleGetterFn2(key0, key1, fullExp) {
ensureSafeMemberName(key1, fullExp);

return function simpleGetterFn2(scope, locals) {
if (scope == null) return undefined;
scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0];
return scope == null ? undefined : scope[key1];
if (scope == null) {
return logAndReturnUndefined(key0, fullExp);
}
var pathVal = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0];
if (pathVal == null) {
return logAndReturnUndefined(key1, fullExp);
}
return pathVal[key1];
};
}

Expand Down Expand Up @@ -1043,7 +1083,10 @@ function getterFn(path, options, fullExp) {
var code = 'var p;\n';
forEach(pathKeys, function(key, index) {
ensureSafeMemberName(key, fullExp);
code += 'if(s == null) return undefined;\n' +
code += 'if(s == null) {\n' +
' l.warn("[$parse] Key `' + key + '` cannot be resolved in expression `' + fullExp.replace(/(["\r\n])/g, '\\$1') + '` because parent object is null.");\n' +
' return undefined;\n' +
'}\n' +
's='+ (index
// we simply dereference 's' on any .dot notation
? 's'
Expand All @@ -1064,11 +1107,11 @@ function getterFn(path, options, fullExp) {
code += 'return s;';

/* jshint -W054 */
var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning
var evaledFnGetter = new Function('s', 'k', 'l', 'pw', code); // s=scope, k=locals, l=log, pw=promiseWarning
/* jshint +W054 */
evaledFnGetter.toString = valueFn(code);
fn = options.unwrapPromises ? function(scope, locals) {
return evaledFnGetter(scope, locals, promiseWarning);
return evaledFnGetter(scope, locals, log, promiseWarning);
} : evaledFnGetter;
}

Expand Down Expand Up @@ -1226,6 +1269,8 @@ function $ParseProvider() {
this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) {
$parseOptions.csp = $sniffer.csp;

log = $log;

promiseWarning = function promiseWarningFn(fullExp) {
if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return;
promiseWarningCache[fullExp] = true;
Expand Down
4 changes: 3 additions & 1 deletion test/BinderSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,10 @@ describe('Binder', function() {
module(function($exceptionHandlerProvider){
$exceptionHandlerProvider.mode('log');
});
inject(function($rootScope, $exceptionHandler, $compile) {
inject(function($rootScope, $exceptionHandler, $compile, $log) {
$compile('<div attr="before {{error.throw()}} after"></div>', null, true)($rootScope);
expect($log.warn.logs.pop()).toMatch(/\[\$parse\] Key `.+` cannot be resolved in expression `.+` because parent object is null\./);
expect($log.warn.logs).toEqual([]);
var errorLogs = $exceptionHandler.errors;
var count = 0;

Expand Down
5 changes: 4 additions & 1 deletion test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3039,10 +3039,13 @@ describe('$compile', function() {
};
});
});
inject(function($templateCache, $compile, $rootScope) {
inject(function($templateCache, $compile, $rootScope, $log) {
$templateCache.put('main.html', '<span>template:{{mainCtrl.name}} <div ng-transclude></div></span>');
element = $compile('<div main>transclude:{{mainCtrl.name}}</div>')($rootScope);
$rootScope.$apply();
expect($log.warn.logs.pop()).toMatch(/\[\$parse\] Key `.+` cannot be resolved in expression `.+` because parent object is null\./);
expect($log.warn.logs.pop()).toMatch(/\[\$parse\] Key `.+` cannot be resolved in expression `.+` because parent object is null\./);
expect($log.warn.logs).toEqual([]);
expect(element.text()).toBe('template:lucas transclude:');
});
});
Expand Down
14 changes: 11 additions & 3 deletions test/ng/interpolateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

describe('$interpolate', function() {

var UNDEFINED_WARNING_REGEXP = /\[\$parse\] Key `.+` cannot be resolved in expression `.+` because parent object is null\./;

it('should return a function when there are no bindings and textOnly is undefined',
inject(function($interpolate) {
expect(typeof $interpolate('some text')).toBe('function');
Expand All @@ -13,11 +15,13 @@ describe('$interpolate', function() {
expect($interpolate('some text', true)).toBeUndefined();
}));

it('should suppress falsy objects', inject(function($interpolate) {
it('should suppress falsy objects', inject(function($interpolate, $log) {
expect($interpolate('{{undefined}}')()).toEqual('');
expect($interpolate('{{undefined+undefined}}')()).toEqual('');
expect($interpolate('{{null}}')()).toEqual('');
expect($interpolate('{{a.b}}')()).toEqual('');
expect($log.warn.logs.pop()).toMatch(/\[\$parse\] Key `.+` cannot be resolved in expression `.+` because parent object is null\./);
expect($log.warn.logs).toEqual([]);
}));

it('should jsonify objects', inject(function($interpolate) {
Expand Down Expand Up @@ -56,8 +60,10 @@ describe('$interpolate', function() {
}));


it('should ignore undefined model', inject(function($interpolate) {
it('should ignore undefined model', inject(function($interpolate, $log) {
expect($interpolate("Hello {{'World' + foo}}")()).toEqual('Hello World');
expect($log.warn.logs.pop()).toMatch(UNDEFINED_WARNING_REGEXP);
expect($log.warn.logs).toEqual([]);
}));


Expand Down Expand Up @@ -215,8 +221,10 @@ describe('$interpolate', function() {
"when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce");
}));

it('should interpolate a multi-part expression when isTrustedContext is false', inject(function($interpolate) {
it('should interpolate a multi-part expression when isTrustedContext is false', inject(function($interpolate, $log) {
expect($interpolate('some/{{id}}')()).toEqual('some/');
expect($log.warn.logs.pop()).toMatch(UNDEFINED_WARNING_REGEXP);
expect($log.warn.logs).toEqual([]);
expect($interpolate('some/{{id}}')({id: 1})).toEqual('some/1');
expect($interpolate('{{foo}}{{bar}}')({foo: 1, bar: 2})).toEqual('12');
}));
Expand Down
Loading