From a2f6528268561c85a2b48e64ea4c0fffce0a6214 Mon Sep 17 00:00:00 2001 From: Chakib Benziane Date: Fri, 6 Mar 2015 12:25:02 +0100 Subject: [PATCH] inject $injector to window --- devtoolsBackground.js | 3 + dist/hint.js | 2491 +++++++++++++++++++++++++++-------------- 2 files changed, 1675 insertions(+), 819 deletions(-) diff --git a/devtoolsBackground.js b/devtoolsBackground.js index 7d23d84..083cfe9 100644 --- a/devtoolsBackground.js +++ b/devtoolsBackground.js @@ -6,8 +6,11 @@ var getPanelContents = function () { if (window.angular && $0) { //TODO: can we move this scope export into updateElementProperties var scope = window.angular.element($0).scope(); + var injector = window.angular.element($0).injector(); // Export $scope to the console window.$scope = scope; + // Export $injector to the console + window.$injector = injector; return (function (scope) { var panelContents = { __private__: {} diff --git a/dist/hint.js b/dist/hint.js index 655c592..1e36a58 100755 --- a/dist/hint.js +++ b/dist/hint.js @@ -473,33 +473,21 @@ angular.Module; },{}],3:[function(require,module,exports){ -'use strict'; - -// Create pipe for all hint messages from different modules -window.angular.hint = require('angular-hint-log'); +//Create pipe for all hint messages from different modules +angular.hint = require('angular-hint-log'); // Load angular hint modules require('angular-hint-controllers'); -// require('angular-hint-directives'); -// require('angular-hint-dom'); +require('angular-hint-directives'); +require('angular-hint-dom'); require('angular-hint-events'); -// require('angular-hint-interpolation'); +require('angular-hint-interpolation'); require('angular-hint-modules'); -require('angular-hint-scopes'); // List of all possible modules // The default ng-hint behavior loads all modules -var allModules = [ -'ngHintControllers', -// 'ngHintDirectives', -// 'ngHintDom', - 'ngHintEvents', -// 'ngHintInterpolation', - 'ngHintModules', - 'ngHintScopes' -]; - -var SEVERITY_WARNING = 2; +var allModules = ['ngHintControllers', 'ngHintDirectives', 'ngHintDom', 'ngHintEvents', + 'ngHintInterpolation', 'ngHintModules']; // Determine whether this run is by protractor. // If protractor is running, the bootstrap will already be deferred. @@ -537,9 +525,6 @@ function maybeBootstrap() { function loadModules() { var modules = [], elt; - if (angular.version.minor < 2) { - return modules; - } if ((elt = document.querySelector('[ng-hint-include]'))) { modules = hintModulesFromElement(elt); @@ -548,8 +533,8 @@ function loadModules() { } else if (document.querySelector('[ng-hint]')) { modules = allModules; } else { - angular.hint.logMessage('General', 'ngHint is included on the page, but is not active because ' + - 'there is no `ng-hint` attribute present', SEVERITY_WARNING); + angular.hint.logMessage('##General## ngHint is included on the page, but is not active because'+ + ' there is no `ng-hint` attribute present'); } return modules; } @@ -566,7 +551,7 @@ function hintModulesFromElement (elt) { return selectedModules.map(hintModuleName).filter(function (name) { return (allModules.indexOf(name) > -1) || - angular.hint.logMessage('General', 'Module ' + name + ' could not be found', SEVERITY_WARNING); + angular.hint.logMessage('##General## Module ' + name + ' could not be found'); }); } @@ -578,275 +563,1368 @@ function title(str) { return str[0].toUpperCase() + str.substr(1); } -var LEVELS = [ - 'error', - 'warning', - 'suggestion' -]; +function flush() { + var log = angular.hint.flush(), groups = Object.keys(log); + for(var i = 0, ii = groups.length; i < ii; i++) { + console.groupCollapsed? console.groupCollapsed('Angular Hint: ' + groups[i]) : + console.log('Angular Hint: ' + groups[i]); + var messages = Object.keys(log[groups[i]]); + for(var j = 0, jj = messages.length; j < jj; j++) { + console.log(messages[j]); + } + console.groupEnd && console.groupEnd(); + } +} +setInterval(flush, 5); -},{"angular-hint-controllers":4,"angular-hint-events":6,"angular-hint-log":16,"angular-hint-modules":17,"angular-hint-scopes":35}],4:[function(require,module,exports){ +},{"angular-hint-controllers":4,"angular-hint-directives":5,"angular-hint-dom":35,"angular-hint-events":37,"angular-hint-interpolation":48,"angular-hint-log":59,"angular-hint-modules":60}],4:[function(require,module,exports){ 'use strict'; -var hint = angular.hint = require('angular-hint-log'); - -var MODULE_NAME = 'Controllers', - CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/, - CATEGORY_CONTROLLER_NAME = 'Name controllers according to best practices', - CATEGORY_GLOBAL_CONTROLLER = 'Using global functions as controllers is against Angular best practices and depricated in Angular 1.3 and up', - SEVERITY_ERROR = 1, - SEVERITY_WARNING = 2; - -// local state -var nameToControllerMap = {}; +var nameToControllerMatch = {}; +var controllers = {}; +var hintLog = angular.hint = require('angular-hint-log'); /** * Decorates $controller with a patching function to * log a message if the controller is instantiated on the window */ angular.module('ngHintControllers', []). - config(['$provide', function ($provide) { - $provide.decorator('$controller', ['$delegate', controllerDecorator]); + config(function ($provide) { + $provide.decorator('$controller', function($delegate) { + return function(ctrl, locals) { + //If the controller name is passed, find the controller than matches it + if(typeof ctrl === 'string') { + ctrl = nameToControllerMatch[ctrl]; + } + locals = locals || {}; + //If the controller is not in the list of already registered controllers + //and it is not connected to the local scope, it must be instantiated on the window + if(!controllers[ctrl] && (!locals.$scope || !locals.$scope[ctrl])) { + if(angular.version.minor <= 2) { + hintLog.logMessage('##Controllers## It is against Angular best practices to ' + + 'instantiate a controller on the window. This behavior is deprecated in Angular' + + ' 1.3.0'); + } else { + hintLog.logMessage('##Controllers## Global instantiation of controllers was deprecated in Angular' + + ' 1.3.0. Define the controller on a module.'); + } + } + var ctrlInstance = $delegate.apply(this, [ctrl, locals]); + return ctrlInstance; + }; + }); +}); + +/** +* Save details of the controllers as they are instantiated +* for use in decoration. +*/ +var originalModule = angular.module; +angular.module = function() { + var module = originalModule.apply(this, arguments); + var originalController = module.controller; + module.controller = function(controllerName, controllerConstructor) { + nameToControllerMatch[controllerName] = controllerConstructor; + var firstLetter = controllerName.charAt(0); + + if(firstLetter !== firstLetter.toUpperCase() && firstLetter === firstLetter.toLowerCase()) { + hintLog.logMessage('##Controllers## The best practice is to name controllers with an' + + ' uppercase first letter. Check the name of \'' + controllerName + '\'.'); + } + + var splitName = controllerName.split('Controller'); + if(splitName.length === 1 || splitName[splitName.length - 1] !== '') { + hintLog.logMessage('##Controllers## The best practice is to name controllers ending with ' + + '\'Controller\'. Check the name of \'' + controllerName + '\''); + } + + controllers[controllerConstructor] = controllerConstructor; + return originalController.apply(this, arguments); + }; + return module; +}; + +},{"angular-hint-log":59}],5:[function(require,module,exports){ +'use strict'; + +var hintLog = angular.hint = require('angular-hint-log'); +var ddLibData = require('./lib/ddLib-data'); + +var RESTRICT_REGEXP = /restrict\s*:\s*['"](.+?)['"]/; +var customDirectives = []; +var dasherize = require('dasherize'); +var search = require('./lib/search'); +var checkPrelimErrors = require('./lib/checkPrelimErrors'); +var getKeysAndValues = require('./lib/getKeysAndValues'); +var defaultDirectives = ddLibData.directiveTypes['angular-default-directives'].directives; +var htmlDirectives = ddLibData.directiveTypes['html-directives'].directives; + +angular.module('ngHintDirectives', ['ngLocale']) + .config(['$provide', function($provide) { + $provide.decorator('$compile', ['$delegate', function($delegate) { + return function(elem) { + elem = angular.element(elem); + for(var i = 0; i < elem.length; i+=2){ + if(elem[i].getElementsByTagName){ + var toSend = Array.prototype.slice.call(elem[i].getElementsByTagName('*')); + search(toSend, customDirectives); + } + } + return $delegate.apply(this, arguments); + }; + }]); }]); -function controllerDecorator($delegate) { - return function(ctrl) { - if (typeof ctrl === 'string') { - var match = ctrl.match(CNTRL_REG); - var ctrlName = (match && match[1]) || ctrl; - sendMessageForControllerName(ctrlName); - if (!nameToControllerMap[ctrlName] && typeof window[ctrlName] === 'function') { - sendMessageForGlobalController(ctrlName); +angular.module('ngLocale').config(function($provide) { + var originalProvider = $provide.provider; + $provide.provider = function(token, provider) { + provider = originalProvider.apply($provide, arguments); + if (token === '$compile') { + var originalProviderDirective = provider.directive; + provider.directive = function(dirsObj) { + for(var prop in dirsObj){ + var propDashed = dasherize(prop); + if(isNaN(+propDashed) && + !defaultDirectives[propDashed] && + !htmlDirectives[propDashed]) { + var matchRestrict = dirsObj[prop].toString().match(RESTRICT_REGEXP); + ddLibData.directiveTypes['angular-default-directives'] + .directives[propDashed] = (matchRestrict && matchRestrict[1]) || 'ACME'; + } + } + return originalProviderDirective.apply(this, arguments); + }; + } + return provider; + }; +}); + +var originalAngularModule = angular.module; +angular.module = function() { + var module = originalAngularModule.apply(this, arguments); + var originalDirective = module.directive; + module.directive = function(directiveName, directiveFactory) { + var originalDirectiveFactory = typeof directiveFactory === 'function' ? directiveFactory : + directiveFactory[directiveFactory.length - 1]; + var factoryStr = originalDirectiveFactory.toString(); + + checkPrelimErrors(directiveName,factoryStr); + + var pairs = getKeysAndValues(factoryStr); + pairs.map(function(pair){customDirectives.push(pair);}); + + var matchRestrict = factoryStr.match(RESTRICT_REGEXP); + var restrict = (matchRestrict && matchRestrict[1]) || 'A'; + var directive = {directiveName: directiveName, restrict: restrict, require:pairs}; + customDirectives.push(directive); + + return originalDirective.apply(this, arguments); + }; + return module; +}; + +},{"./lib/checkPrelimErrors":15,"./lib/ddLib-data":16,"./lib/getKeysAndValues":23,"./lib/search":31,"angular-hint-log":33,"dasherize":34}],6:[function(require,module,exports){ +/** + *@param s: first string to compare + *@param t: second string to compare + * + *@description: + *Checks to see if two strings are similiar enough to even bother checking the Levenshtein Distance. + */ +module.exports = function(s,t) { + var strMap = {}, similarities = 0, STRICTNESS = 0.66; + if(Math.abs(s.length-t.length) > 3) { + return false; + } + s.split('').forEach(function(x){strMap[x] = x;}); + for (var i = t.length - 1; i >= 0; i--) { + similarities = strMap[t.charAt(i)] ? similarities + 1 : similarities; + } + return similarities >= t.length * STRICTNESS; +}; + +},{}],7:[function(require,module,exports){ +var ddLibData = require('./ddLib-data'); + +/** + *@param attribute: attribute name as string e.g. 'ng-click', 'width', 'src', etc. + *@param options: {} options object from beginSearch. + * + *@description attribute exsistance in the types of directives/attibutes (html, angular core, and + * angular custom) and checks the restrict property of values matches its use. + * + *@return {} with attribute exsistance and wrong use e.g. restrict property set to elements only. + **/ +module.exports = function(attribute, options) { + var anyTrue = false, + wrongUse = '', + directive, + restrictProp; + + options.directiveTypes.forEach(function(dirType) { + var isTag = attribute.charAt(0) === '*'; + var isCustomDir = dirType === 'angular-custom-directives'; + if(!isTag) { + directive = ddLibData.directiveTypes[dirType].directives[attribute] || ''; + restrictProp = directive.restrict || directive; + if(restrictProp) { + if(restrictProp.indexOf('E') > -1 && restrictProp.indexOf('A') < 0) { + wrongUse = 'element'; + } + if(restrictProp.indexOf('C') > -1 && restrictProp.indexOf('A') < 0) { + wrongUse = (wrongUse) ? 'element and class' : 'class'; + } + anyTrue = anyTrue || true; + } + } + else if(isTag && isCustomDir){ + directive = ddLibData.directiveTypes[dirType].directives[attribute.substring(1)] || ''; + restrictProp = directive.restrict || directive; + anyTrue = anyTrue || true; + if(restrictProp && restrictProp.indexOf('A') > -1 && restrictProp.indexOf('E') < 0) { + wrongUse = 'attribute'; + } + } + }); + var typeError = wrongUse? 'wronguse':'' || !anyTrue ? 'nonexsisting' : '' || ''; + return {exsists: anyTrue, wrongUse: wrongUse, typeError: typeError}; +}; + +},{"./ddLib-data":16}],8:[function(require,module,exports){ +module.exports = function(info, id, type) { + var s = info.missing.length === 1 ? ' ' : 's '; + var waswere = info.missing.length === 1 ? 'was ' : 'were '; + var missing = ''; + info.missing.forEach(function(str){ + missing += '"'+str+'",'; + }); + missing = '['+missing.substring(0,missing.length-1)+'] '; + var message = 'Attribute'+s+missing+waswere+'found to be missing in '+type+ ' element'+id+'.'; + return message; +}; + +},{}],9:[function(require,module,exports){ +var isMutExclusiveDir = require('./isMutExclusiveDir'); + +module.exports = function(info, id, type) { + var pair = isMutExclusiveDir(info.error); + var message = 'Angular attributes "'+info.error+'" and "'+pair+'" in '+type+ ' element'+id+ + ' should not be attributes together on the same HTML element'; + return message; +}; + +},{"./isMutExclusiveDir":28}],10:[function(require,module,exports){ +var hintLog = require('angular-hint-log'); + +module.exports = function(directiveName) { + var message = 'Directive "'+directiveName+'" should have proper namespace try adding a prefix'+ + ' and/or using camelcase.'; + var domElement = '<'+directiveName+'> '; + hintLog.logMessage('##Directives## ' + message); +}; + +},{"angular-hint-log":33}],11:[function(require,module,exports){ +module.exports = function(info, id, type) { + var ngDir = 'ng-'+info.error.substring(2); + var message = 'Use Angular version of "'+info.error+'" in '+type+' element'+id+'. Try: "'+ngDir+'"'; + return message; +}; + +},{}],12:[function(require,module,exports){ +var ddLibData = require('./ddLib-data'); + +module.exports = function(info, id, type) { + var message = ddLibData.directiveTypes[info.directiveType].message+type+' element'+id+'. '; + var error = (info.error.charAt(0) === '*') ? info.error.substring(1): info.error; + message +='Found incorrect attribute "'+error+'" try "'+info.match+'".'; + return message; +}; + +},{"./ddLib-data":16}],13:[function(require,module,exports){ +var hintLog = require('angular-hint-log'); + +module.exports = function(directiveName) { + var message = 'The use of "replace" in directive factories is deprecated,'+ + ' and it was found in "'+directiveName+'".'; + var domElement = '<'+directiveName+'> '; + hintLog.logMessage('##Directives## ' + message); +}; + +},{"angular-hint-log":33}],14:[function(require,module,exports){ +var ddLibData = require('./ddLib-data'); + +module.exports = function(info, id, type) { + var message = ddLibData.directiveTypes[info.directiveType].message+type+' element'+id+'. '; + var error = (info.error.charAt(0) === '*') ? info.error.substring(1): info.error; + var aecmType = (info.wrongUse.indexOf('attribute') > -1)? 'Element' : 'Attribute'; + message += aecmType+' name "'+error+'" is reserved for '+info.wrongUse+' names only.'; + return message; +}; + +},{"./ddLib-data":16}],15:[function(require,module,exports){ +var hasNameSpace = require('./hasNameSpace'); +var buildNameSpace = require('./buildNameSpace'); +var hasReplaceOption = require('./hasReplaceOption'); +var buildReplaceOption = require('./buildReplaceOption'); + +module.exports = function(dirName, dirFacStr) { + if (!hasNameSpace(dirName)) { + buildNameSpace(dirName); + } + if (hasReplaceOption(dirFacStr)) { + buildReplaceOption(dirName); + } +}; + +},{"./buildNameSpace":10,"./buildReplaceOption":13,"./hasNameSpace":26,"./hasReplaceOption":27}],16:[function(require,module,exports){ +module.exports = { + directiveTypes : { + 'html-directives': { + message: 'There was an HTML error in ', + directives: { + 'abbr' : 'A', + 'accept': 'A', + 'accesskey': 'A', + 'action': 'A', + 'align': 'A', + 'alt': 'A', + 'background': 'A', + 'bgcolor': 'A', + 'border': 'A', + 'cellpadding': 'A', + 'char': 'A', + 'charoff': 'A', + 'charset': 'A', + 'checked': 'A', + 'cite': 'A', + 'class': 'A', + 'classid': 'A', + 'code': 'A', + 'codebase': 'A', + 'color': 'A', + 'cols': 'A', + 'colspan': 'A', + 'content': 'A', + 'data': 'A', + 'defer': 'A', + 'dir': 'A', + 'face': 'A', + 'for': 'A', + 'frame': 'A', + 'frameborder': 'A', + 'headers': 'A', + 'height': 'A', + 'http-equiv': 'A', + 'href': 'A', + 'id': 'A', + 'label': 'A', + 'lang': 'A', + 'language': 'A', + 'link': 'A', + 'marginheight': 'A', + 'marginwidth': 'A', + 'maxlength': 'A', + 'media': 'A', + 'multiple': 'A', + 'name': 'A', + 'object': '!A', + 'onblur': '!A', + 'onchange': '!A', + 'onclick': '!A', + 'onfocus': '!A', + 'onkeydown': '!A', + 'onkeypress': '!A', + 'onkeyup': '!A', + 'onload': '!A', + 'onmousedown': '!A', + 'onmousemove': '!A', + 'onmouseout': '!A', + 'onmouseover': '!A', + 'onmouseup': '!A', + 'onreset': '!A', + 'onselect': '!A', + 'onsubmit': '!A', + 'readonly': 'A', + 'rel': 'A', + 'rev': 'A', + 'role': 'A', + 'rows': 'A', + 'rowspan': 'A', + 'size': 'A', + 'span': 'EA', + 'src': 'A', + 'start': 'A', + 'style': 'A', + 'text': 'A', + 'target': 'A', + 'title': 'A', + 'type': 'A', + 'value': 'A', + 'width': 'A'} + }, + 'angular-default-directives': { + message: 'There was an AngularJS error in ', + directives: { + 'count': 'A', + 'min': 'A', + 'max': 'A', + 'ng-app': 'A', + 'ng-bind': 'A', + 'ng-bindhtml': 'A', + 'ng-bindtemplate': 'A', + 'ng-blur': 'A', + 'ng-change': 'A', + 'ng-checked': 'A', + 'ng-class': 'A', + 'ng-classeven': 'A', + 'ng-classodd': 'A', + 'ng-click': 'A', + 'ng-cloak': 'A', + 'ng-controller': 'A', + 'ng-copy': 'A', + 'ng-csp': 'A', + 'ng-cut': 'A', + 'ng-dblclick': 'A', + 'ng-disabled': 'A', + 'ng-dirty': 'A', + 'ng-focus': 'A', + 'ng-form': 'A', + 'ng-hide': 'A', + 'ng-hint': 'A', + 'ng-hint-exclude': 'A', + 'ng-hint-include': 'A', + 'ng-href': 'A', + 'ng-if': 'A', + 'ng-include': 'A', + 'ng-init': 'A', + 'ng-invalid': 'A', + 'ng-keydown': 'A', + 'ng-keypress': 'A', + 'ng-keyup': 'A', + 'ng-list': 'A', + 'ng-maxlength': 'A', + 'ng-minlength': 'A', + 'ng-model': 'A', + 'ng-model-options': 'A', + 'ng-mousedown': 'A', + 'ng-mouseenter': 'A', + 'ng-mouseleave': 'A', + 'ng-mousemove': 'A', + 'ng-mouseover': 'A', + 'ng-mouseup': 'A', + 'ng-nonbindable': 'A', + 'ng-open': 'A', + 'ng-options': 'A', + 'ng-paste': 'A', + 'ng-pattern': 'A', + 'ng-pluralize': 'A', + 'ng-pristine': 'A', + 'ng-readonly': 'A', + 'ng-repeat': 'A', + 'ng-required': 'A', + 'ng-selected': 'A', + 'ng-show': 'A', + 'ng-src': 'A', + 'ng-srcset': 'A', + 'ng-style': 'A', + 'ng-submit': 'A', + 'ng-switch': 'A', + 'ng-transclude': 'A', + 'ng-true-value': 'A', + 'ng-trim': 'A', + 'ng-false-value': 'A', + 'ng-value': 'A', + 'ng-valid': 'A', + 'ng-view': 'A', + 'required': 'A', + 'when': 'A' + } + }, + 'angular-custom-directives': { + message: 'There was an AngularJS error in ', + directives: { + + } + } + } +}; + +},{}],17:[function(require,module,exports){ +var areSimilarEnough = require('./areSimilarEnough'); +var levenshteinDistance = require('./levenshtein'); + +/** + *@param directiveTypeData: {} with list of directives/attributes and + *their respective restrict properties. + *@param attribute: attribute name as string e.g. 'ng-click', 'width', 'src', etc. + * + *@return {} with Levenshtein Distance and name of the closest match to given attribute. + **/ +module.exports = function(directiveTypeData, attribute) { + if(typeof attribute !== 'string') { + throw new Error('Function must be passed a string as second parameter.'); + } + if((directiveTypeData === null || directiveTypeData === undefined) || + typeof directiveTypeData !== 'object') { + throw new Error('Function must be passed a defined object as first parameter.'); + } + var min_levDist = Infinity, + closestMatch = ''; + + for(var directive in directiveTypeData){ + if(areSimilarEnough(attribute,directive)) { + var currentlevDist = levenshteinDistance(attribute, directive); + closestMatch = (currentlevDist < min_levDist)? directive : closestMatch; + min_levDist = (currentlevDist < min_levDist)? currentlevDist : min_levDist; + } + } + return {min_levDist: min_levDist, match: closestMatch}; +}; + +},{"./areSimilarEnough":6,"./levenshtein":29}],18:[function(require,module,exports){ + +var getFailedAttributesOfElement = require('./getFailedAttributesOfElement'); + +module.exports = function(scopeElements, options) { + return scopeElements.map(getFailedAttributesOfElement.bind(null, options)) + .filter(function(x) {return x;}); +}; + +},{"./getFailedAttributesOfElement":22}],19:[function(require,module,exports){ +var ddLibData = require('./ddLib-data'); + +module.exports = function(dirName, attributes) { + attributes = attributes.map(function(x){return x.nodeName;}); + var directive = ddLibData.directiveTypes['angular-custom-directives'].directives[dirName]; + var missing = []; + if (directive && directive.require) { + for (var i = 0; i < directive.require.length; i++) { + if (attributes.indexOf(directive.require[i].directiveName) < 0) { + missing.push(directive.require[i].directiveName); + } + } + } + return missing; +}; + +},{"./ddLib-data":16}],20:[function(require,module,exports){ +var hintLog = require('angular-hint-log'); + +var build = { + wronguse: require('./buildWrongUse'), + nonexsisting: require('./buildNonExsisting'), + missingrequired: require('./buildMissingRequired'), + ngevent: require('./buildNgEvent'), + mutuallyexclusive: require('./buildMutuallyExclusive') +}; + +/** + *@param failedElements: [] of {}s of all failed elements with their failed attributes and closest + *matches or restrict properties + * + *@return [] of failed messages. + **/ +module.exports = function(failedElements) { + failedElements.forEach(function(obj) { + obj.data.forEach(function(info) { + var id = (obj.domElement.id) ? ' with id: #' + obj.domElement.id : ''; + var type = obj.domElement.nodeName; + var message = build[info.typeError](info, id, type); + hintLog.logMessage('##Directives## ' + message); + }); + }); +}; + +},{"./buildMissingRequired":8,"./buildMutuallyExclusive":9,"./buildNgEvent":11,"./buildNonExsisting":12,"./buildWrongUse":14,"angular-hint-log":33}],21:[function(require,module,exports){ +var normalizeAttribute = require('./normalizeAttribute'); +var ddLibData = require('./ddLib-data'); +var isMutExclusiveDir = require('./isMutExclusiveDir'); +var hasMutExclusivePair = require('./hasMutExclusivePair'); +var attributeExsistsInTypes = require('./attributeExsistsInTypes'); +var getSuggestions = require('./getSuggestions'); + +/** + *@param attributes: [] of attributes from element (includes tag name of element, e.g. DIV, P, etc.) + *@param options: {} options object from beginSearch + * + *@return [] of failedAttributes with their respective suggestions and directiveTypes + **/ +module.exports = function(attributes, options) { + var failedAttrs = [], mutExPairFound = false; + for (var i = 0; i < attributes.length; i++) { + var attr = normalizeAttribute(attributes[i].nodeName); + var dirVal = ddLibData.directiveTypes['html-directives'].directives[attr] || ''; + if (dirVal.indexOf('!') > -1) { + failedAttrs.push({ + error: attr, + directiveType: 'html-directives', + typeError: 'ngevent' + }); + continue; + } + if (!mutExPairFound && isMutExclusiveDir(attr) && hasMutExclusivePair(attr, attributes)) { + failedAttrs.push({ + error: attr, + directiveType: 'angular-default-directives', + typeError: 'mutuallyexclusive' + }); + mutExPairFound = true; + continue; + } + var result = attributeExsistsInTypes(attr,options); + var suggestion = result.typeError === 'nonexsisting' ? + getSuggestions(attr, options) : {match: ''}; + + if (result.typeError) { + failedAttrs.push({ + match: suggestion.match || '', + wrongUse: result.wrongUse || '', + error: attr, + directiveType: suggestion.directiveType || 'angular-custom-directives', + typeError: result.typeError + }); + } + } + return failedAttrs; +}; + +},{"./attributeExsistsInTypes":7,"./ddLib-data":16,"./getSuggestions":24,"./hasMutExclusivePair":25,"./isMutExclusiveDir":28,"./normalizeAttribute":30}],22:[function(require,module,exports){ +var getFailedAttributes = require('./getFailedAttributes'); +var findMissingAttrs = require('./findMissingAttrs'); + + +/** + *@description + *Adds element tag name (DIV, P, SPAN) to list of attributes with '*' prepended + *for identification later. + * + *@param options: {} options object from beginSearch + *@param element: HTML element to check attributes of + * + *@return {} of html element and [] of failed attributes + **/ +module.exports = function(options, element) { + if(element.attributes.length) { + var eleName = element.nodeName.toLowerCase(); + var eleAttrs = Array.prototype.slice.call(element.attributes); + eleAttrs.push({ + nodeName: '*'+eleName + }); + var failedAttrs = getFailedAttributes(eleAttrs, options); + var missingRequired = findMissingAttrs(eleName, eleAttrs); + if(failedAttrs.length || missingRequired.length) { + if(missingRequired.length) { + failedAttrs.push({ + directiveType: 'angular-custom-directive', + missing: missingRequired, + typeError: 'missingrequired' + }); + } + return { + domElement: element, + data: failedAttrs + }; + } + } +}; + +},{"./findMissingAttrs":19,"./getFailedAttributes":21}],23:[function(require,module,exports){ +module.exports = function(str) { + var customDirectives = [], pairs = []; + var matchScope = str.replace(/\n/g,'').match(/scope\s*:\s*{\s*[^}]*['"]\s*}/); + if (matchScope) { + matchScope[0].match(/\w+\s*:\s*['"][a-zA-Z=@&]+['"]/g).map(function(str){ + var temp = str.match(/(\w+)\s*:\s*['"](.+)['"]/); + pairs.push({key:temp[1],value:temp[2]}); + }); + pairs.forEach(function(pair){ + var name = (['=', '@', '&'].indexOf(pair.value) !== -1)? pair.key : pair.value.substring(1); + customDirectives.push({directiveName: name , restrict:'A'}); + }); + } + return customDirectives; +}; + +},{}],24:[function(require,module,exports){ +var ddLibData = require('./ddLib-data'); +var findClosestMatchIn = require('./findClosestMatchIn'); + +/** + *@param attribute: attribute name as string e.g. 'ng-click', 'width', 'src', etc. + *@param options: {} options object from beginSearch. + * + *@return {} with closest match to attribute and the directive type it corresponds to. + **/ +module.exports = function(attribute, options) { + var min_levDist = Infinity, + match = '', + dirType = ''; + + options.directiveTypes.forEach(function(directiveType) { + var isTag = attribute.charAt(0) === '*'; + var isCustomDir = directiveType === 'angular-custom-directives'; + if (!isTag || (isTag && isCustomDir)) { + var directiveTypeData = ddLibData.directiveTypes[directiveType].directives; + var tempMatch = findClosestMatchIn(directiveTypeData, attribute); + if (tempMatch.min_levDist < options.tolerance && tempMatch.min_levDist < min_levDist) { + match = tempMatch.match; + dirType = directiveType; + min_levDist = tempMatch.min_levDist; } } - return $delegate.apply(this, arguments); + }); + return { + match: match, + directiveType: dirType }; -} +}; + +},{"./ddLib-data":16,"./findClosestMatchIn":17}],25:[function(require,module,exports){ +var isMutExclusiveDir = require('./isMutExclusiveDir'); + +module.exports = function(attr, attributes) { + var pair = isMutExclusiveDir(attr); + + return attributes.some(function(otherAttr) { + return otherAttr.nodeName === pair; + }); +}; + +},{"./isMutExclusiveDir":28}],26:[function(require,module,exports){ +module.exports = function(str) { + return str.toLowerCase() !== str; +}; + +},{}],27:[function(require,module,exports){ +module.exports = function(facStr) { + return facStr.match(/replace\s*:/); +}; + +},{}],28:[function(require,module,exports){ +module.exports = function (dirName) { + var exclusiveDirHash = { + 'ng-show' : 'ng-hide', + 'ng-hide' : 'ng-show', + 'ng-switch-when' : 'ng-switch-default', + 'ng-switch-default' : 'ng-switch-when', + }; + return exclusiveDirHash[dirName]; +}; +},{}],29:[function(require,module,exports){ /** -* Save details of the controllers as they are instantiated -* for use in decoration. -* Hint about the best practices for naming controllers. -*/ -var originalModule = angular.module; + *@param s: first string to compare for Levenshtein Distance. + *@param t: second string to compare for Levenshtein Distance. + * + *@description + *Calculates the minimum number of changes (insertion, deletion, transposition) to get from s to t. + * + *credit: http://stackoverflow.com/questions/11919065/sort-an-array-by-the-levenshtein-distance-with-best-performance-in-javascript + *http://www.merriampark.com/ld.htm, http://www.mgilleland.com/ld/ldjavascript.htm, Damerau–Levenshtein distance (Wikipedia) + **/ +module.exports = function(s, t) { + if(typeof s !== 'string' || typeof t !== 'string') { + throw new Error('Function must be passed two strings, given: '+typeof s+' and '+typeof t+'.'); + } + var d = []; + var n = s.length; + var m = t.length; + + if (n === 0) {return m;} + if (m === 0) {return n;} + + for (var ii = n; ii >= 0; ii--) { d[ii] = []; } + for (var ii = n; ii >= 0; ii--) { d[ii][0] = ii; } + for (var jj = m; jj >= 0; jj--) { d[0][jj] = jj; } + for (var i = 1; i <= n; i++) { + var s_i = s.charAt(i - 1); + + for (var j = 1; j <= m; j++) { + if (i == j && d[i][j] > 4) return n; + var t_j = t.charAt(j - 1); + var cost = (s_i == t_j) ? 0 : 1; + var mi = d[i - 1][j] + 1; + var b = d[i][j - 1] + 1; + var c = d[i - 1][j - 1] + cost; + if (b < mi) mi = b; + if (c < mi) mi = c; + d[i][j] = mi; + if (i > 1 && j > 1 && s_i == t.charAt(j - 2) && s.charAt(i - 2) == t_j) { + d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost); + } + } + } + return d[n][m]; +}; -function processController(ctrlName) { - nameToControllerMap[ctrlName] = true; - sendMessageForControllerName(ctrlName); -} +},{}],30:[function(require,module,exports){ +/** + *@param attribute: attribute name before normalization as string + * e.g. 'data-ng-click', 'width', 'x:ng:src', etc. + * + *@return normalized attribute name + **/ +module.exports = function(attribute) { + return attribute.replace(/^(?:data|x)[-_:]/,'').replace(/[:_]/g,'-'); +}; -function sendMessageForGlobalController(name) { - hint.logMessage(MODULE_NAME, - 'add `' + name + '` to a module', - angular.version.minor <= 2 ? SEVERITY_WARNING : SEVERITY_ERROR, - CATEGORY_GLOBAL_CONTROLLER); -} +},{}],31:[function(require,module,exports){ -function sendMessageForControllerName(name) { - var newName = name; - if (!startsWithUpperCase(name)) { - newName = title(newName); +var formatResults = require('./formatResults'); +var findFailedElements = require('./findFailedElements'); +var setCustomDirectives = require('./setCustomDirectives'); +var defaultTypes = [ + 'html-directives', + 'angular-default-directives', + 'angular-custom-directives' +]; + + +/** + * + *@param scopeElements: [] of HTML elements to be checked for incorrect attributes + *@param customDirectives: [] of custom directive objects from $compile decorator + *@param options: {} of options for app to run with: + * options.tolerance: Integer, maximum Levenshtein Distance to be allowed for misspellings + * options.directiveTypes: [] of which type of directives/attributes to search through + **/ +module.exports = function(scopeElements, customDirectives, options) { + if(!Array.isArray(scopeElements)) { + throw new Error('Function search must be passed an array.'); } - if (!endsWithController(name)) { - newName = addControllerSuffix(newName); + options = options || {}; + options.directiveTypes = options.directiveTypes || defaultTypes; + options.tolerance = options.tolerance || 4; + if(customDirectives && customDirectives.length){ + setCustomDirectives(customDirectives); } - if (name !== newName) { - hint.logMessage(MODULE_NAME, - 'Consider renaming `' + name + '` to `' + newName + '`.', - SEVERITY_WARNING, - CATEGORY_CONTROLLER_NAME); + var failedElements = findFailedElements(scopeElements, options); + formatResults(failedElements); +}; + +},{"./findFailedElements":18,"./formatResults":20,"./setCustomDirectives":32}],32:[function(require,module,exports){ +var ddLibData = require('../lib/ddLib-data'); + +module.exports = function(customDirectives) { + customDirectives.forEach(function(directive) { + var directiveName = directive.directiveName.replace(/([A-Z])/g, '-$1').toLowerCase(); + ddLibData.directiveTypes['angular-custom-directives'] + .directives[directiveName] = directive; + }); +}; + +},{"../lib/ddLib-data":16}],33:[function(require,module,exports){ +var queuedMessages = {}; +function logMessage(message) { + var nameAndValue = message.split(/##/); + if(nameAndValue[0] !== '') { + if(queuedMessages['No Name']) { + queuedMessages['No Name'][message] = message; + } else { + queuedMessages['No Name'] = {}; + queuedMessages['No Name'][message] = message; + } + } else if(queuedMessages[nameAndValue[1]]) { + queuedMessages[nameAndValue[1]][nameAndValue[2]] = nameAndValue[2]; + } else { + queuedMessages[nameAndValue[1]] = {}; + queuedMessages[nameAndValue[1]][nameAndValue[2]] = nameAndValue[2]; } -} + module.exports.onMessage(message); +}; + +function flush() { + var flushMessages = queuedMessages; + queuedMessages = {}; + return flushMessages; +}; + +module.exports.onMessage = function(message) {}; +module.exports.logMessage = logMessage; +module.exports.flush = flush; +},{}],34:[function(require,module,exports){ +'use strict'; -function startsWithUpperCase(name) { - var firstChar = name.charAt(0); - return firstChar === firstChar.toUpperCase() && - firstChar !== firstChar.toLowerCase(); +var isArray = Array.isArray || function (obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; +}; + +var isDate = function (obj) { + return Object.prototype.toString.call(obj) === '[object Date]'; +}; + +var isRegex = function (obj) { + return Object.prototype.toString.call(obj) === '[object RegExp]'; +}; + +var has = Object.prototype.hasOwnProperty; +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + return keys; +}; + +function dashCase(str) { + return str.replace(/([A-Z])/g, function ($1) { + return '-' + $1.toLowerCase(); + }); } -function title (name) { - return name[0].toUpperCase() + name.substr(1); +function map(xs, f) { + if (xs.map) { + return xs.map(f); + } + var res = []; + for (var i = 0; i < xs.length; i++) { + res.push(f(xs[i], i)); + } + return res; } -var CONTROLLER_RE = /Controller$/; -function endsWithController(name) { - return CONTROLLER_RE.test(name); +function reduce(xs, f, acc) { + if (xs.reduce) { + return xs.reduce(f, acc); + } + for (var i = 0; i < xs.length; i++) { + acc = f(acc, xs[i], i); + } + return acc; } -var RE = /(Ctrl|Kontroller)?$/; -function addControllerSuffix(name) { - return name.replace(RE, 'Controller'); +function walk(obj) { + if (!obj || typeof obj !== 'object') { + return obj; + } + if (isDate(obj) || isRegex(obj)) { + return obj; + } + if (isArray(obj)) { + return map(obj, walk); + } + return reduce(objectKeys(obj), function (acc, key) { + var camel = dashCase(key); + acc[camel] = walk(obj[key]); + return acc; + }, {}); } -/* - * decorate angular module API - */ +module.exports = function (obj) { + if (typeof obj === 'string') { + return dashCase(obj); + } + return walk(obj); +}; -angular.module = function() { - var module = originalModule.apply(this, arguments), - originalController = module.controller; +},{}],35:[function(require,module,exports){ +'use strict'; + +/** +* Create an interceptor that will log a message when use of a DOM API is detected +*/ +var domInterceptor = require('dom-interceptor'); +domInterceptor.enableLineNumbers(3); +var hintLog = angular.hint = require('angular-hint-log'); +var INTERCEPTOR_FUNCTION = function(message) { + hintLog.logMessage(message); +}; + +/** +* Decorates $controller with a patching function to +* throw an error if DOM APIs are manipulated from +* within an Angular controller +*/ +angular.module('ngHintDom', []). + config(function ($provide) { + $provide.decorator('$controller', function($delegate, $injector) { + + var patchedServices = {}; + + return function(ctrl, locals) { + + if(typeof ctrl === 'string') { + ctrl = nameToConstructorMappings[ctrl]; + } + + var dependencies = $injector.annotate(ctrl); + + // patch methods on $scope + locals = locals || {}; + dependencies.forEach(function (dep) { + if (typeof dep === 'string' && !locals[dep]) { + locals[dep] = patchedServices[dep] || + (patchedServices[dep] = patchService($injector.get('$timeout'))); + } + }); + + function disallowedContext(fn) { + return function () { + domInterceptor.addManipulationListener(INTERCEPTOR_FUNCTION); + var ret = fn.apply(this, arguments); + domInterceptor.removeManipulationListener(); + return ret; + }; + } + + function patchArguments (fn) { + return function () { + for (var i = 0, ii = arguments.length; i < ii; i++) { + if (typeof arguments[i] === 'function') { + arguments[i] = disallowedContext(arguments[i]); + } + } + return fn.apply(this, arguments); + }; + } + + function patchService (obj) { + if (typeof obj === 'function') { + return patchArguments(obj); + } else if (typeof obj === 'object') { + return Object.keys(obj).reduce(function (obj, prop) { + return obj[prop] = patchService(obj[prop]), obj; + }, obj); + } + return obj; + } + + //Detect manipulation of DOM APIs from within the body of the controller + domInterceptor.addManipulationListener(INTERCEPTOR_FUNCTION); + var ctrlInstance = $delegate.apply(this, [ctrl, locals]); + domInterceptor.removeManipulationListener(); + + //Detect manipulation of DOM APIs from properties on the controller + Object.keys(ctrlInstance).forEach(function (prop) { + if (prop[0] !== '$' && typeof ctrlInstance[prop] === 'function') { + ctrlInstance[prop] = disallowedContext(ctrlInstance[prop]); + } + }); + + //Detect manipulation of DOM APIs from functions defined inside the controller + if(locals.$scope) { + Object.keys(locals.$scope).forEach(function (prop) { + if([prop][0] !== '$' && typeof locals.$scope[prop] === 'function') { + locals.$scope[prop] = disallowedContext(locals.$scope[prop]); + } + }); + } + + return ctrlInstance; + }; + }); + }); +/** +* Keep a record of 'ControllerName': Controller pairs +* so that a controller can be retrieved via its name +*/ +var nameToConstructorMappings = {}; +var originalAngularModule = angular.module; +angular.module = function() { + var module = originalAngularModule.apply(this, arguments); + var originalController = module.controller; module.controller = function(controllerName, controllerConstructor) { - if ((controllerName !== null) && (typeof controllerName === 'object')) { - Object.keys(controllerName).forEach(processController); - } else { - processController(controllerName); - } + nameToConstructorMappings[controllerName] = controllerConstructor; return originalController.apply(this, arguments); }; - return module; }; -},{"angular-hint-log":5}],5:[function(require,module,exports){ +},{"angular-hint-log":59,"dom-interceptor":36}],36:[function(require,module,exports){ 'use strict'; -/* - * HintLog creates a queue of messages logged by ngHint modules. This object - * has a key for each ngHint module that corresponds to the messages - * from that module. - */ -var queuedMessages = {}, - MESSAGE_TYPES = [ - 'error', - 'warning', - 'suggestion' - ]; +/** +* The DOM-interceptor should not throw errors because +* of its own access to the DOM. Within the interceptor +* the listener should have no behavior. +*/ +var _listener = function() {}; +var listener = savedListener; +var savedListener = function(message) {}; -/* - * Add a message to the HintLog message queue. Messages are organized into categories - * according to their module name and severity. - */ -function logMessage(moduleName, message, severity, category) { - // If no severity was provided, categorize the message as a `suggestion` - severity = severity || 3; - var messageType = MESSAGE_TYPES[severity - 1]; +/** +* Initializes the listener to a function that is provided. +* The Element, Node, and Document prototypes are then patched to call +* this listener when DOM APIs are accessed. +**/ +function addManipulationListener(newListener) { + listener = _listener; + savedListener = newListener; + patchOnePrototype(Element, 'Element'); + patchOnePrototype(Node, 'Node'); + patchOnePrototype(Document, 'Document'); + listener = savedListener; +} + +/** +* The interceptor should give a helpful message when manipulation is detected. +*/ +var explanation = 'Detected Manipulation of DOM API: '; + + +/** +* The listener should include the line where the users program gives an error +* if line numbers are enabled. Enabling line numbers requires giving a valid +* line of the stack trace in which the line number should appear. This is because +* using an arbitrary line of the stacktrace such as line might return the line within +* the interceptor where the listener was called. +*/ +var stackTraceLine; +function enableLineNumbers(stackTraceLocation) { + if(typeof stackTraceLocation === 'number' && !isNaN(stackTraceLocation)) { + stackTraceLine = stackTraceLocation; + } else { + throw new Error('Enabling line numbers requires an integer parameter of the stack trace line ' + + 'that should be given. Got: ' + stackTraceLocation); + } +} + +/** +* Finds the line number where access of a DOM API was detected +*/ +function findLineNumber() { + var e = new Error(); + var lineNum; + //Find the line in the user's program rather than in this service + if(e.stack) { + lineNum = e.stack.split('\n')[stackTraceLine]; + } else { + //In Safari, an error does not have a line number until it is thrown + try { + throw e; + } catch (e) { + lineNum = e.stack.split('\n')[stackTraceLine]; + } + } + lineNum = lineNum.split(' ')[1] || lineNum; + return lineNum; +} - moduleName = moduleName || 'General'; +/** +* Object to preserve all the original properties +* that will be restored after patching. +**/ +var originalProperties = {}; - // If the category does not exist, initialize a new object - queuedMessages[moduleName] = queuedMessages[moduleName] || {}; - queuedMessages[moduleName][messageType] = queuedMessages[moduleName][messageType] || []; +/** +* Helper function for patching one prototype. +* Saves the unaltered state of the prototype using collectUnalteredPrototypeProperties() +* and then patches the given prototype with a call to the listener. +*/ +function patchOnePrototype(type, typeName) { + collectUnalteredPrototypeProperties(type, typeName); + listener = _listener; + if (!type || !type.prototype) { + throw new Error('collectPrototypeProperties() needs a .prototype to collect properties from. ' + + type + '.prototype is undefined.'); + } + var objectProperties = Object.getOwnPropertyNames(type.prototype); + objectProperties.forEach(function(prop) { + //Access of some prototype values may throw an error + var desc; + try { + desc = Object.getOwnPropertyDescriptor(type.prototype, prop); + } + catch(e) {} + if (desc) { + if (desc.configurable) { + if (desc.value) { + if (typeof desc.value === 'function') { + var originalValue = desc.value; + desc.value = function () { + listener(explanation + prop + (stackTraceLine ? ' ' + findLineNumber() : '')); + return originalValue.apply(this, arguments); + }; + } + } + Object.defineProperty(type.prototype, prop, desc); + } else if (desc.writable) { + try { + var original = type.prototype[prop]; + type.prototype[prop] = function () { + listener(explanation + prop + (stackTraceLine ? ' ' + findLineNumber() : '')); + return original.apply(this, arguments); + }; + } + catch (e) {} + } + } + }); + listener = savedListener; +} - if (queuedMessages[moduleName][messageType].indexOf(message) < 0) { - queuedMessages[moduleName][messageType].push(message); +/** +* Helper method to collect all properties of a given prototype. +* When patching is removed, all prototype properties +* are set back to these original values +**/ +function collectUnalteredPrototypeProperties(type, typeName) { + listener = _listener; + if(!type || !type.prototype) { + throw new Error('collectUnalteredPrototypeProperties() needs a .prototype to collect properties' + + ' from. ' + type + '.prototype is undefined.'); + } else if(!typeName) { + throw new Error('typeName is required to save properties, got: ' + typeName); } + var objectProperties = {}; + var objectPropertyNames = Object.getOwnPropertyNames(type.prototype); + objectPropertyNames.forEach(function(prop) { + //Access of some prototype values may throw an error + try { + objectProperties[prop] = type.prototype[prop]; + } catch(e) {} + }); + listener = savedListener; + originalProperties[typeName] = objectProperties; + return objectProperties; +} - this.onMessage(moduleName, message, messageType, category); +/** +* Controls the unpatching process by unpatching the +* prototypes as well as disabling the patching of individual +* HTML elements and returning those patched elements to their +* original state. +**/ +function removeManipulationListener() { + listener = _listener; + unpatchOnePrototype(Element, 'Element'); + unpatchOnePrototype(Node, 'Node'); + unpatchOnePrototype(Document, 'Document'); + listener = savedListener; } -/* - * Return and empty the current queue of messages. - */ -function flush() { - var flushMessages = queuedMessages; - queuedMessages = {}; - return flushMessages; +/** +* Helper function to unpatch one prototype. +* Sets all properties of the given type back to the +* original values that were collected. +**/ +function unpatchOnePrototype(type, typeName) { + listener = _listener; + if(!typeName) { + throw new Error('typeName must be the name used to save prototype properties. Got: ' + typeName); + } + var objectProperties = Object.getOwnPropertyNames(type.prototype); + objectProperties.forEach(function(prop) { + //Access of some prototype values may throw an error + try{ + var alteredElement = type.prototype[prop]; + if(typeof alteredElement === 'function') { + type.prototype[prop] = originalProperties[typeName][prop]; + } + } catch(e) {} + }); + listener = savedListener; } -module.exports.onMessage = function(message) {}; -module.exports.logMessage = logMessage; -module.exports.flush = flush; +module.exports.addManipulationListener = addManipulationListener; +module.exports.removeManipulationListener = removeManipulationListener; +module.exports.patchOnePrototype = patchOnePrototype; +module.exports.unpatchOnePrototype = unpatchOnePrototype; +module.exports.enableLineNumbers = enableLineNumbers; + -},{}],6:[function(require,module,exports){ +},{}],37:[function(require,module,exports){ 'use strict'; -/** -* Load necessary functions from /lib into variables. -*/ -var ngEventDirectives = require('./lib/getEventDirectives')(), - getEventAttribute = require('./lib/getEventAttribute'), - getFunctionNames = require('./lib/getFunctionNames'), - formatResults = require('./lib/formatResults'); +var hintLog = angular.hint = require('angular-hint-log'); +var ngEventDirectives = require('./lib/getEventDirectives')(); -/** -* Decorate $provide in order to examine ng-event directives -* and hint about their effective use. -*/ -angular.module('ngHintEvents', []) - .config(['$provide', function($provide) { +var getEventAttribute = require('./lib/getEventAttribute'); +var getFunctionNames = require('./lib/getFunctionNames'); +var formatResults = require('./lib/formatResults'); + +angular.module('ngHintEvents',[]) + .config(['$provide',function($provide) { for(var directive in ngEventDirectives) { + var dirName = ngEventDirectives[directive]+'Directive'; - try{ - $provide.decorator(dirName, ['$delegate', '$timeout', '$parse', - function($delegate, $timeout, $parse) { - - var original = $delegate[0].compile, falseBinds = [], messages = []; - - $delegate[0].compile = function(element, attrs, transclude) { - var angularAttrs = attrs.$attr; - var eventAttrName = getEventAttribute(angularAttrs); - var fn = $parse(attrs[eventAttrName]); - var messages = []; - return function ngEventHandler(scope, element, attrs) { - for(var attr in angularAttrs) { - var boundFuncs = getFunctionNames(attrs[attr]); - boundFuncs.forEach(function(boundFn) { - if(ngEventDirectives[attr] && !(boundFn in scope)) { - messages.push({ - scope: scope, - element:element, - attrs: attrs, - boundFunc: boundFn - }); - } - }); - } - element.on(eventAttrName.substring(2).toLowerCase(), function(event) { - scope.$apply(function() { - fn(scope, {$event:event}); - }); + + $provide.decorator(dirName, ['$delegate', '$timeout', '$parse', + function($delegate, $timeout, $parse) { + + var original = $delegate[0].compile, falseBinds = [], messages = []; + + $delegate[0].compile = function(element, attrs, transclude) { + var eventAttrName = getEventAttribute(attrs.$attr); + var fn = $parse(attrs[eventAttrName]); + var messages = []; + return function ngEventHandler(scope, element, attrs) { + for(var attr in attrs.$attr) { + var boundFuncs = getFunctionNames(attrs[attr]); + boundFuncs.forEach(function(boundFn) { + if(ngEventDirectives[attr] && !(boundFn in scope)) { + messages.push({ + scope: scope, + element:element, + attrs: attrs, + boundFunc: boundFn + }); + } + }); + } + element.on(eventAttrName.substring(2).toLowerCase(), function(event) { + scope.$apply(function() { + fn(scope, {$event:event}); }); - formatResults(messages); - }; + }); + formatResults(messages); }; - return $delegate; - }]); - } catch(e) {} + }; + return $delegate; + }]); } }]); -},{"./lib/formatResults":8,"./lib/getEventAttribute":9,"./lib/getEventDirectives":10,"./lib/getFunctionNames":11}],7:[function(require,module,exports){ -'use strict'; - -var getValidProps = require('./getValidProps'), - suggest = require('suggest-it'); +},{"./lib/formatResults":40,"./lib/getEventAttribute":41,"./lib/getEventDirectives":42,"./lib/getFunctionNames":43,"angular-hint-log":47}],38:[function(require,module,exports){ +var getValidProps = require('./getValidProps'); +var getSuggestion = require('./getSuggestion'); module.exports = function addSuggestions(messages) { messages.forEach(function(messageObj) { - var dictionary = getValidProps(messageObj.scope), - suggestion = suggest(dictionary)(messageObj.boundFunc); + var props = getValidProps(messageObj.scope); + var suggestion = getSuggestion(messageObj.boundFunc, props); messageObj['match'] = suggestion; }); return messages; }; -},{"./getValidProps":12,"suggest-it":15}],8:[function(require,module,exports){ -'use strict'; +},{"./getSuggestion":44,"./getValidProps":45}],39:[function(require,module,exports){ +module.exports = function areSimilarEnough(s,t) { + var strMap = {}, similarities = 0, STRICTNESS = .66; + if(Math.abs(s.length-t.length) > 3) { + return false; + } + s.split('').forEach(function(x){strMap[x] = x;}); + for (var i = t.length - 1; i >= 0; i--) { + similarities = strMap[t.charAt(i)] ? similarities + 1 : similarities; + } + return similarities >= t.length * STRICTNESS; +}; -var hintLog = angular.hint = require('angular-hint-log'), - addSuggestions = require('./addSuggestions'), - MODULE_NAME = 'Events', - SEVERITY_ERROR = 1; +},{}],40:[function(require,module,exports){ +var hintLog = require('angular-hint-log'); +var addSuggestions = require('./addSuggestions'); module.exports = function formatResults(messages) { messages = addSuggestions(messages); if(messages.length) { messages.forEach(function(obj) { - var id = (obj.element[0].id) ? ' with id: #' + obj.element[0].id : '', - type = obj.element[0].nodeName, - suggestion = obj.match ? ' (Try "' + obj.match + '").': '.', - message = 'Variable "' + obj.boundFunc + '" called on ' + type + ' element' + id + - ' does not exist in that scope' + suggestion + ' Event directive found on "' + - obj.element[0].outerHTML + '".'; - hintLog.logMessage(MODULE_NAME, message, SEVERITY_ERROR); + var id = (obj.element[0].id) ? ' with id: #'+obj.element[0].id : ''; + var type = obj.element[0].nodeName; + var suggestion = obj.match ? ' (Try "'+obj.match+'")': ''; + var message = 'Variable "'+obj.boundFunc+'" called on '+type+' element'+id+' does not '+ + 'exist in that scope.'+suggestion+' Event directive found on ' + obj.element[0] + ' in ' + + obj.scope + ' scope.'; + hintLog.logMessage('##Events## ' + message); }); } }; -},{"./addSuggestions":7,"angular-hint-log":13}],9:[function(require,module,exports){ -'use strict'; - +},{"./addSuggestions":38,"angular-hint-log":47}],41:[function(require,module,exports){ var ngEventDirectives = require('./getEventDirectives')(); module.exports = function getEventAttribute(attrs) { @@ -857,30 +1935,23 @@ module.exports = function getEventAttribute(attrs) { } }; -},{"./getEventDirectives":10}],10:[function(require,module,exports){ -'use strict'; - +},{"./getEventDirectives":42}],42:[function(require,module,exports){ module.exports = function getEventDirectives() { - var list = 'click submit mouseenter mouseleave mousemove mousedown mouseover mouseup dblclick keyup keydown keypress blur focus submit cut copy paste'.split(' '); + var list = 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '); var eventDirHash = {}; - list.forEach(function(dirName) { - dirName = 'ng'+dirName.charAt(0).toUpperCase()+dirName.substring(1); - eventDirHash[dirName] = dirName; + list.map(function(x){ + var name = 'ng'+x.charAt(0).toUpperCase()+x.substring(1); + eventDirHash[name]=name; }); return eventDirHash; }; -},{}],11:[function(require,module,exports){ -'use strict'; - +},{}],43:[function(require,module,exports){ module.exports = function getFunctionNames(str) { - if (typeof str !== 'string') { - return []; - } - var results = str.replace(/\s+/g, '').split(/[\+\-\/\|\<\>\^=&!%~]/g).map(function(x) { + var results = str.replace(/\s+/g,'').split(/[\+\-\/\|\<\>\^=&!%~]/g).map(function(x){ if(isNaN(+x)) { if(x.match(/\w+\(.*\)$/)){ - return x.substring(0, x.indexOf('(')); + return x.substring(0,x.indexOf('(')); } return x; } @@ -888,53 +1959,279 @@ module.exports = function getFunctionNames(str) { return results; }; -},{}],12:[function(require,module,exports){ -'use strict'; +},{}],44:[function(require,module,exports){ +var areSimilarEnough = require('./areSimilarEnough'); +var levenshteinDistance = require('./levenshtein'); + +module.exports = function getSuggestion(original, props) { + var min_levDist = Infinity, closestMatch = ''; + for(var i in props) { + var prop = props[i]; + if(areSimilarEnough(original, prop)) { + var currentlevDist = levenshteinDistance(original, prop); + var closestMatch = (currentlevDist < min_levDist)? prop : closestMatch; + var min_levDist = (currentlevDist < min_levDist)? currentlevDist : min_levDist; + } + } + return closestMatch; +}; +},{"./areSimilarEnough":39,"./levenshtein":46}],45:[function(require,module,exports){ module.exports = function getValidProps(obj) { var props = []; for(var prop in obj) { - if (prop.charAt(0) !== '$' && typeof obj[prop] === 'function') { + if (prop.charAt(0) != '$' && typeof obj[prop] == 'function') { props.push(prop); } } return props; }; -},{}],13:[function(require,module,exports){ +},{}],46:[function(require,module,exports){ +module.exports = function levenshteinDistance(s, t) { + if(typeof s !== 'string' || typeof t !== 'string') { + throw new Error('Function must be passed two strings, given: '+typeof s+' and '+typeof t+'.'); + } + var d = []; + var n = s.length; + var m = t.length; + + if (n == 0) return m; + if (m == 0) return n; + + for (var i = n; i >= 0; i--) d[i] = []; + for (var i = n; i >= 0; i--) d[i][0] = i; + for (var j = m; j >= 0; j--) d[0][j] = j; + for (var i = 1; i <= n; i++) { + var s_i = s.charAt(i - 1); + + for (var j = 1; j <= m; j++) { + if (i == j && d[i][j] > 4) return n; + var t_j = t.charAt(j - 1); + var cost = (s_i == t_j) ? 0 : 1; + var mi = d[i - 1][j] + 1; + var b = d[i][j - 1] + 1; + var c = d[i - 1][j - 1] + cost; + if (b < mi) mi = b; + if (c < mi) mi = c; + d[i][j] = mi; + if (i > 1 && j > 1 && s_i == t.charAt(j - 2) && s.charAt(i - 2) == t_j) { + d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost); + } + } + } + return d[n][m]; +}; + +},{}],47:[function(require,module,exports){ +module.exports=require(33) +},{"/home/spike/repos/angularjs-batarang/node_modules/angular-hint/node_modules/angular-hint-directives/node_modules/angular-hint-log/hint-log.js":33}],48:[function(require,module,exports){ +'use strict'; + +var getAllParts = require('./lib/getAllParts'); +var buildMessage = require('./lib/buildMessage'); + +angular.module('ngHintInterpolation', []) + .config(['$provide', function($provide) { + var ngHintInterpMessages = []; + $provide.decorator('$interpolate', ['$delegate', '$timeout', function($delegate, $timeout) { + var interpolateWrapper = function() { + var interpolationFn = $delegate.apply(this, arguments); + if(interpolationFn) { + var parts = getAllParts(arguments[0],$delegate.startSymbol(),$delegate.endSymbol()); + var temp = interpolationFnWrap(interpolationFn,arguments, parts); + return temp; + } + }; + var interpolationFnWrap = function(interpolationFn, interpolationArgs, allParts) { + return function(){ + var result = interpolationFn.apply(this, arguments); + buildMessage(allParts, interpolationArgs[0].trim(), arguments[0], $timeout); + return result; + }; + }; + angular.extend(interpolateWrapper,$delegate); + return interpolateWrapper; + }]); + }]); + +},{"./lib/buildMessage":50,"./lib/getAllParts":52}],49:[function(require,module,exports){ +module.exports = function(s,t) { + var strMap = {}, similarities = 0, STRICTNESS = 0.66; + if(Math.abs(s.length-t.length) > 3) { + return false; + } + s.split('').forEach(function(x){strMap[x] = x;}); + for (var i = t.length - 1; i >= 0; i--) { + similarities = strMap[t.charAt(i)] ? similarities + 1 : similarities; + } + return similarities >= t.length * STRICTNESS; +}; + +},{}],50:[function(require,module,exports){ +var hintLog = angular.hint = require('angular-hint-log'); + +var partsEvaluate = require('./partsEvaluate'); + +module.exports = function(allParts, originalInterpolation, scope, $timeout) { + var message = partsEvaluate(allParts, originalInterpolation, scope); + if(message) { + hintLog.logMessage('##Interpolation## ' + message); + } +}; + +},{"./partsEvaluate":57,"angular-hint-log":58}],51:[function(require,module,exports){ +module.exports = function(parts, concatLength) { + var total = ''; + for(var i = 0; i <= concatLength; i++) { + var period = (i===0) ? '' : '.'; + total+=period+parts[i].trim(); + } + return total; +}; + +},{}],52:[function(require,module,exports){ +var getInterpolation = require('./getInterpolation'); +var getOperands = require('./getOperands'); +var concatParts = require('./concatParts'); + +module.exports = function(text, startSym, endSym) { + if(text.indexOf(startSym) < 0 || text.indexOf(endSym) < 0) { + throw new Error('Missing start or end symbol in interpolation. Start symbol: "'+startSym+ + '" End symbol: "'+endSym+'"'); + } + var comboParts = []; + var interpolation = getInterpolation(text, startSym, endSym); + var operands = getOperands(interpolation); + operands.forEach(function(operand) { + var opParts = operand.split('.'); + for(var i = 0; i < opParts.length; i++) { + var result = concatParts(opParts,i); + if(result && comboParts.indexOf(result) < 0 && isNaN(+result)){ + comboParts.push(result); + } + } + }); + return comboParts; +}; + +},{"./concatParts":51,"./getInterpolation":53,"./getOperands":54}],53:[function(require,module,exports){ +module.exports = function(text, startSym, endSym) { + var startInd = text.indexOf(startSym) + startSym.length; + var endInd = text.indexOf(endSym); + return text.substring(startInd, endInd); +}; + +},{}],54:[function(require,module,exports){ +module.exports = function(str) { + return str.split(/[\+\-\/\|<\>\^=&!%~]/g); +}; + +},{}],55:[function(require,module,exports){ +var areSimilarEnough = require('./areSimilarEnough'); +var levenshtein = require('./levenshtein'); + +module.exports = function (part, scope) { + var min_levDist = Infinity, closestMatch = ''; + for(var i in scope) { + if(areSimilarEnough(part, i)) { + var currentlevDist = levenshtein(part, i); + closestMatch = (currentlevDist < min_levDist)? i : closestMatch; + min_levDist = (currentlevDist < min_levDist)? currentlevDist : min_levDist; + } + } + return closestMatch; +}; + +},{"./areSimilarEnough":49,"./levenshtein":56}],56:[function(require,module,exports){ +module.exports = function(s, t) { + if(typeof s !== 'string' || typeof t !== 'string') { + throw new Error('Function must be passed two strings, given: '+typeof s+' and '+typeof t+'.'); + } + var d = []; + var n = s.length; + var m = t.length; + + if (n === 0) {return m;} + if (m === 0) {return n;} + + for (var ii = n; ii >= 0; ii--) { d[ii] = []; } + for (var ii = n; ii >= 0; ii--) { d[ii][0] = ii; } + for (var jj = m; jj >= 0; jj--) { d[0][jj] = jj; } + for (var i = 1; i <= n; i++) { + var s_i = s.charAt(i - 1); + + for (var j = 1; j <= m; j++) { + if (i == j && d[i][j] > 4) return n; + var t_j = t.charAt(j - 1); + var cost = (s_i == t_j) ? 0 : 1; + var mi = d[i - 1][j] + 1; + var b = d[i][j - 1] + 1; + var c = d[i - 1][j - 1] + cost; + if (b < mi) mi = b; + if (c < mi) mi = c; + d[i][j] = mi; + if (i > 1 && j > 1 && s_i == t.charAt(j - 2) && s.charAt(i - 2) == t_j) { + d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost); + } + } + } + return d[n][m]; +}; + +},{}],57:[function(require,module,exports){ +var getSuggestion = require('./getSuggestion'); + +module.exports = function(allParts, originalInterpolation, scope) { + var message, found = false; + allParts.forEach(function(part) { + if(!scope.$eval(part) && !found){ + found = true; + var perInd = part.lastIndexOf('.'); + var tempScope = (perInd > -1) ? scope.$eval(part.substring(0, perInd)) : scope; + var tempPart = part.substring(part.lastIndexOf('.') + 1); + var suggestion = getSuggestion(tempPart, tempScope); + suggestion = (suggestion) ? ' Try: "'+suggestion+'"' : ''; + message = '"'+part+'" was found to be undefined in "'+originalInterpolation+'".'+ suggestion; + } + }); + return message; +}; + +},{"./getSuggestion":55}],58:[function(require,module,exports){ +module.exports=require(33) +},{"/home/spike/repos/angularjs-batarang/node_modules/angular-hint/node_modules/angular-hint-directives/node_modules/angular-hint-log/hint-log.js":33}],59:[function(require,module,exports){ /** * HintLog creates a queue of messages logged by ngHint modules. This object * has a key for each ngHint module that corresponds to the messages * from that module. */ -var queuedMessages = {}, - MESSAGE_TYPES = [ - 'error', - 'warning', - 'suggestion' - ]; +var queuedMessages = {}; /** * Add a message to the HintLog message queue. Messages are organized into categories -* according to their module name and severity. +* according to their module name which is included in the message with ##ModuleName##. +* If a ##ModuleName## is not included, the message is added to a `General` category +* in the queue. **/ -function logMessage(moduleName, message, severity, category) { - // If no severity was provided, categorize the message as a `suggestion` - severity = severity || 3; - var messageType = MESSAGE_TYPES[severity - 1]; - - // If no ModuleName was found, categorize the message under `General` - moduleName = moduleName || 'General'; - - // If the category does not exist, initialize a new object - queuedMessages[moduleName] = queuedMessages[moduleName] || {}; - queuedMessages[moduleName][messageType] = queuedMessages[moduleName][messageType] || []; - - if (queuedMessages[moduleName][messageType].indexOf(message) < 0) { - queuedMessages[moduleName][messageType].push(message); +function logMessage(message) { + //HintLog messages are delimited by `##ModuleName## Module Message` + //Split the message into the name and message value + var nameThenValue = message.split(/##/); + //If no ##ModuleName## was found, categorize the message under `General` + if(nameThenValue[0] !== '') { + //If the category does not exist, initialize a new object + queuedMessages.General = queuedMessages.General || {}; + queuedMessages.General[message] = message; + } else { + //Strip leading spaces in message caused by splitting out ##ModuleName## + nameThenValue[2] = nameThenValue[2].charAt(0) === ' ' ? nameThenValue[2].substring(1) + : nameThenValue[2]; + //If the category does not exist, initialize a new object + queuedMessages[nameThenValue[1]] = queuedMessages[nameThenValue[1]] || {}; + queuedMessages[nameThenValue[1]][nameThenValue[2]] = nameThenValue[2]; } - - module.exports.onMessage(moduleName, message, messageType, category); + module.exports.onMessage(message); } /** @@ -949,88 +2246,35 @@ function flush() { module.exports.onMessage = function(message) {}; module.exports.logMessage = logMessage; module.exports.flush = flush; - -},{}],14:[function(require,module,exports){ -module.exports = distance; - -function distance(a, b) { - var table = []; - if (a.length === 0 || b.length === 0) return Math.max(a.length, b.length); - for (var ii = 0, ilen = a.length + 1; ii !== ilen; ++ii) { - table[ii] = []; - for (var jj = 0, jlen = b.length + 1; jj !== jlen; ++jj) { - if (ii === 0 || jj === 0) table[ii][jj] = Math.max(ii, jj); - else { - var diagPenalty = Number(a[ii-1] !== b[jj-1]); - var diag = table[ii - 1][jj - 1] + diagPenalty; - var top = table[ii - 1][jj] + 1; - var left = table[ii][jj - 1] + 1; - table[ii][jj] = Math.min(left, top, diag); - } - } - } - return table[a.length][b.length]; -} - - -},{}],15:[function(require,module,exports){ -module.exports = suggestDictionary; - -var distance = require('./levenstein_distance'); - -function suggestDictionary(dict, opts) { - opts = opts || {}; - var threshold = opts.threshold || 0.5; - return function suggest(word) { - var length = word.length; - return dict.reduce(function (result, dictEntry) { - var score = distance(dictEntry, word); - if (result.score > score && score / length < threshold) { - result.score = score; - result.word = dictEntry; - } - return result; - }, { score: Infinity }).word; - }; -} - -suggestDictionary.distance = distance; - -},{"./levenstein_distance":14}],16:[function(require,module,exports){ -module.exports=require(5) -},{"/Users/bford/Development/angularjs-batarang/node_modules/angular-hint/node_modules/angular-hint-controllers/node_modules/angular-hint-log/hint-log.js":5}],17:[function(require,module,exports){ +},{}],60:[function(require,module,exports){ 'use strict'; -var storeDependencies = require('./lib/storeDependencies'), - getModule = require('./lib/getModule'), - start = require('./lib/start'), - storeNgAppAndView = require('./lib/storeNgAppAndView'), - storeUsedModules = require('./lib/storeUsedModules'), - hasNameSpace = require('./lib/hasNameSpace'), - modData = require('./lib/moduleData'); +var hintLog = angular.hint = require('angular-hint-log'); +var storeDependencies = require('./lib/storeDependencies'); +var getModule = require('./lib/getModule'); +var start = require('./lib/start'); +var storeNgAppAndView = require('./lib/storeNgAppAndView'); +var storeUsedModules = require('./lib/storeUsedModules'); +var modData = require('./lib/moduleData'); -var doc = Array.prototype.slice.call(document.getElementsByTagName('*')), - originalAngularModule = angular.module, - modules = {}; +var doc = Array.prototype.slice.call(document.getElementsByTagName('*')); +var originalAngularModule = angular.module; +var modules = {}; storeNgAppAndView(doc); -angular.module = function(name, requiresOriginal) { - var module = originalAngularModule.apply(this, arguments), - name = module.name; - - module.requiresOriginal = requiresOriginal; +angular.module = function() { + var module = originalAngularModule.apply(this,arguments); + var name = module.name; modules[name] = module; - hasNameSpace(name); - var modToCheck = getModule(name, true); - - if(modToCheck && modToCheck.requiresOriginal !== module.requiresOriginal) { - if(!modData.createdMulti[name]) { - modData.createdMulti[name] = [getModule(name,true)]; + var modToCheck = getModule(module.name, true); + if(modToCheck && modToCheck.requires.length && module.requires.length) { + if(!modData.createdMulti[module.name]) { + modData.createdMulti[module.name] = [getModule(module.name,true)]; } - modData.createdMulti[name].push(module); + modData.createdMulti[module.name].push(module); } - modData.createdModules[name] = module; + modData.createdModules[module.name] = module; return module; }; @@ -1040,142 +2284,136 @@ angular.module('ngHintModules', []).config(function() { start(); }); -},{"./lib/getModule":20,"./lib/hasNameSpace":24,"./lib/moduleData":26,"./lib/start":29,"./lib/storeDependencies":30,"./lib/storeNgAppAndView":31,"./lib/storeUsedModules":32}],18:[function(require,module,exports){ -var hintLog = angular.hint = require('angular-hint-log'), - MODULE_NAME = 'Modules'; +},{"./lib/getModule":64,"./lib/moduleData":71,"./lib/start":74,"./lib/storeDependencies":75,"./lib/storeNgAppAndView":76,"./lib/storeUsedModules":77,"angular-hint-log":59}],61:[function(require,module,exports){ +module.exports = function(s,t) { + var strMap = {}, + similarities = 0, + STRICTNESS = 0.66; + if(Math.abs(s.length-t.length) > 3) { + return false; + } + s.split('').forEach(function(x){strMap[x] = x;}); + for (var i = t.length - 1; i >= 0; i--) { + similarities = strMap[t.charAt(i)] ? similarities + 1 : similarities; + } + return similarities >= t.length * STRICTNESS; +}; + +},{}],62:[function(require,module,exports){ +var hintLog = angular.hint = require('angular-hint-log'); -module.exports = function(modules) { - modules.forEach(function(module) { - hintLog.logMessage(MODULE_NAME, module.message, module.severity); +module.exports = function(unusedModules) { + unusedModules.forEach(function(module) { + hintLog.logMessage('##Modules## ' + module.message); }); }; -},{"angular-hint-log":16}],19:[function(require,module,exports){ +},{"angular-hint-log":59}],63:[function(require,module,exports){ var modData = require('./moduleData'); - MODULE_NAME = 'Modules', - SEVERITY_WARNING = 2; module.exports = function() { var multiLoaded = []; for(var modName in modData.createdMulti) { - var message = 'Multiple modules with name "' + modName + '" are being created and they will ' + - 'overwrite each other.'; + var message = 'Multiple modules with name "'+modName+'" are being created and they will overwrite each other.'; var multi = modData.createdMulti[modName]; - var multiLength = multi.length; var details = { - existingModule: multi[multiLength - 1], - overwrittenModules: multi.slice(0, multiLength - 1) + existingModule: multi[multi.length - 1], + overwrittenModules: multi.slice(0,multi.length-1) }; - multiLoaded - .push({module: details, message: message, name: MODULE_NAME, severity: SEVERITY_WARNING}); + multiLoaded.push({module:details, message:message}); } return multiLoaded; }; -},{"./moduleData":26}],20:[function(require,module,exports){ +},{"./moduleData":71}],64:[function(require,module,exports){ var modData = require('./moduleData'); module.exports = function(moduleName, getCreated) { - return (getCreated)? modData.createdModules[moduleName] : modData.loadedModules[moduleName]; -}; - -},{"./moduleData":26}],21:[function(require,module,exports){ -var hintLog = angular.hint = require('angular-hint-log'), - MODULE_NAME = 'Modules', - SEVERITY_ERROR = 1; - module.exports = function(attrs, ngAppFound) { - if(attrs['ng-app'] && ngAppFound) { - hintLog.logMessage(MODULE_NAME, 'ng-app may only be included once. The module "' + - attrs['ng-app'].value + '" was not used to bootstrap because ng-app was already included.', - SEVERITY_ERROR); - } + return (getCreated)? modData.createdModules[moduleName] : modData.loadedModules[moduleName]; +}; + +},{"./moduleData":71}],65:[function(require,module,exports){ +module.exports = function(attrs) { return attrs['ng-app'] ? attrs['ng-app'].value : undefined; - }; +}; +},{}],66:[function(require,module,exports){ +var levenshteinDistance = require('./levenshtein'); +var areSimilarEnough = require('./areSimilarEnough'); +var modData = require('./moduleData'); +module.exports = function(module){ + var min_levDist = Infinity, + closestMatch = ''; + for(var createdModule in modData.createdModules) { + if(areSimilarEnough(createdModule, module)) { + var currentlevDist = levenshteinDistance(module, createdModule); + if(currentlevDist < 5) { + closestMatch = (currentlevDist < min_levDist)? createdModule : closestMatch; + min_levDist = (currentlevDist < min_levDist)? currentlevDist : min_levDist; + } + } + } + return closestMatch; +}; -},{"angular-hint-log":16}],22:[function(require,module,exports){ -var getModule = require('./getModule'), - dictionary = Object.keys(require('./moduleData').createdModules), - suggest = require('suggest-it')(dictionary), - SEVERITY_ERROR = 1; +},{"./areSimilarEnough":61,"./levenshtein":70,"./moduleData":71}],67:[function(require,module,exports){ +var getModule = require('./getModule'); +var getSuggestion = require('./getSuggestion'); module.exports = function(loadedModules) { var undeclaredModules = []; - for(var module in loadedModules) { + for( var module in loadedModules) { var cModule = getModule(module, true); if(!cModule) { - var match = suggest(module), - suggestion = (match) ? '; Try: "'+match+'"' : '', - message = 'Module "'+module+'" was loaded but does not exist'+suggestion+'.'; - - undeclaredModules.push({module: null, message: message, severity: SEVERITY_ERROR}); + var match = getSuggestion(module); + var suggestion = (match) ? '; Try: "'+match+'"' : ''; + var message = 'Module "'+module+'" was loaded but does not exist'+suggestion+'.'; + undeclaredModules.push({module:null, message:message}); } } return undeclaredModules; }; -},{"./getModule":20,"./moduleData":26,"suggest-it":34}],23:[function(require,module,exports){ +},{"./getModule":64,"./getSuggestion":66}],68:[function(require,module,exports){ var getModule = require('./getModule'); -var IGNORED = ['ngHintControllers', 'ngHintDirectives', 'ngHintDom', 'ngHintEvents', - 'ngHintInterpolation', 'ngHintModules', 'ngHintScopes', 'ng', 'ngLocale', 'protractorBaseModule_'], - SEVERITY_WARNING = 2; - module.exports = function(createdModules) { var unusedModules = []; for(var module in createdModules) { if(!getModule(module)) { - var cModule = createdModules[module], - message = 'Module "' + cModule.name + '" was created but never loaded.'; - if(IGNORED.indexOf(cModule.name) === -1) { - unusedModules.push({module: cModule, message: message, severity: SEVERITY_WARNING}); - } + var cModule = createdModules[module]; + var message = 'Module "'+cModule.name+'" was created but never loaded.'; + unusedModules.push({module:cModule, message:message}); } } return unusedModules; }; -},{"./getModule":20}],24:[function(require,module,exports){ -var hintLog = angular.hint = require('angular-hint-log'), - MODULE_NAME = 'Modules', - SEVERITY_SUGGESTION = 3; - -module.exports = function(str) { - if (str === 'ng') { - return true; - } - if(str.toLowerCase() === str || str.charAt(0).toUpperCase() === str.charAt(0)) { - hintLog.logMessage(MODULE_NAME, 'The best practice for' + - ' module names is to use lowerCamelCase. Check the name of "' + str + '".', - SEVERITY_SUGGESTION); - return false; - } - return true; -}; - -},{"angular-hint-log":16}],25:[function(require,module,exports){ +},{"./getModule":64}],69:[function(require,module,exports){ var normalizeAttribute = require('./normalizeAttribute'); module.exports = function(attrs) { - for(var i = 0, length = attrs.length; i < length; i++) { - if(normalizeAttribute(attrs[i].nodeName) === 'ng-view' || - attrs[i].value.indexOf('ng-view') > -1) { - return true; + for(var i = 0; i < attrs.length; i++) { + if(normalizeAttribute(attrs[i].nodeName) === 'ng-view' + || attrs[i].value.indexOf('ng-view') > -1) { + return true; } } }; -},{"./normalizeAttribute":28}],26:[function(require,module,exports){ +},{"./normalizeAttribute":73}],70:[function(require,module,exports){ +module.exports=require(56) +},{"/home/spike/repos/angularjs-batarang/node_modules/angular-hint/node_modules/angular-hint-interpolation/lib/levenshtein.js":56}],71:[function(require,module,exports){ module.exports = { - createdModules: {}, - createdMulti: {}, - loadedModules: {} -}; + createdModules: {}, + createdMulti: {}, + loadedModules: {} + }; -},{}],27:[function(require,module,exports){ -var modData = require('./moduleData'), - getModule = require('./getModule'); +},{}],72:[function(require,module,exports){ +var modData = require('./moduleData'); +var getModule = require('./getModule'); module.exports = function() { if(modData.ngViewExists && !getModule('ngRoute')) { @@ -1183,24 +2421,24 @@ module.exports = function() { } }; -},{"./getModule":20,"./moduleData":26}],28:[function(require,module,exports){ +},{"./getModule":64,"./moduleData":71}],73:[function(require,module,exports){ module.exports = function(attribute) { - return attribute.replace(/^(?:data|x)[-_:]/, '').replace(/[:_]/g, '-'); + return attribute.replace(/^(?:data|x)[-_:]/,'').replace(/[:_]/g,'-'); }; -},{}],29:[function(require,module,exports){ -var display = require('./display'), - formatMultiLoaded = require('./formatMultiLoaded'), - getUnusedModules = require('./getUnusedModules'), - getUndeclaredModules = require('./getUndeclaredModules'), - modData = require('./moduleData'), - ngViewNoNgRoute = require('./ngViewNoNgRoute'); +},{}],74:[function(require,module,exports){ +var display = require('./display'); +var formatMultiLoaded = require('./formatMultiLoaded'); +var getUnusedModules = require('./getUnusedModules'); +var getUndeclaredModules = require('./getUndeclaredModules'); +var modData = require('./moduleData'); +var ngViewNoNgRoute = require('./ngViewNoNgRoute'); module.exports = function() { - var unusedModules = getUnusedModules(modData.createdModules), - undeclaredModules = getUndeclaredModules(modData.loadedModules), - multiLoaded = formatMultiLoaded(), - noNgRoute = ngViewNoNgRoute(); + var unusedModules = getUnusedModules(modData.createdModules); + var undeclaredModules = getUndeclaredModules(modData.loadedModules); + var multiLoaded = formatMultiLoaded(); + var noNgRoute = ngViewNoNgRoute(); if(unusedModules.length || undeclaredModules.length || multiLoaded.length || noNgRoute) { var toSend = unusedModules.concat(undeclaredModules) .concat(multiLoaded); @@ -1211,7 +2449,7 @@ module.exports = function() { } }; -},{"./display":18,"./formatMultiLoaded":19,"./getUndeclaredModules":22,"./getUnusedModules":23,"./moduleData":26,"./ngViewNoNgRoute":27}],30:[function(require,module,exports){ +},{"./display":62,"./formatMultiLoaded":63,"./getUndeclaredModules":67,"./getUnusedModules":68,"./moduleData":71,"./ngViewNoNgRoute":72}],75:[function(require,module,exports){ var modData = require('./moduleData'); module.exports = function(module, isNgAppMod) { @@ -1227,11 +2465,11 @@ module.exports = function(module, isNgAppMod) { } }; -},{"./moduleData":26}],31:[function(require,module,exports){ -var getNgAppMod = require('./getNgAppMod'), - inAttrsOrClasses = require('./inAttrsOrClasses'), - storeDependencies = require('./storeDependencies'), - modData = require('./moduleData'); +},{"./moduleData":71}],76:[function(require,module,exports){ +var getNgAppMod = require('./getNgAppMod'); +var inAttrsOrClasses = require('./inAttrsOrClasses'); +var storeDependencies = require('./storeDependencies'); +var modData = require('./moduleData'); module.exports = function(doms) { var bothFound, @@ -1243,14 +2481,11 @@ module.exports = function(doms) { for(var i = 0; i < doms.length; i++) { elem = doms[i]; - var attributes = elem.attributes; isElemName = elem.nodeName.toLowerCase() === 'ng-view'; - isInAttrsOrClasses = inAttrsOrClasses(attributes); + isInAttrsOrClasses = inAttrsOrClasses(elem.attributes); ngViewFound = isElemName || isInAttrsOrClasses; - - ngAppMod = getNgAppMod(attributes, modData.ngAppFound); - modData.ngAppFound = modData.ngAppFound || ngAppMod; + ngAppMod = getNgAppMod(elem.attributes); if(ngAppMod) { storeDependencies(ngAppMod, true); @@ -1263,7 +2498,7 @@ module.exports = function(doms) { } }; -},{"./getNgAppMod":21,"./inAttrsOrClasses":25,"./moduleData":26,"./storeDependencies":30}],32:[function(require,module,exports){ +},{"./getNgAppMod":65,"./inAttrsOrClasses":69,"./moduleData":71,"./storeDependencies":75}],77:[function(require,module,exports){ var storeDependencies = require('./storeDependencies'); var storeUsedModules = module.exports = function(module, modules){ @@ -1274,387 +2509,5 @@ var storeUsedModules = module.exports = function(module, modules){ storeUsedModules(mod, modules); }); } -}; -},{"./storeDependencies":30}],33:[function(require,module,exports){ -module.exports=require(14) -},{"/Users/bford/Development/angularjs-batarang/node_modules/angular-hint/node_modules/angular-hint-events/node_modules/suggest-it/lib/levenstein_distance.js":14}],34:[function(require,module,exports){ -module.exports=require(15) -},{"./levenstein_distance":33,"/Users/bford/Development/angularjs-batarang/node_modules/angular-hint/node_modules/angular-hint-events/node_modules/suggest-it/lib/suggest-it.js":15}],35:[function(require,module,exports){ -'use strict'; - -var summarize = require('./lib/summarize-model'); -var hint = angular.hint = require('angular-hint-log'); -var debounceOn = require('debounce-on'); - -hint.emit = function () {}; - -module.exports = angular.module('ngHintScopes', []).config(['$provide', function ($provide) { - $provide.decorator('$rootScope', ['$delegate', '$parse', decorateRootScope]); - $provide.decorator('$compile', ['$delegate', decorateDollaCompile]); -}]); - -function decorateRootScope($delegate, $parse) { - - var perf = window.performance || { now: function () { return 0; } }; - - var scopes = {}, - watching = {}; - - var debouncedEmitModelChange = debounceOn(emitModelChange, 10, byScopeId); - - hint.watch = function (scopeId, path) { - path = typeof path === 'string' ? path.split('.') : path; - - if (!watching[scopeId]) { - watching[scopeId] = {}; - } - - for (var i = 1, ii = path.length; i <= ii; i += 1) { - var partialPath = path.slice(0, i).join('.'); - if (watching[scopeId][partialPath]) { - continue; - } - var get = gettterer(scopeId, partialPath); - var value = summarize(get()); - watching[scopeId][partialPath] = { - get: get, - value: value - }; - hint.emit('model:change', { - id: scopeId, - path: partialPath, - value: value - }); - } - }; - - hint.assign = function (scopeId, path, value) { - var scope; - if (scope = scopes[scopeId]) { - scope.$apply(function () { - return $parse(path).assign(scope, value); - }); - } - }; - - hint.inspectScope = function (scopeId) { - var scope; - if (scope = scopes[scopeId]) { - window.$scope = scope; - } - }; - - hint.unwatch = function (scopeId, unwatchPath) { - Object.keys(watching[scopeId]). - forEach(function (path) { - if (path.indexOf(unwatchPath) === 0) { - delete watching[scopeId][path]; - } - }); - }; - - var debouncedEmit = debounceOn(hint.emit, 10, function (params) { - return params.id + params.path; - }); - - - var scopePrototype = ('getPrototypeOf' in Object) ? - Object.getPrototypeOf($delegate) : $delegate.__proto__; - - // var _watch = scopePrototype.$watch; - // scopePrototype.$watch = function (watchExpression, reactionFunction) { - // var watchStr = humanReadableWatchExpression(watchExpression); - // var scopeId = this.$id; - // if (typeof watchExpression === 'function') { - // arguments[0] = function () { - // var start = perf.now(); - // var ret = watchExpression.apply(this, arguments); - // var end = perf.now(); - // hint.emit('scope:watch', { - // id: scopeId, - // watch: watchStr, - // time: end - start - // }); - // return ret; - // }; - // } else { - // var thatScope = this; - // arguments[0] = function () { - // var start = perf.now(); - // var ret = thatScope.$eval(watchExpression); - // var end = perf.now(); - // hint.emit('scope:watch', { - // id: scopeId, - // watch: watchStr, - // time: end - start - // }); - // return ret; - // }; - // } - - // if (typeof reactionFunction === 'function') { - // var applyStr = reactionFunction.toString(); - // arguments[1] = function () { - // var start = perf.now(); - // var ret = reactionFunction.apply(this, arguments); - // var end = perf.now(); - // hint.emit('scope:reaction', { - // id: this.$id, - // watch: watchStr, - // time: end - start - // }); - // return ret; - // }; - // } - - // return _watch.apply(this, arguments); - // }; - - - var _destroy = scopePrototype.$destroy; - scopePrototype.$destroy = function () { - var id = this.id; - - hint.emit('scope:destroy', { id: id }); - - delete scopes[id]; - delete watching[id]; - - return _destroy.apply(this, arguments); - }; - - - var _new = scopePrototype.$new; - scopePrototype.$new = function () { - var child = _new.apply(this, arguments); - - scopes[child.$id] = child; - watching[child.$id] = {}; - - hint.emit('scope:new', { parent: this.$id, child: child.$id }); - setTimeout(function () { - emitScopeElt(child); - }, 0); - return child; - }; - - function emitScopeElt (scope) { - var scopeId = scope.$id; - var elt = findElt(scopeId); - var descriptor = scopeDescriptor(elt, scope); - hint.emit('scope:link', { - id: scopeId, - descriptor: descriptor - }); - } - - function findElt (scopeId) { - var elts = document.querySelectorAll('.ng-scope'); - var elt, scope; - - for (var i = 0; i < elts.length; i++) { - elt = angular.element(elts[i]); - scope = elt.scope(); - if (scope.$id === scopeId) { - return elt; - } - } - } - - - var _digest = scopePrototype.$digest; - scopePrototype.$digest = function (fn) { - var start = perf.now(); - var ret = _digest.apply(this, arguments); - var end = perf.now(); - hint.emit('scope:digest', { id: this.$id, time: end - start }); - return ret; - }; - - - var _apply = scopePrototype.$apply; - scopePrototype.$apply = function (fn) { - var start = perf.now(); - var ret = _apply.apply(this, arguments); - var end = perf.now(); - hint.emit('scope:apply', { id: this.$id, time: end - start }); - debouncedEmitModelChange(this); - return ret; - }; - - - function gettterer (scopeId, path) { - if (path === '') { - return function () { - return scopes[scopeId]; - }; - } - var getter = $parse(path); - return function () { - return getter(scopes[scopeId]); - }; - } - - function emitModelChange (scope) { - var scopeId = scope.$id; - if (watching[scopeId]) { - Object.keys(watching[scopeId]).forEach(function (path) { - var model = watching[scopeId][path]; - var value = summarize(model.get()); - if (value !== model.value) { - hint.emit('model:change', { - id: scope.$id, - path: path, - oldValue: model.value, - value: value - }); - model.value = value; - } - }); - } - } - - hint.emit('scope:new', { - parent: null, - child: $delegate.$id - }); - scopes[$delegate.$id] = $delegate; - watching[$delegate.$id] = {}; - - return $delegate; -} - -function decorateDollaCompile ($delegate) { - var newCompile = function () { - var link = $delegate.apply(this, arguments); - - return function (scope) { - var elt = link.apply(this, arguments); - var descriptor = scopeDescriptor(elt, scope); - hint.emit('scope:link', { - id: scope.$id, - descriptor: descriptor - }); - return elt; - } - }; - - // TODO: test this - // copy private helpers like $$addScopeInfo - for (var prop in $delegate) { - if ($delegate.hasOwnProperty(prop)) { - newCompile[prop] = $delegate[prop]; - } - } - return newCompile; -} - -function scopeDescriptor (elt, scope) { - var val, - types = [ - 'ng-app', - 'ng-controller', - 'ng-repeat', - 'ng-include' - ], - theseTypes = [], - type; - - if (elt) { - for (var i = 0; i < types.length; i++) { - type = types[i]; - if (val = elt.attr(type)) { - theseTypes.push(type + '="' + val + '"'); - } - } - } - if (theseTypes.length === 0) { - return 'scope.$id=' + scope.$id; - } else { - return theseTypes.join(' '); - } -} - -function byScopeId (scope) { - return scope.$id; -} - -function humanReadableWatchExpression (fn) { - if (fn.exp) { - fn = fn.exp; - } else if (fn.name) { - fn = fn.name; - } - return fn.toString(); -} - -},{"./lib/summarize-model":36,"angular-hint-log":37,"debounce-on":38}],36:[function(require,module,exports){ - -module.exports = function summarizeModel (model) { - - if (model instanceof Array) { - return JSON.stringify(model.map(summarizeProperty)); - } else if (typeof model === 'object') { - return JSON.stringify(Object. - keys(model). - filter(isAngularPrivatePropertyName). - reduce(shallowSummary, {})); - } else { - return model; - } - - function shallowSummary (obj, prop) { - obj[prop] = summarizeProperty(model[prop]); - return obj; - } -}; - -function isAngularPrivatePropertyName (key) { - return !(key[0] === '$' && key[1] === '$') && key !== '$parent' && key !== '$root'; -} - -// TODO: handle DOM nodes, fns, etc better. -function summarizeProperty (obj) { - return obj instanceof Array ? - { '~array-length': obj.length } : - obj === null ? - null : - typeof obj === 'object' ? - { '~object': true } : - obj; -} - -},{}],37:[function(require,module,exports){ -module.exports=require(5) -},{"/Users/bford/Development/angularjs-batarang/node_modules/angular-hint/node_modules/angular-hint-controllers/node_modules/angular-hint-log/hint-log.js":5}],38:[function(require,module,exports){ -module.exports = function debounceOn (fn, timeout, hash) { - var timeouts = {}; - - timeout = typeof timeout === 'number' ? timeout : (hash = timeout, 100); - hash = typeof hash === 'function' ? hash : defaultHash; - - return function () { - var key = hash.apply(null, arguments); - var args = arguments; - if (typeof timeouts[key] === 'undefined') { - timeouts[key] = setTimeout(function () { - delete timeouts[key]; - fn.apply(null, args); - }, timeout); - } - return function cancel () { - if (timeouts[key]) { - clearTimeout(timeouts[key]); - delete timeouts[key]; - return true; - } - return false; - }; - }; -}; - -function defaultHash () { - return Array.prototype.join.call(arguments, '::'); } - -},{}]},{},[1]); +},{"./storeDependencies":75}]},{},[1]);