feat(zone): add support for long stack traces
This commit is contained in:
		
							parent
							
								
									d5fcac4d7a
								
							
						
					
					
						commit
						df21c3c77d
					
				| @ -31,6 +31,7 @@ var _HTLM_DEFAULT_SCRIPTS_JS = [ | ||||
|   {src: '/rtts_assert/lib/rtts_assert.js', mimeType: 'text/javascript'}, | ||||
|   {src: '/deps/es6-module-loader-sans-promises.src.js', mimeType: 'text/javascript'}, | ||||
|   {src: '/deps/zone.js', mimeType: 'text/javascript'}, | ||||
|   {src: '/deps/long-stack-trace-zone.js', mimeType: 'text/javascript'}, | ||||
|   {src: '/deps/system.src.js', mimeType: 'text/javascript'}, | ||||
|   {src: '/deps/extension-register.js', mimeType: 'text/javascript'}, | ||||
|   {src: '/deps/runtime_paths.js', mimeType: 'text/javascript'}, | ||||
| @ -68,6 +69,7 @@ var CONFIG = { | ||||
|       "node_modules/systemjs/dist/system.src.js", | ||||
|       "node_modules/systemjs/lib/extension-register.js", | ||||
|       "node_modules/zone.js/zone.js", | ||||
|       "node_modules/zone.js/long-stack-trace-zone.js", | ||||
|       "tools/build/runtime_paths.js", | ||||
|       "node_modules/angular/angular.js" | ||||
|     ] | ||||
|  | ||||
| @ -20,6 +20,7 @@ module.exports = function(config) { | ||||
|       'node_modules/systemjs/dist/system.src.js', | ||||
|       'node_modules/systemjs/lib/extension-register.js', | ||||
|       'node_modules/zone.js/zone.js', | ||||
|       'node_modules/zone.js/long-stack-trace-zone.js', | ||||
| 
 | ||||
|       'tools/build/file2modulename.js', | ||||
|       'test-main.js' | ||||
|  | ||||
| @ -2,6 +2,7 @@ name: core | ||||
| environment: | ||||
|   sdk: '>=1.4.0' | ||||
| dependencies: | ||||
|   stack_trace: '1.1.1' | ||||
|   change_detection: | ||||
|     path: ../change_detection | ||||
|   di: | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import {Injector, bind, OpaqueToken} from 'di/di'; | ||||
| import {Type, FIELD, isBlank, isPresent, BaseException, assertionsEnabled} from 'facade/lang'; | ||||
| import {Type, FIELD, isBlank, isPresent, BaseException, assertionsEnabled, print} from 'facade/lang'; | ||||
| import {DOM, Element} from 'facade/dom'; | ||||
| import {Compiler, CompilerCache} from './compiler/compiler'; | ||||
| import {ProtoView} from './compiler/view'; | ||||
| @ -11,7 +11,8 @@ import {RecordRange} from 'change_detection/record_range'; | ||||
| import {TemplateLoader} from './compiler/template_loader'; | ||||
| import {DirectiveMetadataReader} from './compiler/directive_metadata_reader'; | ||||
| import {AnnotatedType} from './compiler/annotated_type'; | ||||
| import {ListWrapper} from 'facade/collection'; | ||||
| import {List, ListWrapper} from 'facade/collection'; | ||||
| import {PromiseWrapper} from 'facade/async'; | ||||
| import {VmTurnZone} from 'core/zone/vm_turn_zone'; | ||||
| import {LifeCycle} from 'core/life_cycle/life_cycle'; | ||||
| 
 | ||||
| @ -77,24 +78,47 @@ function _injectorBindings(appComponentType) { | ||||
|       documentDependentBindings(appComponentType)); | ||||
| } | ||||
| 
 | ||||
| function _createVmZone(givenReporter:Function){ | ||||
|   var defaultErrorReporter = (exception, stackTrace) => { | ||||
|     var longStackTrace = ListWrapper.join(stackTrace, "\n\n-----async gap-----\n"); | ||||
|     print(`${exception}\n\n${longStackTrace}`); | ||||
|     throw exception; | ||||
|   }; | ||||
| 
 | ||||
|   var reporter = isPresent(givenReporter) ? givenReporter : defaultErrorReporter; | ||||
| 
 | ||||
|   var zone = new VmTurnZone({enableLongStackTrace: assertionsEnabled()}); | ||||
|   zone.initCallbacks({onErrorHandler: reporter}); | ||||
|   return zone; | ||||
| } | ||||
| 
 | ||||
| // Multiple calls to this method are allowed. Each application would only share
 | ||||
| // _rootInjector, which is not user-configurable by design, thus safe to share.
 | ||||
| export function bootstrap(appComponentType: Type, bindings=null) { | ||||
|   // TODO(rado): prepopulate template cache, so applications with only
 | ||||
|   // index.html and main.js are possible.
 | ||||
| export function bootstrap(appComponentType: Type, bindings=null, givenBootstrapErrorReporter=null) { | ||||
|   var bootstrapProcess = PromiseWrapper.completer(); | ||||
| 
 | ||||
|   var zone = _createVmZone(givenBootstrapErrorReporter); | ||||
|   zone.run(() => { | ||||
|     // TODO(rado): prepopulate template cache, so applications with only
 | ||||
|     // index.html and main.js are possible.
 | ||||
| 
 | ||||
|   var zone = new VmTurnZone(); | ||||
|   return zone.run(() => { | ||||
|     if (isBlank(_rootInjector)) _rootInjector = new Injector(_rootBindings); | ||||
| 
 | ||||
|     var appInjector = _rootInjector.createChild(_injectorBindings(appComponentType)); | ||||
|     if (isPresent(bindings)) appInjector = appInjector.createChild(bindings); | ||||
| 
 | ||||
|     return appInjector.asyncGet(LifeCycle). | ||||
|         then((lc) => { | ||||
|           lc.registerWith(zone); | ||||
|           lc.tick(); | ||||
|         }). | ||||
|         then((_) => appInjector); | ||||
|     PromiseWrapper.then(appInjector.asyncGet(LifeCycle), | ||||
|       (lc) => { | ||||
|         lc.registerWith(zone); | ||||
|         lc.tick(); //the first tick that will bootstrap the app
 | ||||
| 
 | ||||
|         bootstrapProcess.complete(appInjector); | ||||
|       }, | ||||
| 
 | ||||
|       (err) => { | ||||
|         bootstrapProcess.reject(err) | ||||
|       }); | ||||
|   }); | ||||
| 
 | ||||
|   return bootstrapProcess.promise; | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import {FIELD} from 'facade/lang'; | ||||
| import {FIELD, print} from 'facade/lang'; | ||||
| import {ChangeDetector} from 'change_detection/change_detector'; | ||||
| import {VmTurnZone} from 'core/zone/vm_turn_zone'; | ||||
| import {ListWrapper} from 'facade/collection'; | ||||
| 
 | ||||
| export class LifeCycle { | ||||
|   _changeDetector:ChangeDetector; | ||||
| @ -10,7 +11,15 @@ export class LifeCycle { | ||||
|   } | ||||
| 
 | ||||
|   registerWith(zone:VmTurnZone) { | ||||
|     // temporary error handler, we should inject one
 | ||||
|     var errorHandler = (exception, stackTrace) => { | ||||
|       var longStackTrace = ListWrapper.join(stackTrace, "\n\n-----async gap-----\n"); | ||||
|       print(`${exception}\n\n${longStackTrace}`); | ||||
|       throw exception; | ||||
|     }; | ||||
| 
 | ||||
|     zone.initCallbacks({ | ||||
|       onErrorHandler: errorHandler, | ||||
|       onTurnDone: () => this.tick() | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @ -1,45 +1,68 @@ | ||||
| library angular.zone; | ||||
| 
 | ||||
| import 'dart:async' as async; | ||||
| import 'package:stack_trace/stack_trace.dart' show Chain; | ||||
| 
 | ||||
| class VmTurnZone { | ||||
|   Function _onTurnStart; | ||||
|   Function _onTurnDone; | ||||
|   Function _onScheduleMicrotask; | ||||
|   Function _onErrorHandler; | ||||
| 
 | ||||
|   async.Zone _outerZone; | ||||
|   async.Zone _innerZone; | ||||
| 
 | ||||
|   int _nestedRunCounter; | ||||
| 
 | ||||
|   VmTurnZone() { | ||||
|   VmTurnZone({bool enableLongStackTrace}) { | ||||
|     _nestedRunCounter = 0; | ||||
|     _outerZone = async.Zone.current; | ||||
|     _innerZone = _outerZone.fork(specification: new async.ZoneSpecification( | ||||
|     _innerZone = _createInnerZoneWithErrorHandling(enableLongStackTrace); | ||||
|   } | ||||
| 
 | ||||
|   initCallbacks({Function onTurnStart, Function onTurnDone, Function onScheduleMicrotask, Function onErrorHandler}) { | ||||
|     this._onTurnStart = onTurnStart; | ||||
|     this._onTurnDone = onTurnDone; | ||||
|     this._onScheduleMicrotask = onScheduleMicrotask; | ||||
|     this._onErrorHandler = onErrorHandler; | ||||
|   } | ||||
| 
 | ||||
|   dynamic run(fn()) => _innerZone.run(fn); | ||||
| 
 | ||||
|   dynamic runOutsideAngular(fn()) => _outerZone.run(fn); | ||||
| 
 | ||||
| 
 | ||||
|   async.Zone _createInnerZoneWithErrorHandling(bool enableLongStackTrace) { | ||||
|     if (enableLongStackTrace) { | ||||
|       return Chain.capture(() { | ||||
|         return _createInnerZone(async.Zone.current); | ||||
|       }, onError: this._onErrorWithLongStackTrace); | ||||
|     } else { | ||||
|       return async.runZoned(() { | ||||
|         return _createInnerZone(async.Zone.current); | ||||
|       }, onError: this._onErrorWithoutLongStackTrace); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async.Zone _createInnerZone(async.Zone zone) { | ||||
|     return zone.fork(specification: new async.ZoneSpecification( | ||||
|         run: _onRun, | ||||
|         runUnary: _onRunUnary, | ||||
|         scheduleMicrotask: _onMicrotask | ||||
|     )); | ||||
|   } | ||||
|    | ||||
|   initCallbacks({Function onTurnStart, Function onTurnDone, Function onScheduleMicrotask}) { | ||||
|     this._onTurnStart = onTurnStart; | ||||
|     this._onTurnDone = onTurnDone; | ||||
|     this._onScheduleMicrotask = onScheduleMicrotask; | ||||
|   } | ||||
|    | ||||
|   dynamic run(fn()) => _innerZone.run(fn); | ||||
| 
 | ||||
|   dynamic runOutsideAngular(fn()) => _outerZone.run(fn); | ||||
|    | ||||
| 
 | ||||
|   dynamic _onRunBase(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) { | ||||
|     _nestedRunCounter++; | ||||
|     try { | ||||
|       if (_nestedRunCounter == 1 && _onTurnStart != null) delegate.run(zone, _onTurnStart); | ||||
| 
 | ||||
|       return fn(); | ||||
| 
 | ||||
|     } catch (e, s) { | ||||
|       if (_onErrorHandler != null && _nestedRunCounter == 1) { | ||||
|         _onErrorHandler(e, [s.toString()]); | ||||
|       } else { | ||||
|         rethrow; | ||||
|       } | ||||
|     } finally { | ||||
|       _nestedRunCounter--; | ||||
|       if (_nestedRunCounter == 0 && _onTurnDone != null) _finishTurn(zone, delegate); | ||||
| @ -47,10 +70,10 @@ class VmTurnZone { | ||||
|   } | ||||
| 
 | ||||
|   dynamic _onRun(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) => | ||||
|     _onRunBase(self, delegate, zone, () => delegate.run(zone, fn)); | ||||
|       _onRunBase(self, delegate, zone, () => delegate.run(zone, fn)); | ||||
| 
 | ||||
|   dynamic _onRunUnary(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn(args), args) => | ||||
|     _onRunBase(self, delegate, zone, () => delegate.runUnary(zone, fn, args)); | ||||
|       _onRunBase(self, delegate, zone, () => delegate.runUnary(zone, fn, args)); | ||||
| 
 | ||||
|   void _finishTurn(zone, delegate) { | ||||
|     delegate.run(zone, _onTurnDone); | ||||
| @ -58,9 +81,25 @@ class VmTurnZone { | ||||
| 
 | ||||
|   _onMicrotask(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn) { | ||||
|     if (this._onScheduleMicrotask != null) { | ||||
|       this._onScheduleMicrotask(fn); | ||||
|       _onScheduleMicrotask(fn); | ||||
|     } else { | ||||
|       delegate.scheduleMicrotask(zone, fn); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   _onErrorWithLongStackTrace(exception, Chain chain) { | ||||
|     final traces = chain.terse.traces.map((t) => t.toString()).toList(); | ||||
|     _onError(exception, traces, chain.traces[0]); | ||||
|   } | ||||
|   _onErrorWithoutLongStackTrace(exception, StackTrace trace) { | ||||
|     _onError(exception, [trace.toString()], trace); | ||||
|   } | ||||
| 
 | ||||
|   _onError(exception, List<String> traces, StackTrace singleTrace) { | ||||
|     if (_onErrorHandler != null) { | ||||
|       _onErrorHandler(exception, traces); | ||||
|     } else { | ||||
|       _outerZone.handleUncaughtError(exception, singleTrace); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import {List, ListWrapper} from 'facade/collection'; | ||||
| import {normalizeBlank} from 'facade/lang'; | ||||
| import {List, ListWrapper, StringMapWrapper} from 'facade/collection'; | ||||
| import {normalizeBlank, isPresent} from 'facade/lang'; | ||||
| 
 | ||||
| export class VmTurnZone { | ||||
|   _outerZone; | ||||
| @ -7,24 +7,24 @@ export class VmTurnZone { | ||||
| 
 | ||||
|   _onTurnStart:Function; | ||||
|   _onTurnDone:Function; | ||||
|   _onErrorHandler:Function; | ||||
| 
 | ||||
|   _nestedRunCounter:number; | ||||
| 
 | ||||
|   constructor() { | ||||
|   constructor({enableLongStackTrace}) { | ||||
|     this._nestedRunCounter = 0; | ||||
|     this._onTurnStart = null; | ||||
|     this._onTurnDone = null; | ||||
|     this._onErrorHandler = null; | ||||
| 
 | ||||
|     this._outerZone = window.zone; | ||||
|     this._innerZone = this._outerZone.fork({ | ||||
|       beforeTask: () => this._beforeTask(), | ||||
|       afterTask: () => this._afterTask() | ||||
|     }); | ||||
|     this._innerZone = this._createInnerZone(this._outerZone, enableLongStackTrace); | ||||
|   } | ||||
| 
 | ||||
|   initCallbacks({onTurnStart, onTurnDone, onScheduleMicrotask} = {}) { | ||||
|   initCallbacks({onTurnStart, onTurnDone, onScheduleMicrotask, onErrorHandler} = {}) { | ||||
|     this._onTurnStart = normalizeBlank(onTurnStart); | ||||
|     this._onTurnDone = normalizeBlank(onTurnDone); | ||||
|     this._onErrorHandler = normalizeBlank(onErrorHandler); | ||||
|   } | ||||
| 
 | ||||
|   run(fn) { | ||||
| @ -35,6 +35,29 @@ export class VmTurnZone { | ||||
|     return this._outerZone.run(fn); | ||||
|   } | ||||
| 
 | ||||
|   _createInnerZone(zone, enableLongStackTrace) { | ||||
|     var vmTurnZone = this; | ||||
|     var errorHandling; | ||||
| 
 | ||||
|     if (enableLongStackTrace) { | ||||
|       errorHandling = StringMapWrapper.merge(Zone.longStackTraceZone, { | ||||
|         onError: function (e) { | ||||
|           vmTurnZone._onError(this, e) | ||||
|         } | ||||
|       }); | ||||
|     } else { | ||||
|       errorHandling = { | ||||
|         onError: function (e) { | ||||
|           vmTurnZone._onError(this, e) | ||||
|         } | ||||
|       }; | ||||
|     } | ||||
| 
 | ||||
|     return zone.fork(errorHandling).fork({ | ||||
|       beforeTask: () => {this._beforeTask()}, | ||||
|       afterTask: () => {this._afterTask()} | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   _beforeTask(){ | ||||
|     this._nestedRunCounter ++; | ||||
| @ -49,4 +72,18 @@ export class VmTurnZone { | ||||
|       this._onTurnDone(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   _onError(zone, e) { | ||||
|     if (isPresent(this._onErrorHandler)) { | ||||
|       var trace = [normalizeBlank(e.stack)]; | ||||
| 
 | ||||
|       while (zone && zone.constructedAtException) { | ||||
|         trace.push(zone.constructedAtException.get()); | ||||
|         zone = zone.parent; | ||||
|       } | ||||
|       this._onErrorHandler(e, trace); | ||||
|     } else { | ||||
|       throw e; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -54,7 +54,7 @@ export function main() { | ||||
| 
 | ||||
|   describe('bootstrap factory method', () => { | ||||
|     it('should throw if no element is found', (done) => { | ||||
|       var injectorPromise = bootstrap(HelloRootCmp); | ||||
|       var injectorPromise = bootstrap(HelloRootCmp, [], (e,t) => {throw e;}); | ||||
|       PromiseWrapper.then(injectorPromise, null, (reason) => { | ||||
|         expect(reason.message).toContain( | ||||
|             'The app selector "hello-app" did not match any elements'); | ||||
|  | ||||
							
								
								
									
										1
									
								
								modules/core/test/packages
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								modules/core/test/packages
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | ||||
| ../packages | ||||
| @ -10,7 +10,7 @@ export function main() { | ||||
| 
 | ||||
|     beforeEach(() => { | ||||
|       log = new Log(); | ||||
|       zone = new VmTurnZone(); | ||||
|       zone = new VmTurnZone({enableLongStackTrace: true}); | ||||
|       zone.initCallbacks({ | ||||
|         onTurnStart: log.fn('onTurnStart'), | ||||
|         onTurnDone: log.fn('onTurnDone') | ||||
| @ -73,12 +73,95 @@ export function main() { | ||||
|     }); | ||||
| 
 | ||||
|     describe("exceptions", () => { | ||||
|       it('should rethrow exceptions from the body', () => { | ||||
|       var trace, exception, saveStackTrace; | ||||
|       beforeEach(() => { | ||||
|         trace = null; | ||||
|         exception = null; | ||||
|         saveStackTrace = (e, t) => { | ||||
|           exception = e; | ||||
|           trace = t; | ||||
|         }; | ||||
|       }); | ||||
| 
 | ||||
|       it('should call the on error callback when it is defined', () => { | ||||
|         zone.initCallbacks({onErrorHandler: saveStackTrace}); | ||||
| 
 | ||||
|         zone.run(() => { | ||||
|           throw new BaseException('aaa'); | ||||
|         }); | ||||
| 
 | ||||
|         expect(exception).toBeDefined(); | ||||
|       }); | ||||
| 
 | ||||
|       it('should rethrow exceptions from the body when no callback defined', () => { | ||||
|         expect(() => { | ||||
|           zone.run(() => { | ||||
|             throw new BaseException('hello'); | ||||
|             throw new BaseException('bbb'); | ||||
|           }); | ||||
|         }).toThrowError('hello'); | ||||
|         }).toThrowError('bbb'); | ||||
|       }); | ||||
| 
 | ||||
|       it('should produce long stack traces', (done) => { | ||||
|         zone.initCallbacks({onErrorHandler: saveStackTrace}); | ||||
| 
 | ||||
|         var c = PromiseWrapper.completer(); | ||||
| 
 | ||||
|         zone.run(function () { | ||||
|           PromiseWrapper.setTimeout(function () { | ||||
|             PromiseWrapper.setTimeout(function () { | ||||
|               c.complete(null); | ||||
|               throw new BaseException('ccc'); | ||||
|             }, 0); | ||||
|           }, 0); | ||||
|         }); | ||||
| 
 | ||||
|         c.promise.then((_) => { | ||||
|           // then number of traces for JS and Dart is different
 | ||||
|           expect(trace.length).toBeGreaterThan(1); | ||||
|           done(); | ||||
|         }); | ||||
|       }); | ||||
| 
 | ||||
|       it('should produce long stack traces (when using promises)', (done) => { | ||||
|         zone.initCallbacks({onErrorHandler: saveStackTrace}); | ||||
| 
 | ||||
|         var c = PromiseWrapper.completer(); | ||||
| 
 | ||||
|         zone.run(function () { | ||||
|           PromiseWrapper.resolve(null).then((_) => { | ||||
|             return PromiseWrapper.resolve(null).then((__) => { | ||||
|               c.complete(null); | ||||
|               throw new BaseException("ddd"); | ||||
|             }); | ||||
|           }); | ||||
|         }); | ||||
| 
 | ||||
|         c.promise.then((_) => { | ||||
|           // then number of traces for JS and Dart is different
 | ||||
|           expect(trace.length).toBeGreaterThan(1); | ||||
|           done(); | ||||
|         }); | ||||
|       }); | ||||
| 
 | ||||
|       it('should disable long stack traces', (done) => { | ||||
|         var zone = new VmTurnZone({enableLongStackTrace: false}); | ||||
|         zone.initCallbacks({onErrorHandler: saveStackTrace}); | ||||
| 
 | ||||
|         var c = PromiseWrapper.completer(); | ||||
| 
 | ||||
|         zone.run(function () { | ||||
|           PromiseWrapper.setTimeout(function () { | ||||
|             PromiseWrapper.setTimeout(function () { | ||||
|               c.complete(null); | ||||
|               throw new BaseException('ccc'); | ||||
|             }, 0); | ||||
|           }, 0); | ||||
|         }); | ||||
| 
 | ||||
|         c.promise.then((_) => { | ||||
|           expect(trace.length).toEqual(1); | ||||
|           done(); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
| @ -12,11 +12,11 @@ class PromiseWrapper { | ||||
|     return new Future.error(obj); | ||||
|   } | ||||
| 
 | ||||
|   static Future<List> all(List<Future> promises){ | ||||
|   static Future<List> all(List<Future> promises) { | ||||
|     return Future.wait(promises); | ||||
|   } | ||||
| 
 | ||||
|   static Future then(Future promise, Function success, Function onError){ | ||||
|   static Future then(Future promise, Function success, Function onError) { | ||||
|     if (success == null) return promise.catchError(onError); | ||||
|     return promise.then(success, onError: onError); | ||||
|   } | ||||
| @ -24,13 +24,24 @@ class PromiseWrapper { | ||||
|   static completer(){ | ||||
|     return new _Completer(new Completer()); | ||||
|   } | ||||
| 
 | ||||
|   static setTimeout(fn, millis) { | ||||
|     new Timer(new Duration(milliseconds: millis), fn); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class _Completer { | ||||
|   Completer c; | ||||
| 
 | ||||
|   _Completer(this.c); | ||||
| 
 | ||||
|   get promise => c.future; | ||||
|   get complete => c.complete; | ||||
|   get reject => c.completeError; | ||||
| 
 | ||||
|   complete(v) { | ||||
|     c.complete(v); | ||||
|   } | ||||
| 
 | ||||
|   reject(v) { | ||||
|     c.completeError(v); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -33,4 +33,8 @@ export class PromiseWrapper { | ||||
|       reject: reject | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   static setTimeout(fn, millis) { | ||||
|     window.setTimeout(fn, millis); | ||||
|   } | ||||
| } | ||||
| @ -59,6 +59,24 @@ export class StringMapWrapper { | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   static merge(m1, m2) { | ||||
|     var m = {}; | ||||
| 
 | ||||
|     for (var attr in m1) { | ||||
|       if (m1.hasOwnProperty(attr)){ | ||||
|         m[attr] = m1[attr]; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     for (var attr in m2) { | ||||
|       if (m2.hasOwnProperty(attr)){ | ||||
|         m[attr] = m2[attr]; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return m; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class ListWrapper { | ||||
|  | ||||
| @ -214,4 +214,8 @@ export function assertionsEnabled() { | ||||
|   } catch (e) { | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function print(obj) { | ||||
|   console.log(obj); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user