Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit b8ea7f6

Browse files
IgorMinarvojtajina
authored andcommitted
feat(ngError): add error message compression and better error messages
- add toThrowNg matcher
1 parent 88eaea8 commit b8ea7f6

35 files changed

+315
-160
lines changed

angularFiles.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ angularFiles = {
55
'src/AngularPublic.js',
66
'src/jqLite.js',
77
'src/apis.js',
8+
'src/ngError.js',
89

910
'src/auto/injector.js',
1011

src/Angular.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ function nextUid() {
215215

216216
/**
217217
* Set or clear the hashkey for an object.
218-
* @param obj object
218+
* @param obj object
219219
* @param h the hashkey (!truthy to delete the hashkey)
220220
*/
221221
function setHashKey(obj, h) {
@@ -590,7 +590,10 @@ function isLeafNode (node) {
590590
* @returns {*} The copy or updated `destination`, if `destination` was specified.
591591
*/
592592
function copy(source, destination){
593-
if (isWindow(source) || isScope(source)) throw Error("Can't copy Window or Scope");
593+
if (isWindow(source) || isScope(source)) {
594+
throw ngError(43, "Can't copy! Making copies of Window or Scope instances is not supported.");
595+
}
596+
594597
if (!destination) {
595598
destination = source;
596599
if (source) {
@@ -603,7 +606,7 @@ function copy(source, destination){
603606
}
604607
}
605608
} else {
606-
if (source === destination) throw Error("Can't copy equivalent objects or arrays");
609+
if (source === destination) throw ngError(44, "Can't copy! Source and destination are identical.");
607610
if (isArray(source)) {
608611
destination.length = 0;
609612
for ( var i = 0; i < source.length; i++) {
@@ -1055,7 +1058,7 @@ function bindJQuery() {
10551058
*/
10561059
function assertArg(arg, name, reason) {
10571060
if (!arg) {
1058-
throw new Error("Argument '" + (name || '?') + "' is " + (reason || "required"));
1061+
throw ngError(45, "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
10591062
}
10601063
return arg;
10611064
}

src/auto/injector.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ function createInjector(modulesToLoad) {
422422
},
423423
providerInjector = (providerCache.$injector =
424424
createInternalInjector(providerCache, function() {
425-
throw Error("Unknown provider: " + path.join(' <- '));
425+
throw ngError(1, "Unknown provider: {0}", path.join(' <- '));
426426
})),
427427
instanceCache = {},
428428
instanceInjector = (instanceCache.$injector =
@@ -455,7 +455,7 @@ function createInjector(modulesToLoad) {
455455
provider_ = providerInjector.instantiate(provider_);
456456
}
457457
if (!provider_.$get) {
458-
throw Error('Provider ' + name + ' must define $get factory method.');
458+
throw ngError(2, "Provider '{0}' must define $get factory method.", name);
459459
}
460460
return providerCache[name + providerSuffix] = provider_;
461461
}
@@ -536,12 +536,9 @@ function createInjector(modulesToLoad) {
536536
function createInternalInjector(cache, factory) {
537537

538538
function getService(serviceName) {
539-
if (typeof serviceName !== 'string') {
540-
throw Error('Service name expected');
541-
}
542539
if (cache.hasOwnProperty(serviceName)) {
543540
if (cache[serviceName] === INSTANTIATING) {
544-
throw Error('Circular dependency: ' + path.join(' <- '));
541+
throw ngError(4, 'Circular dependency found: {0}', path.join(' <- '));
545542
}
546543
return cache[serviceName];
547544
} else {
@@ -563,6 +560,9 @@ function createInjector(modulesToLoad) {
563560

564561
for(i = 0, length = $inject.length; i < length; i++) {
565562
key = $inject[i];
563+
if (typeof key !== 'string') {
564+
throw ngError(3, 'Incorrect injection token! Expected service name as string, got {0}', key);
565+
}
566566
args.push(
567567
locals && locals.hasOwnProperty(key)
568568
? locals[key]

src/jqLite.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ function JQLite(element) {
153153
}
154154
if (!(this instanceof JQLite)) {
155155
if (isString(element) && element.charAt(0) != '<') {
156-
throw Error('selectors not implemented');
156+
throw ngError(46, 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
157157
}
158158
return new JQLite(element);
159159
}
@@ -627,22 +627,22 @@ forEach({
627627
}
628628
}
629629
return false;
630-
};
630+
};
631631

632632
events[type] = [];
633-
634-
// Refer to jQuery's implementation of mouseenter & mouseleave
633+
634+
// Refer to jQuery's implementation of mouseenter & mouseleave
635635
// Read about mouseenter and mouseleave:
636636
// http://www.quirksmode.org/js/events_mouse.html#link8
637-
var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}
637+
var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"};
638+
638639
bindFn(element, eventmap[type], function(event) {
639640
var ret, target = this, related = event.relatedTarget;
640641
// For mousenter/leave call the handler if related is outside the target.
641642
// NB: No relatedTarget if the mouse left/entered the browser window
642643
if ( !related || (related !== target && !contains(target, related)) ){
643644
handle(event, type);
644-
}
645-
645+
}
646646
});
647647

648648
} else {

src/loader.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ function setupModuleLoader(window) {
7070
}
7171
return ensure(modules, name, function() {
7272
if (!requires) {
73-
throw Error('No module: ' + name);
73+
throw ngError(47, "Module '{0}' is not available! You either misspelled the module name or forgot to load it.", name);
7474
}
7575

7676
/** @type {!Array.<Array.<*>>} */

src/ng/cacheFactory.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function $CacheFactoryProvider() {
2828

2929
function cacheFactory(cacheId, options) {
3030
if (cacheId in caches) {
31-
throw Error('cacheId ' + cacheId + ' taken');
31+
throw ngError(10, "CacheId '{0}' is already taken!", cacheId);
3232
}
3333

3434
var size = 0,

src/ng/compile.js

+13-20
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818
*/
1919

2020

21-
var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';
22-
23-
2421
/**
2522
* @ngdoc function
2623
* @name ng.$compile
@@ -155,7 +152,6 @@ function $CompileProvider($provide) {
155152
Suffix = 'Directive',
156153
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
157154
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
158-
MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ',
159155
urlSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/;
160156

161157

@@ -392,10 +388,6 @@ function $CompileProvider($provide) {
392388
};
393389
}
394390

395-
function wrongMode(localName, mode) {
396-
throw Error("Unsupported '" + mode + "' for '" + localName + "'.");
397-
}
398-
399391
function safeAddClass($element, className) {
400392
try {
401393
$element.addClass(className);
@@ -669,7 +661,7 @@ function $CompileProvider($provide) {
669661
compileNode = $template[0];
670662

671663
if ($template.length != 1 || compileNode.nodeType !== 1) {
672-
throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue);
664+
throw ngError(12, "Template for directive '{0}' must have exactly one root element.", directiveName);
673665
}
674666

675667
replaceWith(jqCollection, $compileNode, compileNode);
@@ -755,7 +747,7 @@ function $CompileProvider($provide) {
755747
}
756748
value = $element[retrievalMethod]('$' + require + 'Controller');
757749
if (!value && !optional) {
758-
throw Error("No controller: " + require);
750+
throw ngError(13, "Controller '{0}', required by directive '{1}', can't be found!", require, directiveName);
759751
}
760752
return value;
761753
} else if (isArray(require)) {
@@ -783,8 +775,8 @@ function $CompileProvider($provide) {
783775

784776
var parentScope = scope.$parent || scope;
785777

786-
forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
787-
var match = definiton.match(LOCAL_REGEXP) || [],
778+
forEach(newIsolateScopeDirective.scope, function(definition, scopeName) {
779+
var match = definition.match(LOCAL_REGEXP) || [],
788780
attrName = match[3] || scopeName,
789781
optional = (match[2] == '?'),
790782
mode = match[1], // @, =, or &
@@ -815,8 +807,8 @@ function $CompileProvider($provide) {
815807
parentSet = parentGet.assign || function() {
816808
// reset the change, or we will throw this exception on every $digest
817809
lastValue = scope[scopeName] = parentGet(parentScope);
818-
throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] +
819-
' (directive: ' + newIsolateScopeDirective.name + ')');
810+
throw ngError(14, "Expression '{0}' used with directive '{1}' is non-assignable!",
811+
attrs[attrName], newIsolateScopeDirective.name);
820812
};
821813
lastValue = scope[scopeName] = parentGet(parentScope);
822814
scope.$watch(function parentValueWatch() {
@@ -846,8 +838,8 @@ function $CompileProvider($provide) {
846838
}
847839

848840
default: {
849-
throw Error('Invalid isolate scope definition for directive ' +
850-
newIsolateScopeDirective.name + ': ' + definiton);
841+
throw ngError(15, "Invalid isolate scope definition for directive '{0}'. Definition: {... {1}: '{2}' ...}",
842+
newIsolateScopeDirective.name, scopeName, definition);
851843
}
852844
}
853845
});
@@ -1000,7 +992,8 @@ function $CompileProvider($provide) {
1000992
compileNode = $template[0];
1001993

1002994
if ($template.length != 1 || compileNode.nodeType !== 1) {
1003-
throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content);
995+
throw ngError(16, "Template for directive '{0}' must have exactly one root element. Template: {1}",
996+
origAsyncDirective.name, templateUrl);
1004997
}
1005998

1006999
tempTemplateAttrs = {$attr: {}};
@@ -1037,7 +1030,7 @@ function $CompileProvider($provide) {
10371030
linkQueue = null;
10381031
}).
10391032
error(function(response, code, headers, config) {
1040-
throw Error('Failed to load template: ' + config.url);
1033+
throw ngError(17, 'Failed to load template: {0}', config.url);
10411034
});
10421035

10431036
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
@@ -1065,8 +1058,8 @@ function $CompileProvider($provide) {
10651058

10661059
function assertNoDuplicate(what, previousDirective, directive, element) {
10671060
if (previousDirective) {
1068-
throw Error('Multiple directives [' + previousDirective.name + ', ' +
1069-
directive.name + '] asking for ' + what + ' on: ' + startingTag(element));
1061+
throw ngError(18, 'Multiple directives [{0}, {1}] asking for {2} on: {3}',
1062+
previousDirective.name, directive.name, what, startingTag(element));
10701063
}
10711064
}
10721065

src/ng/controller.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,8 @@ function $ControllerProvider() {
7474
instance = $injector.instantiate(expression, locals);
7575

7676
if (identifier) {
77-
if (typeof locals.$scope !== 'object') {
78-
throw new Error('Can not export controller as "' + identifier + '". ' +
79-
'No scope object provided!');
77+
if (!(locals && typeof locals.$scope == 'object')) {
78+
throw ngError(47, "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", constructor || expression.name, identifier);
8079
}
8180

8281
locals.$scope[identifier] = instance;

src/ng/directive/input.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
475475
var patternObj = scope.$eval(pattern);
476476

477477
if (!patternObj || !patternObj.test) {
478-
throw new Error('Expected ' + pattern + ' to be a RegExp but was ' + patternObj);
478+
throw ngError(5, 'ngPattern error! Expected {0} to be a RegExp but was {1}. Element: {2}',
479+
pattern, patternObj, startingTag(element));
479480
}
480481
return validate(patternObj, value);
481482
};
@@ -918,8 +919,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
918919
ngModelSet = ngModelGet.assign;
919920

920921
if (!ngModelSet) {
921-
throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + $attr.ngModel +
922-
' (' + startingTag($element) + ')');
922+
throw ngError(6, "ngModel error! Expression '{0}' is non-assignable. Element: {1}", $attr.ngModel,
923+
startingTag($element));
923924
}
924925

925926
/**

src/ng/directive/ngRepeat.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
157157
hashFnLocals = {$id: hashKey};
158158

159159
if (!match) {
160-
throw Error("Expected ngRepeat in form of '_item_ in _collection_[ track by _id_]' but got '" +
161-
expression + "'.");
160+
throw ngError(7, "ngRepeat error! Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.",
161+
expression);
162162
}
163163

164164
lhs = match[1];
@@ -182,8 +182,8 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
182182

183183
match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
184184
if (!match) {
185-
throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
186-
lhs + "'.");
185+
throw ngError(8, "ngRepeat error! '_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
186+
lhs);
187187
}
188188
valueIdentifier = match[3] || match[1];
189189
keyIdentifier = match[2];
@@ -244,8 +244,8 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
244244
if (block && block.element) lastBlockMap[block.id] = block;
245245
});
246246
// This is a duplicate and we need to throw an error
247-
throw new Error('Duplicates in a repeater are not allowed. Repeater: ' + expression +
248-
' key: ' + trackById);
247+
throw ngError(50, "ngRepeat error! Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}",
248+
expression, trackById);
249249
} else {
250250
// new never before seen block
251251
nextBlockOrder[index] = { id: trackById };

src/ng/directive/select.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,9 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
300300
var match;
301301

302302
if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
303-
throw Error(
304-
"Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_ (track by _expr_)?'" +
305-
" but got '" + optionsExp + "'.");
303+
throw ngError(9,
304+
"ngOptions error! Expected expression in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '{0}'. Element: {1}",
305+
optionsExp, startingTag(selectElement));
306306
}
307307

308308
var displayFn = $parse(match[2] || match[1]),
@@ -357,7 +357,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
357357
for (var trackIndex = 0; trackIndex < collection.length; trackIndex++) {
358358
locals[valueName] = collection[trackIndex];
359359
if (trackFn(scope, locals) == key) break;
360-
}
360+
}
361361
} else {
362362
locals[valueName] = collection[key];
363363
}

src/ng/httpBackend.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ var XHR = window.XMLHttpRequest || function() {
22
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
33
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
44
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
5-
throw new Error("This browser does not support XMLHttpRequest.");
5+
throw ngError(19, "This browser does not support XMLHttpRequest.");
66
};
77

88

src/ng/interpolate.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ function $InterpolateProvider() {
139139
return concat.join('');
140140
}
141141
catch(err) {
142-
var newErr = new Error('Error while interpolating: ' + text + '\n' + err.toString());
142+
var newErr = ngError(48, "$interpolate error! Can't interpolate: {0}\n{1}", text, err.toString());
143143
$exceptionHandler(newErr);
144144
}
145145
};

src/ng/location.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ function LocationHtml5Url(appBase, basePrefix) {
9595
matchUrl(url, parsed);
9696
var pathUrl = beginsWith(appBaseNoFile, url);
9797
if (!isString(pathUrl)) {
98-
throw Error('Invalid url "' + url + '", missing path prefix "' + appBaseNoFile + '".');
98+
throw ngError(21, '$location error! Invalid url "{0}", missing path prefix "{1}".', url, appBaseNoFile);
9999
}
100100
matchAppUrl(pathUrl, parsed);
101101
extend(this, parsed);
@@ -157,11 +157,11 @@ function LocationHashbangUrl(appBase, hashPrefix) {
157157
matchUrl(url, this);
158158
var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
159159
if (!isString(withoutBaseUrl)) {
160-
throw new Error('Invalid url "' + url + '", does not start with "' + appBase + '".');
160+
throw ngError(22, '$location error! Invalid url "{0}", does not start with "{1}".', url, appBase);
161161
}
162162
var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' ? beginsWith(hashPrefix, withoutBaseUrl) : withoutBaseUrl;
163163
if (!isString(withoutHashUrl)) {
164-
throw new Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '".');
164+
throw ngError(49, '$location error! Invalid url "{0}", missing hash prefix "{1}".', url, hashPrefix);
165165
}
166166
matchAppUrl(withoutHashUrl, this);
167167
this.$$compose();

0 commit comments

Comments
 (0)