Skip to content

Commit b2d63ac

Browse files
mheveryIgorMinar
authored andcommitted
Changed error handling so that better stack traces are displayed in the ng-errors
1 parent 4af32de commit b2d63ac

14 files changed

Lines changed: 144 additions & 102 deletions

.externalToolBuilders/JSTD_Tests.launch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
22
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
33
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
4-
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
4+
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
55
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;launchConfigurationWorkingSet editPageId=&quot;org.eclipse.ui.resourceWorkingSetPage&quot; factoryID=&quot;org.eclipse.ui.internal.WorkingSetFactory&quot; id=&quot;1262905463390_2&quot; label=&quot;workingSet&quot; name=&quot;workingSet&quot;&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/angular.js/test&quot; type=&quot;2&quot;/&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/angular.js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;/launchConfigurationWorkingSet&gt;}"/>
66
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js}/test.sh"/>
77
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>

.externalToolBuilders/docs.launch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
33
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/build&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
44
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
5-
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
5+
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
66
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/docs&quot; type=&quot;2&quot;/&gt;&#10;&lt;item path=&quot;/angular.js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
77
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/gen_docs.sh}"/>
88
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>

css/angular.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
border: 2px solid #FF0000;
99
font-family: "Courier New", Courier, monospace;
1010
font-size: smaller;
11+
white-space: pre;
1112
}
1213

