diff --git a/modules/angular2/src/core/application.js b/modules/angular2/src/core/application.js index 7b6eefc7cb..7e6133aa31 100644 --- a/modules/angular2/src/core/application.js +++ b/modules/angular2/src/core/application.js @@ -259,8 +259,8 @@ export function bootstrap(appComponentType: Type, bootstrapProcess.resolve(new ApplicationRef(componentRef, appComponentType, appInjector)); }, - (err) => { - bootstrapProcess.reject(err) + (err, stackTrace) => { + bootstrapProcess.reject(err, stackTrace) }); }); diff --git a/modules/angular2/src/di/injector.ts b/modules/angular2/src/di/injector.ts index 45795ec53b..1bbf49392f 100644 --- a/modules/angular2/src/di/injector.ts +++ b/modules/angular2/src/di/injector.ts @@ -338,7 +338,7 @@ class _AsyncInjectorStrategy { var deps = this.injector._resolveDependencies(key, binding, true); var depsPromise = PromiseWrapper.all(deps); - var promise = PromiseWrapper.then(depsPromise, null, (e) => this._errorHandler(key, e)) + var promise = PromiseWrapper.then(depsPromise, null, (e, s) => this._errorHandler(key, e, s)) .then(deps => this._findOrCreate(key, binding, deps)) .then(instance => this._cacheInstance(key, instance)); @@ -346,9 +346,9 @@ class _AsyncInjectorStrategy { return promise; } - _errorHandler(key: Key, e): Promise { + _errorHandler(key: Key, e, stack): Promise { if (e instanceof AbstractBindingError) e.addKey(key); - return PromiseWrapper.reject(e); + return PromiseWrapper.reject(e, stack); } _findOrCreate(key: Key, binding: ResolvedBinding, deps: List) { diff --git a/modules/angular2/src/facade/async.dart b/modules/angular2/src/facade/async.dart index fadb9c724c..a255a55f4b 100644 --- a/modules/angular2/src/facade/async.dart +++ b/modules/angular2/src/facade/async.dart @@ -6,9 +6,13 @@ export 'dart:async' show Future, Stream, StreamController, StreamSubscription; class PromiseWrapper { static Future resolve(obj) => new Future.value(obj); - static Future reject(obj) => new Future.error( + static Future reject(obj, stackTrace) => new Future.error( obj, - obj is Error ? obj.stackTrace : null); + stackTrace != null + ? stackTrace + : obj is Error + ? obj.stackTrace + : null); static Future all(List promises) => Future.wait(promises); @@ -23,7 +27,7 @@ class PromiseWrapper { return promise.catchError(onError); } - static _Completer completer() => new _Completer(new Completer()); + static CompleterWrapper completer() => new CompleterWrapper(new Completer()); // TODO(vic): create a TimerWrapper static Timer setTimeout(fn(), int millis) @@ -99,10 +103,10 @@ class EventEmitter extends Stream { } } -class _Completer { +class CompleterWrapper { final Completer c; - _Completer(this.c); + CompleterWrapper(this.c); Future get promise => c.future; @@ -110,11 +114,10 @@ class _Completer { c.complete(v); } - void reject(v) { - var stack = null; - if (v is Error) { - stack = v.stackTrace; + void reject(error, stack) { + if (stack == null && error is Error) { + stack = error.stackTrace; } - c.completeError(v, stack); + c.completeError(error, stack); } } diff --git a/modules/angular2/src/facade/async.ts b/modules/angular2/src/facade/async.ts index 6d58145322..bbda7d0b96 100644 --- a/modules/angular2/src/facade/async.ts +++ b/modules/angular2/src/facade/async.ts @@ -15,7 +15,7 @@ export var Promise = (global).Promise; export class PromiseWrapper { static resolve(obj): Promise { return Promise.resolve(obj); } - static reject(obj): Promise { return Promise.reject(obj); } + static reject(obj, _): Promise { return Promise.reject(obj); } // Note: We can't rename this method into `catch`, as this is not a valid // method name in Dart. @@ -29,7 +29,7 @@ export class PromiseWrapper { } static then(promise: Promise, success: (value: any) => T | Thenable, - rejection: (error: any) => T | Thenable): Promise { + rejection: (error: any, stack?: any) => T | Thenable): Promise { return promise.then(success, rejection); } diff --git a/modules/angular2/src/mock/xhr_mock.js b/modules/angular2/src/mock/xhr_mock.js index a59d73bbc9..15cbe4316c 100644 --- a/modules/angular2/src/mock/xhr_mock.js +++ b/modules/angular2/src/mock/xhr_mock.js @@ -88,7 +88,7 @@ class _PendingRequest { complete(response: string) { if (isBlank(response)) { - this.completer.reject(`Failed to load ${this.url}`); + this.completer.reject(`Failed to load ${this.url}`, null); } else { this.completer.resolve(response); } diff --git a/modules/angular2/test/core/compiler/integration_dart_spec.dart b/modules/angular2/test/core/compiler/integration_dart_spec.dart index db3a196059..72d6c3d84d 100644 --- a/modules/angular2/test/core/compiler/integration_dart_spec.dart +++ b/modules/angular2/test/core/compiler/integration_dart_spec.dart @@ -7,6 +7,7 @@ import 'package:angular2/src/test_lib/test_bed.dart'; import 'package:angular2/test_lib.dart'; class MockException implements Error { var message; var stackTrace; } +class NonError { var message; } void functionThatThrows() { try { throw new MockException(); } @@ -19,6 +20,16 @@ void functionThatThrows() { } } +void functionThatThrowsNonError() { + try { throw new NonError(); } + catch(e, stack) { + // If we lose the stack trace the message will no longer match + // the first line in the stack + e.message = stack.toString().split('\n')[0]; + rethrow; + } +} + main() { describe('TypeLiteral', () { it('should publish via appInjector', @@ -37,7 +48,7 @@ main() { }); describe('Error handling', () { - it('should preserve stack traces throws from components', + it('should preserve Error stack traces thrown from components', inject([TestBed, AsyncTestCompleter], (tb, async) { tb.overrideView(Dummy, new View( template: '', @@ -49,6 +60,19 @@ main() { async.done(); }); })); + + it('should preserve non-Error stack traces thrown from components', + inject([TestBed, AsyncTestCompleter], (tb, async) { + tb.overrideView(Dummy, new View( + template: '', + directives: [ThrowingComponent2] + )); + + tb.createView(Dummy).catchError((e, stack) { + expect(stack.toString().split('\n')[0]).toEqual(e.message); + async.done(); + }); + })); }); } @@ -81,3 +105,13 @@ class ThrowingComponent { functionThatThrows(); } } + +@Component( + selector: 'throwing-component2' +) +@View(template: '') +class ThrowingComponent2 { + ThrowingComponent2() { + functionThatThrowsNonError(); + } +} diff --git a/modules/angular2/test/facade/async_dart_spec.dart b/modules/angular2/test/facade/async_dart_spec.dart index 5266dc1277..2404735ff2 100644 --- a/modules/angular2/test/facade/async_dart_spec.dart +++ b/modules/angular2/test/facade/async_dart_spec.dart @@ -5,6 +5,7 @@ import 'package:angular2/test_lib.dart'; import 'package:angular2/src/facade/async.dart'; class MockException implements Error { var message; var stackTrace; } +class NonError { var message; } void functionThatThrows() { try { throw new MockException(); } @@ -18,7 +19,13 @@ void functionThatThrows() { } void functionThatThrowsNonError() { - throw 'this is an error'; + try { throw new NonError(); } + catch(e, stack) { + // If we lose the stack trace the message will no longer match + // the first line in the stack + e.message = stack.toString().split('\n')[0]; + rethrow; + } } void expectFunctionThatThrowsWithStackTrace( @@ -29,72 +36,65 @@ void expectFunctionThatThrowsWithStackTrace( }); } -void expectFunctionThatThrowsWithoutStackTrace(Future future, - AsyncTestCompleter async) { - PromiseWrapper.catchError(future, (err, StackTrace stack) { - expect(stack).toBe(null); - async.done(); - }); -} - main() { - describe('Completer', () { + describe('async facade', () { + describe('Completer', () { - it('should preserve error stack traces', - inject([AsyncTestCompleter], (async) { - var c = PromiseWrapper.completer(); - - expectFunctionThatThrowsWithStackTrace(c.promise, async); - - try { - functionThatThrows(); - } catch(e) { - c.reject(e); - } - })); - - // TODO: We might fix this one day; for now testing it to be explicit - it('CANNOT preserve error stack traces for non-Errors', - inject([AsyncTestCompleter], (async) { - var c = PromiseWrapper.completer(); - - expectFunctionThatThrowsWithoutStackTrace(c.promise, async); - - try { - functionThatThrowsNonError(); - } catch(e) { - c.reject(e); - } - })); - - }); - - describe('PromiseWrapper', () { - - describe('reject', () { - - it('should preserve error stack traces', + it('should preserve Error stack traces', inject([AsyncTestCompleter], (async) { + var c = PromiseWrapper.completer(); + + expectFunctionThatThrowsWithStackTrace(c.promise, async); + try { functionThatThrows(); } catch(e) { - var rejectedFuture = PromiseWrapper.reject(e); - expectFunctionThatThrowsWithStackTrace(rejectedFuture, async); + c.reject(e, null); } })); - // TODO: We might fix this one day; for now testing it to be explicit - it('CANNOT preserve stack traces for non-Errors', + it('should preserve error stack traces for non-Errors', inject([AsyncTestCompleter], (async) { + var c = PromiseWrapper.completer(); + + expectFunctionThatThrowsWithStackTrace(c.promise, async); + try { functionThatThrowsNonError(); - } catch(e) { - var rejectedFuture = PromiseWrapper.reject(e); - expectFunctionThatThrowsWithoutStackTrace(rejectedFuture, async); + } catch(e, s) { + c.reject(e, s); } })); }); + describe('PromiseWrapper', () { + + describe('reject', () { + + it('should preserve Error stack traces', + inject([AsyncTestCompleter], (async) { + try { + functionThatThrows(); + } catch(e) { + var rejectedFuture = PromiseWrapper.reject(e, null); + expectFunctionThatThrowsWithStackTrace(rejectedFuture, async); + } + })); + + it('should preserve stack traces for non-Errors', + inject([AsyncTestCompleter], (async) { + try { + functionThatThrowsNonError(); + } catch(e, s) { + var rejectedFuture = PromiseWrapper.reject(e, s); + expectFunctionThatThrowsWithStackTrace(rejectedFuture, async); + } + })); + + }); + + }); + }); } diff --git a/modules/angular2/test/render/dom/compiler/compiler_common_tests.js b/modules/angular2/test/render/dom/compiler/compiler_common_tests.js index e7a4663cd6..83e04f2b8a 100644 --- a/modules/angular2/test/render/dom/compiler/compiler_common_tests.js +++ b/modules/angular2/test/render/dom/compiler/compiler_common_tests.js @@ -219,7 +219,7 @@ class FakeTemplateLoader extends TemplateLoader { } } - return PromiseWrapper.reject('Load failed'); + return PromiseWrapper.reject('Load failed', null); } } diff --git a/modules/angular2/test/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy_spec.js b/modules/angular2/test/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy_spec.js index 4bcca815f5..c671652361 100644 --- a/modules/angular2/test/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy_spec.js +++ b/modules/angular2/test/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy_spec.js @@ -131,7 +131,7 @@ class FakeXHR extends XHR { get(url: string): Promise { var response = MapWrapper.get(this._responses, url); if (isBlank(response)) { - return PromiseWrapper.reject('xhr error'); + return PromiseWrapper.reject('xhr error', null); } return PromiseWrapper.resolve(response); diff --git a/modules/angular2/test/render/dom/shadow_dom/style_inliner_spec.js b/modules/angular2/test/render/dom/shadow_dom/style_inliner_spec.js index 28ed95e312..fcb5f49257 100644 --- a/modules/angular2/test/render/dom/shadow_dom/style_inliner_spec.js +++ b/modules/angular2/test/render/dom/shadow_dom/style_inliner_spec.js @@ -223,7 +223,7 @@ class FakeXHR extends XHR { get(url: string): Promise { var response = MapWrapper.get(this._responses, url); if (isBlank(response)) { - return PromiseWrapper.reject('xhr error'); + return PromiseWrapper.reject('xhr error', null); } return PromiseWrapper.resolve(response); diff --git a/tools/build/dartanalyzer.js b/tools/build/dartanalyzer.js index 6017551e4c..2e2b08177f 100644 --- a/tools/build/dartanalyzer.js +++ b/tools/build/dartanalyzer.js @@ -76,8 +76,8 @@ module.exports = function(gulp, plugins, config) { return; } } - // TODO(yjbanov): fix ng2 code and remove the check below - if (line.match(/_stack/)) { + // TODO: https://github.com/angular/ts2dart/issues/168 + if (line.match(/_stack' is not used/)) { return; }