Skip to content

Commit 8ab7735

Browse files
committed
fix(errors): require passing stack traces explicitly in ng2 own code
1 parent 5c88f66 commit 8ab7735

File tree

11 files changed

+111
-74
lines changed

11 files changed

+111
-74
lines changed

modules/angular2/src/core/application.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,8 @@ export function bootstrap(appComponentType: Type,
259259
bootstrapProcess.resolve(new ApplicationRef(componentRef, appComponentType, appInjector));
260260
},
261261

262-
(err) => {
263-
bootstrapProcess.reject(err)
262+
(err, stackTrace) => {
263+
bootstrapProcess.reject(err, stackTrace)
264264
});
265265
});
266266

modules/angular2/src/di/injector.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,17 +338,17 @@ class _AsyncInjectorStrategy {
338338
var deps = this.injector._resolveDependencies(key, binding, true);
339339
var depsPromise = PromiseWrapper.all(deps);
340340

341-
var promise = PromiseWrapper.then(depsPromise, null, (e) => this._errorHandler(key, e))
341+
var promise = PromiseWrapper.then(depsPromise, null, (e, s) => this._errorHandler(key, e, s))
342342
.then(deps => this._findOrCreate(key, binding, deps))
343343
.then(instance => this._cacheInstance(key, instance));
344344

345345
this.injector._setInstance(key, new _Waiting(promise));
346346
return promise;
347347
}
348348

