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

Commit cf43ccd

Browse files
fix(minErr): stringify non-JSON compatible objects in error messages
Fix the JSON stringification to output a more meaningful string when an object cannot be normally converted to a JSON string, such as when the object contains cyclic references that would cause `JSON.stringify()` to throw an error. Closes #10085
1 parent a9352c1 commit cf43ccd

7 files changed

+58
-20
lines changed

angularFiles.js

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var angularFiles = {
55
'src/minErr.js',
66
'src/Angular.js',
77
'src/loader.js',
8+
'src/stringify.js',
89
'src/AngularPublic.js',
910
'src/jqLite.js',
1011
'src/apis.js',
@@ -73,6 +74,7 @@ var angularFiles = {
7374
],
7475

7576
'angularLoader': [
77+
'stringify.js',
7678
'src/minErr.js',
7779
'src/loader.js'
7880
],

src/.jshintrc

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"angularModule": false,
2020
"nodeName_": false,
2121
"uid": false,
22+
"toDebugString": false,
2223

2324
"REGEX_STRING_REGEXP" : false,
2425
"lowercase": false,

src/minErr.js

+3-20
Original file line numberDiff line numberDiff line change
@@ -37,31 +37,14 @@ function minErr(module, ErrorConstructor) {
3737
prefix = '[' + (module ? module + ':' : '') + code + '] ',
3838
template = arguments[1],
3939
templateArgs = arguments,
40-
stringify = function(obj) {
41-
if (typeof obj === 'function') {
42-
return obj.toString().replace(/ \{[\s\S]*$/, '');
43-
} else if (typeof obj === 'undefined') {
44-
return 'undefined';
45-
} else if (typeof obj !== 'string') {
46-
return JSON.stringify(obj);
47-
}
48-
return obj;
49-
},
40+
5041
message, i;
5142

5243
message = prefix + template.replace(/\{\d+\}/g, function(match) {
5344
var index = +match.slice(1, -1), arg;
5445

5546
if (index + 2 < templateArgs.length) {
56-
arg = templateArgs[index + 2];
57-
if (typeof arg === 'function') {
58-
return arg.toString().replace(/ ?\{[\s\S]*$/, '');
59-
} else if (typeof arg === 'undefined') {
60-
return 'undefined';
61-
} else if (typeof arg !== 'string') {
62-
return toJson(arg);
63-
}
64-
return arg;
47+
return toDebugString(templateArgs[index + 2]);
6548
}
6649
return match;
6750
});
@@ -70,7 +53,7 @@ function minErr(module, ErrorConstructor) {
7053
(module ? module + '/' : '') + code;
7154
for (i = 2; i < arguments.length; i++) {
7255
message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
73-
encodeURIComponent(stringify(arguments[i]));
56+
encodeURIComponent(toDebugString(arguments[i]));
7457
}
7558
return new ErrorConstructor(message);
7659
};

src/stringify.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
/* global: toDebugString: true */
4+
5+
function serializeObject(obj) {
6+
var seen = [];
7+
8+
return JSON.stringify(obj, function(key, val) {
9+
val = toJsonReplacer(key, val);
10+
if (isObject(val)) {
11+
12+
if (seen.indexOf(val) >= 0) return '<<already seen>>';
13+
14+
seen.push(val);
15+
}
16+
return val;
17+
});
18+
}
19+
20+
function toDebugString(obj) {
21+
if (typeof obj === 'function') {
22+
return obj.toString().replace(/ \{[\s\S]*$/, '');
23+
} else if (typeof obj === 'undefined') {
24+
return 'undefined';
25+
} else if (typeof obj !== 'string') {
26+
return serializeObject(obj);
27+
}
28+
return obj;
29+
}

test/.jshintrc

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"angularModule": false,
1919
"nodeName_": false,
2020
"uid": false,
21+
"toDebugString": false,
2122

2223
"lowercase": false,
2324
"uppercase": false,

test/minErrSpec.js

+7
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ describe('minErr', function() {
6060
toMatch(/^\[test:26\] false: false; zero: 0; null: null; undefined: undefined; emptyStr: /);
6161
});
6262

63+
it('should handle arguments that are objects with cyclic references', function() {
64+
var a = { b: { } };
65+
a.b.a = a;
66+
67+
var myError = testError('26', 'a is {0}', a);
68+
expect(myError.message).toMatch(/a is {"b":{"a":"<<already seen>>"}}/);
69+
});
6370

6471
it('should preserve interpolation markers when fewer arguments than needed are provided', function() {
6572
// this way we can easily see if we are passing fewer args than needed

test/stringifySpec.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
describe('toDebugString', function() {
4+
it('should convert its argument to a string', function() {
5+
expect(toDebugString('string')).toEqual('string');
6+
expect(toDebugString(123)).toEqual('123');
7+
expect(toDebugString({a:{b:'c'}})).toEqual('{"a":{"b":"c"}}');
8+
expect(toDebugString(function fn() { var a = 10; })).toEqual('function fn()');
9+
expect(toDebugString()).toEqual('undefined');
10+
var a = { };
11+
a.a = a;
12+
expect(toDebugString(a)).toEqual('{"a":"<<already seen>>"}');
13+
expect(toDebugString([a,a])).toEqual('[{"a":"<<already seen>>"},"<<already seen>>"]');
14+
});
15+
});

0 commit comments

Comments
 (0)