diff --git a/src/.eslintrc.json b/src/.eslintrc.json index 64e8b866e6c8..a04eb9e58aa8 100644 --- a/src/.eslintrc.json +++ b/src/.eslintrc.json @@ -15,8 +15,9 @@ "splice": false, "push": false, "toString": false, - "minErrConfig": false, + "errConfigObj": false, "errorHandlingConfig": false, + "loadModulesErrorConfig":false, "isValidObjectMaxDepth": false, "ngMinErr": false, "_angular": false, diff --git a/src/Angular.js b/src/Angular.js index 7c424897ff18..8bd90eeaf172 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -10,7 +10,7 @@ splice, push, toString, - minErrConfig, + errConfigObj, errorHandlingConfig, isValidObjectMaxDepth, ngMinErr, diff --git a/src/auto/injector.js b/src/auto/injector.js index f41d1e8c8e9a..21dea72ee111 100644 --- a/src/auto/injector.js +++ b/src/auto/injector.js @@ -830,8 +830,7 @@ function createInjector(modulesToLoad, strictDi) { provider[invokeArgs[1]].apply(provider, invokeArgs[2]); } } - - try { + function tryBlock() { if (isString(module)) { moduleFn = angularModule(module); instanceInjector.modules[module] = moduleFn; @@ -845,20 +844,28 @@ function createInjector(modulesToLoad, strictDi) { } else { assertArgFn(module, 'module'); } - } catch (e) { - if (isArray(module)) { - module = module[module.length - 1]; - } - if (e.message && e.stack && e.stack.indexOf(e.message) === -1) { - // Safari & FF's stack traces don't contain error.message content - // unlike those of Chrome and IE - // So if stack doesn't contain message, we create a new string that contains both. - // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. - // eslint-disable-next-line no-ex-assign - e = e.message + '\n' + e.stack; - } - throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}', - module, e.stack || e.message || e); + } + + if (!errorHandlingConfig().isModuleError) { + tryBlock(); + } else { + try { + tryBlock(); + } catch (e) { + if (isArray(module)) { + module = module[module.length - 1]; + } + if (errorHandlingConfig().isModuleStack && e.message && e.stack && e.stack.indexOf(e.message) === -1) { + // Safari & FF's stack traces don't contain error.message content + // unlike those of Chrome and IE + // So if stack doesn't contain message, we create a new string that contains both. + // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. + // eslint-disable-next-line no-ex-assign + e = e.message + '\n' + e.stack; + } + throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}', + module,errorHandlingConfig().isModuleStack ? (e.stack || e.message || e) : e.message); + } } }); return runBlocks; diff --git a/src/minErr.js b/src/minErr.js index e20040319222..2d7133791959 100644 --- a/src/minErr.js +++ b/src/minErr.js @@ -1,15 +1,17 @@ 'use strict'; /* exported - minErrConfig, + errConfigObj, errorHandlingConfig, isValidObjectMaxDepth */ -var minErrConfig = { - objectMaxDepth: 5 +var errConfigObj = { + objectMaxDepth: 5, + isUrlParameters:true, + isModuleStack:true, + isModuleError:true }; - /** * @ngdoc function * @name angular.errorHandlingConfig @@ -30,14 +32,29 @@ var minErrConfig = { * * `objectMaxDepth` **{Number}** - The max depth for stringifying objects. Setting to a * non-positive or non-numeric value, removes the max depth limit. * Default: 5 + * * `isUrlParameters` **{Boolean}** - Specifies wether the generated url error will contain the paramters or not. + * Default: true + * * `isModuleStack` **{Boolean}** - Specifies wether the generated error for a module has a stack + * Default: true + * * `isModuleError` **{Boolean}** - Specifies wether the error will be rethrown for each module + * Default: true */ function errorHandlingConfig(config) { if (isObject(config)) { if (isDefined(config.objectMaxDepth)) { - minErrConfig.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN; + errConfigObj.objectMaxDepth = isValidObjectMaxDepth(config.objectMaxDepth) ? config.objectMaxDepth : NaN; + } + if (isDefined(config.isUrlParameters) && isBoolean(config.isUrlParameters)) { + errConfigObj.isUrlParameters = config.isUrlParameters; + } + if (isDefined(config.isModuleStack) && isBoolean(config.isModuleStack)) { + errConfigObj.isModuleStack = config.isModuleStack; + } + if (isDefined(config.isModuleError) && isBoolean(config.isModuleError)) { + errConfigObj.isModuleError = config.isModuleError; } } else { - return minErrConfig; + return errConfigObj; } } @@ -50,6 +67,7 @@ function isValidObjectMaxDepth(maxDepth) { return isNumber(maxDepth) && maxDepth > 0; } + /** * @description * @@ -87,7 +105,7 @@ function minErr(module, ErrorConstructor) { template = arguments[1], message = '[' + (module ? module + ':' : '') + code + '] ', templateArgs = sliceArgs(arguments, 2).map(function(arg) { - return toDebugString(arg, minErrConfig.objectMaxDepth); + return toDebugString(arg, errConfigObj.objectMaxDepth); }), paramPrefix, i; @@ -103,9 +121,10 @@ function minErr(module, ErrorConstructor) { message += '\nhttp://errors.angularjs.org/"NG_VERSION_FULL"/' + (module ? module + '/' : '') + code; - - for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { - message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]); + if (errConfigObj.isUrlParameters) { + for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { + message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]); + } } return new ErrorConstructor(message); diff --git a/test/.eslintrc.json b/test/.eslintrc.json index 6401cb26f590..facd3ca166dc 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -25,7 +25,7 @@ /* angular.js */ "angular": false, - "minErrConfig": false, + "errConfigObj": false, "errorHandlingConfig": false, "msie": false, "jqLite": false, diff --git a/test/auto/injectorSpec.js b/test/auto/injectorSpec.js index 7f5254e201e6..fba755fede19 100644 --- a/test/auto/injectorSpec.js +++ b/test/auto/injectorSpec.js @@ -940,6 +940,13 @@ describe('injector', function() { describe('error handling', function() { + var obj = angular.copy(errorHandlingConfig()); + + afterEach(function() { + errorHandlingConfig({isModuleStack:obj.isModuleStack}); + errorHandlingConfig({isModuleError:obj.isModuleError}); + }); + it('should handle wrong argument type', function() { expect(function() { createInjector([ @@ -957,6 +964,33 @@ describe('injector', function() { }).toThrowMinErr('$injector', 'modulerr', /Failed to instantiate module .+ due to:\n.*MyError/); }); + it('should just rethrow the exception if ´isModuleError´ is false', function() { + expect(function() { + errorHandlingConfig({isModuleError:false}); + createInjector([function() { + throw new Error('MyError'); + }], {}); + }).toThrow(new Error('MyError')); + }); + + it('should not generate the stack for the failed module if ´isModuleStack´ is false', function() { + errorHandlingConfig({isModuleStack:false}); + expect(function() { + createInjector([function() { + throw new Error('MyError'); + }], {}); + + }).not.toThrowMinErr('$injector', 'modulerr', /(\w+@|at\s+\w+).+:\d+:\d+/); + }); + + it('should generate the stack if ´isModuleStack´ option is true', function() { + errorHandlingConfig({isModuleStack:true}); + expect(function() { + createInjector([function() { + throw new Error('MyError'); + }], {}); + }).toThrowMinErr('$injector', 'modulerr', /(\w+@|at\s+\w+).+:\d+:\d+/); + }); it('should decorate the missing service error with module name', function() { angular.module('TestModule', [], function(xyzzy) {}); diff --git a/test/minErrSpec.js b/test/minErrSpec.js index aae001cba415..4b505c8b9a5a 100644 --- a/test/minErrSpec.js +++ b/test/minErrSpec.js @@ -1,33 +1,86 @@ 'use strict'; describe('errors', function() { - var originalObjectMaxDepthInErrorMessage = minErrConfig.objectMaxDepth; + var originalObj = angular.copy(errConfigObj); afterEach(function() { - minErrConfig.objectMaxDepth = originalObjectMaxDepthInErrorMessage; + errConfigObj.objectMaxDepth = originalObj.objectMaxDepth; + errConfigObj.isUrlParameters = originalObj.isUrlParameters; + errConfigObj.isModuleError = originalObj.isModuleError; + errConfigObj.isModuleStack = originalObj.isModuleStack; }); describe('errorHandlingConfig', function() { - it('should get default objectMaxDepth', function() { - expect(errorHandlingConfig().objectMaxDepth).toBe(5); + describe('objectMaxDepth',function() { + it('should get default objectMaxDepth', function() { + expect(errorHandlingConfig().objectMaxDepth).toBe(5); + }); + + it('should set objectMaxDepth', function() { + errorHandlingConfig({objectMaxDepth: 3}); + expect(errorHandlingConfig().objectMaxDepth).toBe(3); + }); + + it('should not change objectMaxDepth when undefined is supplied', function() { + errorHandlingConfig({objectMaxDepth: undefined}); + expect(errorHandlingConfig().objectMaxDepth).toBe(originalObj.objectMaxDepth); + }); + + they('should set objectMaxDepth to NaN when $prop is supplied', + [NaN, null, true, false, -1, 0], function(maxDepth) { + errorHandlingConfig({objectMaxDepth: maxDepth}); + expect(errorHandlingConfig().objectMaxDepth).toBeNaN(); + } + ); }); - it('should set objectMaxDepth', function() { - errorHandlingConfig({objectMaxDepth: 3}); - expect(errorHandlingConfig().objectMaxDepth).toBe(3); - }); - it('should not change objectMaxDepth when undefined is supplied', function() { - errorHandlingConfig({objectMaxDepth: undefined}); - expect(errorHandlingConfig().objectMaxDepth).toBe(originalObjectMaxDepthInErrorMessage); + describe('isUrlParameters',function() { + it('should get default isUrlParameters', function() { + expect(errorHandlingConfig().isUrlParameters).toBe(true); + }); + it('should set isUrlParameters', function() { + errorHandlingConfig({isUrlParameters:false}); + expect(errorHandlingConfig().isUrlParameters).toBe(false); + errorHandlingConfig({isUrlParameters:true}); + expect(errorHandlingConfig().isUrlParameters).toBe(true); + }); + it('should not change its value when non-boolean is supplied', function() { + errorHandlingConfig({isUrlParameters:123}); + expect(errorHandlingConfig().isUrlParameters).toBe(originalObj.isUrlParameters); + }); + }); + describe('isModuleStack',function() { + it('should get default isModuleStack', function() { + expect(errorHandlingConfig().isModuleStack).toBe(true); + }); + it('should set isModuleStack', function() { + errorHandlingConfig({isModuleStack:false}); + expect(errorHandlingConfig().isModuleStack).toBe(false); + errorHandlingConfig({isModuleStack:true}); + expect(errorHandlingConfig().isModuleStack).toBe(true); + }); + it('should not change its value when non-boolean is supplied', function() { + errorHandlingConfig({isModuleStack:123}); + expect(errorHandlingConfig().isModuleStack).toBe(originalObj.isModuleStack); + }); + }); + describe('isModuleError',function() { + it('should get default isModuleError', function() { + expect(errorHandlingConfig().isModuleError).toBe(true); + }); + it('should set isModuleError', function() { + errorHandlingConfig({isModuleError:false}); + expect(errorHandlingConfig().isModuleError).toBe(false); + errorHandlingConfig({isModuleError:true}); + expect(errorHandlingConfig().isModuleError).toBe(true); + }); + it('should not change its value when non-boolean is supplied', function() { + errorHandlingConfig({isModuleError:123}); + expect(errorHandlingConfig().isModuleError).toBe(originalObj.isModuleError); + }); }); - they('should set objectMaxDepth to NaN when $prop is supplied', - [NaN, null, true, false, -1, 0], function(maxDepth) { - errorHandlingConfig({objectMaxDepth: maxDepth}); - expect(errorHandlingConfig().objectMaxDepth).toBeNaN(); - } - ); }); describe('minErr', function() { @@ -164,5 +217,13 @@ describe('errors', function() { expect(testError('acode', 'aproblem', 'a', 'b', 'value with space').message) .toMatch(/^[\s\S]*\?p0=a&p1=b&p2=value%20with%20space$/); }); + + it('should not generate URL query parameters when isUrlParameters is false', function() { + + errorHandlingConfig({isUrlParameters:false}); + expect(testError('acode', 'aproblem', 'a', 'b', 'c').message) + .not.toContain('?p0=a&p1=b&p2=c'); + }); + }); });