349-
_errorHandler(key: Key, e): Promise<any> {
349+
_errorHandler(key: Key, e, stack): Promise<any> {
350350
if (e instanceof AbstractBindingError) e.addKey(key);
351-
return PromiseWrapper.reject(e);
351+
return PromiseWrapper.reject(e, stack);
352352
}
353353

354354
_findOrCreate(key: Key, binding: ResolvedBinding, deps: List<any>) {

modules/angular2/src/facade/async.dart

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ export 'dart:async' show Future, Stream, StreamController, StreamSubscription;
66
class PromiseWrapper {
77
static Future resolve(obj) => new Future.value(obj);
88

9-
static Future reject(obj) => new Future.error(
9+
static Future reject(obj, stackTrace) => new Future.error(
1010
obj,
11-
obj is Error ? obj.stackTrace : null);
11+
stackTrace != null
12+
? stackTrace
13+
: obj is Error
14+
? obj.stackTrace
15+
: null);
1216

1317
static Future<List> all(List<Future> promises) => Future.wait(promises);
1418

@@ -23,7 +27,7 @@ class PromiseWrapper {
2327
return promise.catchError(onError);
2428
}
2529

26-
static _Completer completer() => new _Completer(new Completer());
30+
static CompleterWrapper completer() => new CompleterWrapper(new Completer());
2731

2832
// TODO(vic): create a TimerWrapper
2933
static Timer setTimeout(fn(), int millis)
@@ -99,22 +103,21 @@ class EventEmitter extends Stream {
99103
}
100104
}
101105

102-
class _Completer {
106+
class CompleterWrapper {
103107
final Completer c;
104108

105-
_Completer(this.c);
109+
CompleterWrapper(this.c);
106110

107111
Future get promise => c.future;
108112

109113
void resolve(v) {
110114
c.complete(v);
111115
}
112116

113-
void reject(v) {
114-
var stack = null;
115-
if (v is Error) {
116-
stack = v.stackTrace;
117+
void reject(error, stack) {
118+
if (stack == null && error is Error) {
119+
stack = error.stackTrace;
117120
}
118-
c.completeError(v, stack);
121+
c.completeError(error, stack);
119122
}
120123
}

modules/angular2/src/facade/async.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export var Promise = (<any>global).Promise;
1515
export class PromiseWrapper {
1616
static resolve(obj): Promise<any> { return Promise.resolve(obj); }
1717

18-
static reject(obj): Promise<any> { return Promise.reject(obj); }
18+
static reject(obj, _): Promise<any> { return Promise.reject(obj); }
1919

2020
// Note: We can't rename this method into `catch`, as this is not a valid
2121
// method name in Dart.
@@ -29,7 +29,7 @@ export class PromiseWrapper {
2929
}
3030

3131
static then<T>(promise: Promise<T>, success: (value: any) => T | Thenable<T>,
32-
rejection: (error: any) => T | Thenable<T>): Promise<T> {
32+
rejection: (error: any, stack?: any) => T | Thenable<T>): Promise<T> {
3333
return promise.then(success, rejection);
3434
}
3535

modules/angular2/src/mock/xhr_mock.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class _PendingRequest {
8888

8989
complete(response: string) {
9090
if (isBlank(response)) {
91-
this.completer.reject(`Failed to load ${this.url}`);
91+
this.completer.reject(`Failed to load ${this.url}`, null);
9292
} else {
9393
this.completer.resolve(response);
9494
}

modules/angular2/test/core/compiler/integration_dart_spec.dart

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:angular2/src/test_lib/test_bed.dart';
77
import 'package:angular2/test_lib.dart';
88

99
class MockException implements Error { var message; var stackTrace; }
10+
class NonError { var message; }
1011

1112
void functionThatThrows() {
1213
try { throw new MockException(); }
@@ -19,6 +20,16 @@ void functionThatThrows() {
1920
}
2021
}
2122

23+
void functionThatThrowsNonError() {
24+
try { throw new NonError(); }
25+
catch(e, stack) {
26+
// If we lose the stack trace the message will no longer match
27+
// the first line in the stack
28+
e.message = stack.toString().split('\n')[0];
29+
rethrow;
30+
}
31+
}
32+
2233
main() {
2334
describe('TypeLiteral', () {
2435
it('should publish via appInjector',
@@ -37,7 +48,7 @@ main() {
3748
});
3849

3950
describe('Error handling', () {
40-
it('should preserve stack traces throws from components',
51+
it('should preserve Error stack traces thrown from components',
4152
inject([TestBed, AsyncTestCompleter], (tb, async) {
4253
tb.overrideView(Dummy, new View(
4354
template: '<throwing-component></throwing-component>',
@@ -49,6 +60,19 @@ main() {
4960
async.done();
5061
});
5162
}));
63+
64+
it('should preserve non-Error stack traces thrown from components',
65+
inject([TestBed, AsyncTestCompleter], (tb, async) {
66+
tb.overrideView(Dummy, new View(
67+
template: '<throwing-component2></throwing-component2>',
68+
directives: [ThrowingComponent2]
69+
));
70+
71+
tb.createView(Dummy).catchError((e, stack) {
72+
expect(stack.toString().split('\n')[0]).toEqual(e.message);
73+
async.done();
74+
});
75+
}));
5276
});
5377
}
5478

@@ -81,3 +105,13 @@ class ThrowingComponent {
81105
functionThatThrows();
82106
}
83107
}
108+
109+
@Component(
110+
selector: 'throwing-component2'
111+
)
112+
@View(template: '')
113+
class ThrowingComponent2 {
114+
ThrowingComponent2() {
115+
functionThatThrowsNonError();
116+
}
117+
}

modules/angular2/test/facade/async_dart_spec.dart

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:angular2/test_lib.dart';
55
import 'package:angular2/src/facade/async.dart';
66

77
class MockException implements Error { var message; var stackTrace; }
8+
class NonError { var message; }
89

910
void functionThatThrows() {
1011
try { throw new MockException(); }
@@ -18,7 +19,13 @@ void functionThatThrows() {
1819
}
1920

2021
void functionThatThrowsNonError() {
21-
throw 'this is an error';
22+
try { throw new NonError(); }
23+
catch(e, stack) {
24+
// If we lose the stack trace the message will no longer match
25+
// the first line in the stack
26+
e.message = stack.toString().split('\n')[0];
27+
rethrow;
28+
}
2229
}
2330

2431
void expectFunctionThatThrowsWithStackTrace(
@@ -29,72 +36,65 @@ void expectFunctionThatThrowsWithStackTrace(
2936
});
3037
}
3138

32-
void expectFunctionThatThrowsWithoutStackTrace(Future future,
33-
AsyncTestCompleter async) {
34-
PromiseWrapper.catchError(future, (err, StackTrace stack) {
35-
expect(stack).toBe(null);
36-
async.done();
37-
});
38-
}
39-
4039
main() {
41-
describe('Completer', () {
42-
43-
it('should preserve error stack traces',
44-
inject([AsyncTestCompleter], (async) {
45-
var c = PromiseWrapper.completer();
46-
47-
expectFunctionThatThrowsWithStackTrace(c.promise, async);
48-
49-
try {
50-
functionThatThrows();
51-
} catch(e) {
52-
c.reject(e);
53-
}
54-
}));
40+
describe('async facade', () {
41+
describe('Completer', () {
5542

56-
// TODO: We might fix this one day; for now testing it to be explicit
57-
it('CANNOT preserve error stack traces for non-Errors',
58-
inject([AsyncTestCompleter], (async) {
59-
var c = PromiseWrapper.completer();
60-
61-
expectFunctionThatThrowsWithoutStackTrace(c.promise, async);
62-
63-
try {
64-
functionThatThrowsNonError();
65-
} catch(e) {
66-
c.reject(e);
67-
}
68-
}));
69-
70-
});
71-
72-
describe('PromiseWrapper', () {
43+
it('should preserve Error stack traces',
44+
inject([AsyncTestCompleter], (async) {
45+
var c = PromiseWrapper.completer();
7346

74-
describe('reject', () {
47+
expectFunctionThatThrowsWithStackTrace(c.promise, async);
7548

76-
it('should preserve error stack traces',
77-
inject([AsyncTestCompleter], (async) {
7849
try {
7950
functionThatThrows();
8051
} catch(e) {
81-
var rejectedFuture = PromiseWrapper.reject(e);
82-
expectFunctionThatThrowsWithStackTrace(rejectedFuture, async);
52+
c.reject(e, null);
8353
}
8454
}));
8555

86-
// TODO: We might fix this one day; for now testing it to be explicit
87-
it('CANNOT preserve stack traces for non-Errors',
56+
it('should preserve error stack traces for non-Errors',
8857
inject([AsyncTestCompleter], (async) {
58+
var c = PromiseWrapper.completer();
59+
60+
expectFunctionThatThrowsWithStackTrace(c.promise, async);
61+
8962
try {
9063
functionThatThrowsNonError();
91-
} catch(e) {
92-
var rejectedFuture = PromiseWrapper.reject(e);
93-
expectFunctionThatThrowsWithoutStackTrace(rejectedFuture, async);
64+
} catch(e, s) {
65+
c.reject(e, s);
9466
}
9567
}));
9668

9769
});
9870

71+
describe('PromiseWrapper', () {
72+
73+
describe('reject', () {
74+
75+
it('should preserve Error stack traces',
76+
inject([AsyncTestCompleter], (async) {
77+
try {
78+
functionThatThrows();
79+
} catch(e) {
80+
var rejectedFuture = PromiseWrapper.reject(e, null);
81+
expectFunctionThatThrowsWithStackTrace(rejectedFuture, async);
82+
}
83+
}));
84+
85+
it('should preserve stack traces for non-Errors',
86+
inject([AsyncTestCompleter], (async) {
87+
try {
88+
functionThatThrowsNonError();
89+
} catch(e, s) {
90+
var rejectedFuture = PromiseWrapper.reject(e, s);
91+
expectFunctionThatThrowsWithStackTrace(rejectedFuture, async);
92+
}
93+
}));
94+
95+
});
96+
97+
});
98+
9999
});
100100
}

modules/angular2/test/render/dom/compiler/compiler_common_tests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ class FakeTemplateLoader extends TemplateLoader {
219219
}
220220
}
221221

222-
return PromiseWrapper.reject('Load failed');
222+
return PromiseWrapper.reject('Load failed', null);
223223
}
224224
}
225225

modules/angular2/test/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy_spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ class FakeXHR extends XHR {
131131
get(url: string): Promise<string> {
132132
var response = MapWrapper.get(this._responses, url);
133133
if (isBlank(response)) {
134-
return PromiseWrapper.reject('xhr error');
134+
return PromiseWrapper.reject('xhr error', null);
135135
}
136136

137137
return PromiseWrapper.resolve(response);

modules/angular2/test/render/dom/shadow_dom/style_inliner_spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ class FakeXHR extends XHR {
223223
get(url: string): Promise<string> {
224224
var response = MapWrapper.get(this._responses, url);
225225
if (isBlank(response)) {
226-
return PromiseWrapper.reject('xhr error');
226+
return PromiseWrapper.reject('xhr error', null);
227227
}
228228

229229
return PromiseWrapper.resolve(response);

0 commit comments

Comments
 (0)