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

Commit

Permalink
feat(ngError): add error message compression and better error messages
Browse files Browse the repository at this point in the history
- add toThrowNg matcher
  • Loading branch information
IgorMinar authored and vojtajina committed May 25, 2013
1 parent 88eaea8 commit b8ea7f6
Show file tree
Hide file tree
Showing 35 changed files with 315 additions and 160 deletions.
1 change: 1 addition & 0 deletions angularFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ angularFiles = {
'src/AngularPublic.js',
'src/jqLite.js',
'src/apis.js',
'src/ngError.js',

'src/auto/injector.js',

Expand Down
11 changes: 7 additions & 4 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ function nextUid() {

/**
* Set or clear the hashkey for an object.
* @param obj object
* @param obj object
* @param h the hashkey (!truthy to delete the hashkey)
*/
function setHashKey(obj, h) {
Expand Down Expand Up @@ -590,7 +590,10 @@ function isLeafNode (node) {
* @returns {*} The copy or updated `destination`, if `destination` was specified.
*/
function copy(source, destination){
if (isWindow(source) || isScope(source)) throw Error("Can't copy Window or Scope");
if (isWindow(source) || isScope(source)) {
throw ngError(43, "Can't copy! Making copies of Window or Scope instances is not supported.");
}

if (!destination) {
destination = source;
if (source) {
Expand All @@ -603,7 +606,7 @@ function copy(source, destination){
}
}
} else {
if (source === destination) throw Error("Can't copy equivalent objects or arrays");
if (source === destination) throw ngError(44, "Can't copy! Source and destination are identical.");
if (isArray(source)) {
destination.length = 0;
for ( var i = 0; i < source.length; i++) {
Expand Down Expand Up @@ -1055,7 +1058,7 @@ function bindJQuery() {
*/
function assertArg(arg, name, reason) {
if (!arg) {
throw new Error("Argument '" + (name || '?') + "' is " + (reason || "required"));
throw ngError(45, "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
}
return arg;
}
Expand Down
12 changes: 6 additions & 6 deletions src/auto/injector.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ function createInjector(modulesToLoad) {
},
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function() {
throw Error("Unknown provider: " + path.join(' <- '));
throw ngError(1, "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
Expand Down Expand Up @@ -455,7 +455,7 @@ function createInjector(modulesToLoad) {
provider_ = providerInjector.instantiate(provider_);
}
if (!provider_.$get) {
throw Error('Provider ' + name + ' must define $get factory method.');
throw ngError(2, "Provider '{0}' must define $get factory method.", name);
}
return providerCache[name + providerSuffix] = provider_;
}
Expand Down Expand Up @@ -536,12 +536,9 @@ function createInjector(modulesToLoad) {
function createInternalInjector(cache, factory) {

function getService(serviceName) {
if (typeof serviceName !== 'string') {
throw Error('Service name expected');
}
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw Error('Circular dependency: ' + path.join(' <- '));
throw ngError(4, 'Circular dependency found: {0}', path.join(' <- '));
}
return cache[serviceName];
} else {
Expand All @@ -563,6 +560,9 @@ function createInjector(modulesToLoad) {

for(i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
if (typeof key !== 'string') {
throw ngError(3, 'Incorrect injection token! Expected service name as string, got {0}', key);
}
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
Expand Down
14 changes: 7 additions & 7 deletions src/jqLite.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ function JQLite(element) {
}
if (!(this instanceof JQLite)) {
if (isString(element) && element.charAt(0) != '<') {
throw Error('selectors not implemented');
throw ngError(46, 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
}
return new JQLite(element);
}
Expand Down Expand Up @@ -627,22 +627,22 @@ forEach({
}
}
return false;
};
};

events[type] = [];
// Refer to jQuery's implementation of mouseenter & mouseleave

// Refer to jQuery's implementation of mouseenter & mouseleave
// Read about mouseenter and mouseleave:
// http://www.quirksmode.org/js/events_mouse.html#link8
var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}
var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"};

bindFn(element, eventmap[type], function(event) {
var ret, target = this, related = event.relatedTarget;
// For mousenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
if ( !related || (related !== target && !contains(target, related)) ){
handle(event, type);
}

}
});

} else {
Expand Down
2 changes: 1 addition & 1 deletion src/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function setupModuleLoader(window) {
}
return ensure(modules, name, function() {
if (!requires) {
throw Error('No module: ' + name);
throw ngError(47, "Module '{0}' is not available! You either misspelled the module name or forgot to load it.", name);
}

/** @type {!Array.<Array.<*>>} */
Expand Down
2 changes: 1 addition & 1 deletion src/ng/cacheFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function $CacheFactoryProvider() {

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

var size = 0,
Expand Down
33 changes: 13 additions & 20 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
*/


var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: ';


/**
* @ngdoc function
* @name ng.$compile
Expand Down Expand Up @@ -155,7 +152,6 @@ function $CompileProvider($provide) {
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ',
urlSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/;


Expand Down Expand Up @@ -392,10 +388,6 @@ function $CompileProvider($provide) {
};
}

function wrongMode(localName, mode) {
throw Error("Unsupported '" + mode + "' for '" + localName + "'.");
}

function safeAddClass($element, className) {
try {
$element.addClass(className);
Expand Down Expand Up @@ -669,7 +661,7 @@ function $CompileProvider($provide) {
compileNode = $template[0];

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

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

var parentScope = scope.$parent || scope;

forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
var match = definiton.match(LOCAL_REGEXP) || [],
forEach(newIsolateScopeDirective.scope, function(definition, scopeName) {
var match = definition.match(LOCAL_REGEXP) || [],
attrName = match[3] || scopeName,
optional = (match[2] == '?'),
mode = match[1], // @, =, or &
Expand Down Expand Up @@ -815,8 +807,8 @@ function $CompileProvider($provide) {
parentSet = parentGet.assign || function() {
// reset the change, or we will throw this exception on every $digest
lastValue = scope[scopeName] = parentGet(parentScope);
throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] +
' (directive: ' + newIsolateScopeDirective.name + ')');
throw ngError(14, "Expression '{0}' used with directive '{1}' is non-assignable!",
attrs[attrName], newIsolateScopeDirective.name);
};
lastValue = scope[scopeName] = parentGet(parentScope);
scope.$watch(function parentValueWatch() {
Expand Down Expand Up @@ -846,8 +838,8 @@ function $CompileProvider($provide) {
}

default: {
throw Error('Invalid isolate scope definition for directive ' +
newIsolateScopeDirective.name + ': ' + definiton);
throw ngError(15, "Invalid isolate scope definition for directive '{0}'. Definition: {... {1}: '{2}' ...}",
newIsolateScopeDirective.name, scopeName, definition);
}
}
});
Expand Down Expand Up @@ -1000,7 +992,8 @@ function $CompileProvider($provide) {
compileNode = $template[0];

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

tempTemplateAttrs = {$attr: {}};
Expand Down Expand Up @@ -1037,7 +1030,7 @@ function $CompileProvider($provide) {
linkQueue = null;
}).
error(function(response, code, headers, config) {
throw Error('Failed to load template: ' + config.url);
throw ngError(17, 'Failed to load template: {0}', config.url);
});

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

function assertNoDuplicate(what, previousDirective, directive, element) {
if (previousDirective) {
throw Error('Multiple directives [' + previousDirective.name + ', ' +
directive.name + '] asking for ' + what + ' on: ' + startingTag(element));
throw ngError(18, 'Multiple directives [{0}, {1}] asking for {2} on: {3}',
previousDirective.name, directive.name, what, startingTag(element));
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/ng/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,8 @@ function $ControllerProvider() {
instance = $injector.instantiate(expression, locals);

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

locals.$scope[identifier] = instance;
Expand Down
7 changes: 4 additions & 3 deletions src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
var patternObj = scope.$eval(pattern);

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

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

/**
Expand Down
12 changes: 6 additions & 6 deletions src/ng/directive/ngRepeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
hashFnLocals = {$id: hashKey};

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

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

match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
if (!match) {
throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
lhs + "'.");
throw ngError(8, "ngRepeat error! '_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
lhs);
}
valueIdentifier = match[3] || match[1];
keyIdentifier = match[2];
Expand Down Expand Up @@ -244,8 +244,8 @@ var ngRepeatDirective = ['$parse', '$animator', function($parse, $animator) {
if (block && block.element) lastBlockMap[block.id] = block;
});
// This is a duplicate and we need to throw an error
throw new Error('Duplicates in a repeater are not allowed. Repeater: ' + expression +
' key: ' + trackById);
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}",
expression, trackById);
} else {
// new never before seen block
nextBlockOrder[index] = { id: trackById };
Expand Down
8 changes: 4 additions & 4 deletions src/ng/directive/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
var match;

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

var displayFn = $parse(match[2] || match[1]),
Expand Down Expand Up @@ -357,7 +357,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
for (var trackIndex = 0; trackIndex < collection.length; trackIndex++) {
locals[valueName] = collection[trackIndex];
if (trackFn(scope, locals) == key) break;
}
}
} else {
locals[valueName] = collection[key];
}
Expand Down
2 changes: 1 addition & 1 deletion src/ng/httpBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ var XHR = window.XMLHttpRequest || function() {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
throw new Error("This browser does not support XMLHttpRequest.");
throw ngError(19, "This browser does not support XMLHttpRequest.");
};


Expand Down
2 changes: 1 addition & 1 deletion src/ng/interpolate.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ function $InterpolateProvider() {
return concat.join('');
}
catch(err) {
var newErr = new Error('Error while interpolating: ' + text + '\n' + err.toString());
var newErr = ngError(48, "$interpolate error! Can't interpolate: {0}\n{1}", text, err.toString());
$exceptionHandler(newErr);
}
};
Expand Down
6 changes: 3 additions & 3 deletions src/ng/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ function LocationHtml5Url(appBase, basePrefix) {
matchUrl(url, parsed);
var pathUrl = beginsWith(appBaseNoFile, url);
if (!isString(pathUrl)) {
throw Error('Invalid url "' + url + '", missing path prefix "' + appBaseNoFile + '".');
throw ngError(21, '$location error! Invalid url "{0}", missing path prefix "{1}".', url, appBaseNoFile);
}
matchAppUrl(pathUrl, parsed);
extend(this, parsed);
Expand Down Expand Up @@ -157,11 +157,11 @@ function LocationHashbangUrl(appBase, hashPrefix) {
matchUrl(url, this);
var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
if (!isString(withoutBaseUrl)) {
throw new Error('Invalid url "' + url + '", does not start with "' + appBase + '".');
throw ngError(22, '$location error! Invalid url "{0}", does not start with "{1}".', url, appBase);
}
var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' ? beginsWith(hashPrefix, withoutBaseUrl) : withoutBaseUrl;
if (!isString(withoutHashUrl)) {
throw new Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '".');
throw ngError(49, '$location error! Invalid url "{0}", missing hash prefix "{1}".', url, hashPrefix);
}
matchAppUrl(withoutHashUrl, this);
this.$$compose();
Expand Down
Loading

0 comments on commit b8ea7f6

Please sign in to comment.