1314
.ng-validation-error {

src/Angular.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ var _undefined = undefined,
8484
PRIORITY_WATCH = -1000,
8585
PRIORITY_LAST = 99999,
8686
PRIORITY = {'FIRST': PRIORITY_FIRST, 'LAST': PRIORITY_LAST, 'WATCH':PRIORITY_WATCH},
87+
Error = window.Error,
8788
jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy
8889
_ = window['_'],
8990
/** holds major version number for IE or NaN for real browsers */
@@ -557,6 +558,18 @@ function foreachSorted(obj, iterator, context) {
557558
}
558559

559560

561+
function formatError(arg) {
562+
if (arg instanceof Error) {
563+
if (arg.stack) {
564+
arg = arg.stack;
565+
} else if (arg.sourceURL) {
566+
arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
567+
}
568+
}
569+
return arg;
570+
}
571+
572+
560573
function extend(dst) {
561574
foreach(arguments, function(obj){
562575
if (obj !== dst) {

src/Scope.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ function expressionCompile(exp){
101101
}
102102

103103
function errorHandlerFor(element, error) {
104-
elementError(element, NG_EXCEPTION, isDefined(error) ? toJson(error) : error);
104+
elementError(element, NG_EXCEPTION, isDefined(error) ? formatError(error) : error);
105105
}
106106

107107
function createScope(parent, providers, instanceCache) {

src/directives.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ angularDirective("ng:bind", function(expression, element){
182182
oldElement = this.hasOwnProperty($$element) ? this.$element : _undefined;
183183
this.$element = element;
184184
value = this.$tryEval(expression, function(e){
185-
error = toJson(e);
185+
error = formatError(e);
186186
});
187187
this.$element = oldElement;
188188
// If we are HTML than save the raw HTML data so that we don't
@@ -466,15 +466,15 @@ angularWidget("@ng:repeat", function(expression, element){
466466
var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
467467
lhs, rhs, valueIdent, keyIdent;
468468
if (! match) {
469-
throw "Expected ng:repeat in form of 'item in collection' but got '" +
470-
expression + "'.";
469+
throw Error("Expected ng:repeat in form of 'item in collection' but got '" +
470+
expression + "'.");
471471
}
472472
lhs = match[1];
473473
rhs = match[2];
474474
match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);
475475
if (!match) {
476-
throw "'item' in 'item in collection' should be identifier or (key, value) but got '" +
477-
keyValue + "'.";
476+
throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
477+
keyValue + "'.");
478478
}
479479
valueIdent = match[3] || match[1];
480480
keyIdent = match[2];

src/parser.js

Lines changed: 31 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,7 @@ function lex(text, parseStringsForObjects){
6767
tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
6868
index += 1;
6969
} else {
70-
throw "Lexer Error: Unexpected next character [" +
71-
text.substring(index) +
72-
"] in expression '" + text +
73-
"' at column '" + (index+1) + "'.";
70+
throwError("Unexpected next character ", index, index+1);
7471
}
7572
}
7673
lastCh = ch;
@@ -103,6 +100,16 @@ function lex(text, parseStringsForObjects){
103100
function isExpOperator(ch) {
104101
return ch == '-' || ch == '+' || isNumber(ch);
105102
}
103+
104+
function throwError(error, start, end) {
105+
end = end || index;
106+
throw Error("Lexer Error: " + error + " at column" +
107+
(isDefined(start) ?
108+
"s " + start + "-" + index + " [" + text.substring(start, end) + "]" :
109+
" " + end) +
110+
" in expression [" + text + "].");
111+
}
112+
106113
function readNumber() {
107114
var number = "";
108115
var start = index;
@@ -121,7 +128,7 @@ function lex(text, parseStringsForObjects){
121128
} else if (isExpOperator(ch) &&
122129
(!peekCh || !isNumber(peekCh)) &&
123130
number.charAt(number.length - 1) == 'e') {
124-
throw 'Lexer found invalid exponential value "' + text + '"';
131+
throwError('Invalid exponent');
125132
} else {
126133
break;
127134
}
@@ -151,7 +158,7 @@ function lex(text, parseStringsForObjects){
151158
}
152159
tokens.push({index:start, text:ident, fn:fn, json: OPERATORS[ident]});
153160
}
154-
161+
155162
function readString(quote) {
156163
var start = index;
157164
index++;
@@ -165,9 +172,7 @@ function lex(text, parseStringsForObjects){
165172
if (ch == 'u') {
166173
var hex = text.substring(index + 1, index + 5);
167174
if (!hex.match(/[\da-f]{4}/i))
168-
throw "Lexer Error: Invalid unicode escape [\\u" +
169-
hex + "] starting at column '" +
170-
start + "' in expression '" + text + "'.";
175+
throwError( "Invalid unicode escape [\\u" + hex + "]");
171176
index += 4;
172177
string += String.fromCharCode(parseInt(hex, 16));
173178
} else {
@@ -194,9 +199,7 @@ function lex(text, parseStringsForObjects){
194199
}
195200
index++;
196201
}
197-
throw "Lexer Error: Unterminated quote [" +
198-
text.substring(start) + "] starting at column '" +
199-
(start+1) + "' in expression '" + text + "'.";
202+
throwError("Unterminated quote", start);
200203
}
201204
function readRegexp(quote) {
202205
var start = index;
@@ -227,9 +230,7 @@ function lex(text, parseStringsForObjects){
227230
}
228231
index++;
229232
}
230-
throw "Lexer Error: Unterminated RegExp [" +
231-
text.substring(start) + "] starting at column '" +
232-
(start+1) + "' in expression '" + text + "'.";
233+
throwError("Unterminated RegExp", start);
233234
}
234235
}
235236

@@ -249,17 +250,16 @@ function parser(text, json){
249250
};
250251

251252
///////////////////////////////////
252-
253-
function error(msg, token) {
254-
throw "Token '" + token.text +
255-
"' is " + msg + " at column='" +
256-
(token.index + 1) + "' of expression '" +
257-
text + "' starting at '" + text.substring(token.index) + "'.";
253+
function throwError(msg, token) {
254+
throw Error("Parse Error: Token '" + token.text +
255+
"' " + msg + " at column " +
256+
(token.index + 1) + " of expression [" +
257+
text + "] starting at [" + text.substring(token.index) + "].");
258258
}
259259

260260
function peekToken() {
261261
if (tokens.length === 0)
262-
throw "Unexpected end of expression: " + text;
262+
throw Error("Unexpected end of expression: " + text);
263263
return tokens[0];
264264
}
265265

@@ -280,10 +280,7 @@ function parser(text, json){
280280
if (token) {
281281
if (json && !token.json) {
282282
index = token.index;
283-
throw "Expression at column='" +
284-
token.index + "' of expression '" +
285-
text + "' starting at '" + text.substring(token.index) +
286-
"' is not valid json.";
283+
throwError("is not valid json", token);
287284
}
288285
tokens.shift();
289286
this.currentToken = token;
@@ -294,11 +291,7 @@ function parser(text, json){
294291

295292
function consume(e1){
296293
if (!expect(e1)) {
297-
var token = peek();
298-
throw "Expecting '" + e1 + "' at column '" +
299-
(token.index+1) + "' in '" +
300-
text + "' got '" +
301-
text.substring(token.index) + "'.";
294+
throwError("is unexpected, expecting [" + e1 + "]", peek());
302295
}
303296
}
304297

@@ -320,8 +313,7 @@ function parser(text, json){
320313

321314
function assertAllConsumed(){
322315
if (tokens.length !== 0) {
323-
throw "Did not understand '" + text.substring(tokens[0].index) +
324-
"' while evaluating '" + text + "'.";
316+
throwError("is extra token not part of expression", tokens[0]);
325317
}
326318
}
327319

@@ -387,28 +379,16 @@ function parser(text, json){
387379
}
388380

389381
function expression(){
390-
return throwStmt();
391-
}
392-
393-
function throwStmt(){
394-
if (expect('throw')) {
395-
var throwExp = assignment();
396-
return function (self) {
397-
throw throwExp(self);
398-
};
399-
} else {
400-
return assignment();
401-
}
382+
return assignment();
402383
}
403384

404385
function assignment(){
405386
var left = logicalOR();
406387
var token;
407388
if (token = expect('=')) {
408389
if (!left.isAssignable) {
409-
throw "Left hand side '" +
410-
text.substring(0, token.index) + "' of assignment '" +
411-
text.substring(token.index) + "' is not assignable.";
390+
throwError("implies assignment but [" +
391+
text.substring(0, token.index) + "] can not be assigned to", token);
412392
}
413393
var ident = function(){return left.isAssignable;};
414394
return binaryFn(ident, token.fn, logicalOR());
@@ -498,8 +478,7 @@ function parser(text, json){
498478
instance = instance[key];
499479
}
500480
if (typeof instance != $function) {
501-
throw "Function '" + token.text + "' at column '" +
502-
(token.index+1) + "' in '" + text + "' is not defined.";
481+
throwError("should be a function", token);
503482
}
504483
return instance;
505484
}
@@ -518,7 +497,7 @@ function parser(text, json){
518497
var token = expect();
519498
primary = token.fn;
520499
if (!primary) {
521-
error("not a primary expression", token);
500+
throwError("not a primary expression", token);
522501
}
523502
}
524503
var next;
@@ -530,7 +509,7 @@ function parser(text, json){
530509
} else if (next.text === '.') {
531510
primary = fieldAccess(primary);
532511
} else {
533-
throw "IMPOSSIBLE";
512+
throwError("IMPOSSIBLE");
534513
}
535514
}
536515
return primary;

src/services.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,6 @@ angularServiceInject("$location", function(browser) {
311311
<button ng:click="$log.error(message)">error</button>
312312
*/
313313
angularServiceInject("$log", function($window){
314-
var console = $window.console || {log: noop, warn: noop, info: noop, error: noop},
315-
log = console.log || noop;
316314
return {
317315
/**
318316
* @ngdoc method
@@ -322,7 +320,7 @@ angularServiceInject("$log", function($window){
322320
* @description
323321
* Write a log message
324322
*/
325-
log: bind(console, log),
323+
log: consoleLog('log'),
326324

327325
/**
328326
* @ngdoc method
@@ -332,7 +330,7 @@ angularServiceInject("$log", function($window){
332330
* @description
333331
* Write a warning message
334332
*/
335-
warn: bind(console, console.warn || log),
333+
warn: consoleLog('warn'),
336334

337335
/**
338336
* @ngdoc method
@@ -342,7 +340,7 @@ angularServiceInject("$log", function($window){
342340
* @description
343341
* Write an information message
344342
*/
345-
info: bind(console, console.info || log),
343+
info: consoleLog('info'),
346344

347345
/**
348346
* @ngdoc method
@@ -352,8 +350,25 @@ angularServiceInject("$log", function($window){
352350
* @description
353351
* Write an error message
354352
*/
355-
error: bind(console, console.error || log)
353+
error: consoleLog('error')
356354
};
355+
356+
function consoleLog(type) {
357+
var console = $window.console || {};
358+
var logFn = console[type] || console.log || noop;
359+
if (logFn.apply) {
360+
return function(){
361+
var args = [];
362+
foreach(arguments, function(arg){
363+
args.push(formatError(arg));
364+
});
365+
return logFn.apply(console, args);
366+
};
367+
} else {
368+
// we are IE, in which case there is nothing we can do
369+
return logFn;
370+
}
371+
}
357372
}, ['$window'], EAGER_PUBLISHED);
358373

359374
/**

test/BinderTest.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -276,15 +276,15 @@ BinderTest.prototype.testIfTextBindingThrowsErrorDecorateTheSpan = function(){
276276
a.scope.$eval();
277277
var span = childNode(doc, 0);
278278
assertTrue(span.hasClass('ng-exception'));
279-
assertEquals('ErrorMsg1', fromJson(span.text()));
280-
assertEquals('"ErrorMsg1"', span.attr('ng-exception'));
279+
assertTrue(!!span.text().match(/ErrorMsg1/));
280+
assertTrue(!!span.attr('ng-exception').match(/ErrorMsg1/));
281281

282282
a.scope.$set('error.throw', function(){throw "MyError";});
283283
a.scope.$eval();
284284
span = childNode(doc, 0);
285285
assertTrue(span.hasClass('ng-exception'));
286286
assertTrue(span.text(), span.text().match('MyError') !== null);
287-
assertEquals('"MyError"', span.attr('ng-exception'));
287+
assertEquals('MyError', span.attr('ng-exception'));
288288

289289
a.scope.$set('error.throw', function(){return "ok";});
290290
a.scope.$eval();
@@ -438,13 +438,12 @@ BinderTest.prototype.testActionOnAHrefThrowsError = function(){
438438
var model = {books:[]};
439439
var c = this.compile('<a ng:click="action()">Add Phone</a>', model);
440440
c.scope.action = function(){
441-
throw {a:'abc', b:2};
441+
throw new Error('MyError');
442442
};
443443
var input = c.node;
444444
browserTrigger(input, 'click');
445-
var error = fromJson(input.attr('ng-exception'));
446-
assertEquals("abc", error.a);
447-
assertEquals(2, error.b);
445+
var error = input.attr('ng-exception');
446+
assertTrue(!!error.match(/MyError/));
448447
assertTrue("should have an error class", input.hasClass('ng-exception'));
449448

450449
// TODO: I think that exception should never get cleared so this portion of test makes no sense

0 commit comments

Comments
 (0)