From e81023bac7af4ab1233427dc8707964e88e6b517 Mon Sep 17 00:00:00 2001 From: "Carlo s A. Guillen" Date: Thu, 7 Aug 2014 14:38:26 -0700 Subject: [PATCH] feat(*): implement connection between content script and devTools pane --- HintCtrl.js | 12 + background.js | 8 + dist/hint.js | 2480 ++++++++++++++++++++++++++++++++ gulpfile.js | 16 + hint.bundle.js | 2916 ++++++++++++++++++++++++++++---------- hint.html | 2 - hint.js | 25 +- inject.js | 12 +- js/devtoolsBackground.js | 1 + manifest.json | 21 +- package.json | 7 +- 11 files changed, 4743 insertions(+), 757 deletions(-) create mode 100644 background.js create mode 100755 dist/hint.js create mode 100644 gulpfile.js diff --git a/HintCtrl.js b/HintCtrl.js index 424a4be..add88f1 100644 --- a/HintCtrl.js +++ b/HintCtrl.js @@ -6,6 +6,18 @@ angular.module('ngHintUI') $scope.module, $scope.type, $scope.isEmpty = ''; //message data will be an array sent from hint log to batarang to here + + // connect to background page + var port = chrome.extension.connect(); + port.onMessage.addListener(function(msg) { + $scope.$apply(function () { + $scope.messageData.Directives['Error-Messages'].push(msg + ' ' + Math.random()); + }); + }); + port.onDisconnect.addListener(function (a) { + console.log(a); + }); + $scope.messageData = { 'Modules': { 'Error-Messages': ['qwer$scope is a message', 'So issdfs $scope', 'Dont forget asdfsbout me too'], diff --git a/background.js b/background.js new file mode 100644 index 0000000..1ad6aac --- /dev/null +++ b/background.js @@ -0,0 +1,8 @@ + +chrome.runtime.onConnect.addListener(function(port) { + + // context script –> background + chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) { + port.postMessage(msg); + }); +}); diff --git a/dist/hint.js b/dist/hint.js new file mode 100755 index 0000000..8d41d4b --- /dev/null +++ b/dist/hint.js @@ -0,0 +1,2480 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o} */ + var modules = {}; + + /** + * @ngdoc function + * @name angular.module + * @module ng + * @description + * + * The `angular.module` is a global place for creating, registering and retrieving Angular + * modules. + * All modules (angular core or 3rd party) that should be available to an application must be + * registered using this mechanism. + * + * When passed two or more arguments, a new module is created. If passed only one argument, an + * existing module (the name passed as the first argument to `module`) is retrieved. + * + * + * # Module + * + * A module is a collection of services, directives, controllers, filters, and configuration information. + * `angular.module` is used to configure the {@link auto.$injector $injector}. + * + * ```js + * // Create a new module + * var myModule = angular.module('myModule', []); + * + * // register a new service + * myModule.value('appName', 'MyCoolApp'); + * + * // configure existing services inside initialization blocks. + * myModule.config(['$locationProvider', function($locationProvider) { + * // Configure existing providers + * $locationProvider.hashPrefix('!'); + * }]); + * ``` + * + * Then you can create an injector and load your modules like this: + * + * ```js + * var injector = angular.injector(['ng', 'myModule']) + * ``` + * + * However it's more likely that you'll just use + * {@link ng.directive:ngApp ngApp} or + * {@link angular.bootstrap} to simplify this process for you. + * + * @param {!string} name The name of the module to create or retrieve. + * @param {!Array.=} requires If specified then new module is being created. If + * unspecified then the module is being retrieved for further configuration. + * @param {Function=} configFn Optional configuration function for the module. Same as + * {@link angular.Module#config Module#config()}. + * @returns {module} new module with the {@link angular.Module} api. + */ + return function module(name, requires, configFn) { + var assertNotHasOwnProperty = function(name, context) { + if (name === 'hasOwnProperty') { + throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); + } + }; + + assertNotHasOwnProperty(name, 'module'); + if (requires && modules.hasOwnProperty(name)) { + modules[name] = null; + } + return ensure(modules, name, function() { + if (!requires) { + throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + + "the module name or forgot to load it. If registering a module ensure that you " + + "specify the dependencies as the second argument.", name); + } + + /** @type {!Array.>} */ + var invokeQueue = []; + + /** @type {!Array.} */ + var runBlocks = []; + + var config = invokeLater('$injector', 'invoke'); + + /** @type {angular.Module} */ + var moduleInstance = { + // Private state + _invokeQueue: invokeQueue, + _runBlocks: runBlocks, + + /** + * @ngdoc property + * @name angular.Module#requires + * @module ng + * @returns {Array.} List of module names which must be loaded before this module. + * @description + * Holds the list of modules which the injector will load before the current module is + * loaded. + */ + requires: requires, + + /** + * @ngdoc property + * @name angular.Module#name + * @module ng + * @returns {string} Name of the module. + * @description + */ + name: name, + + + /** + * @ngdoc method + * @name angular.Module#provider + * @module ng + * @param {string} name service name + * @param {Function} providerType Construction function for creating new instance of the + * service. + * @description + * See {@link auto.$provide#provider $provide.provider()}. + */ + provider: invokeLater('$provide', 'provider'), + + /** + * @ngdoc method + * @name angular.Module#factory + * @module ng + * @param {string} name service name + * @param {Function} providerFunction Function for creating new instance of the service. + * @description + * See {@link auto.$provide#factory $provide.factory()}. + */ + factory: invokeLater('$provide', 'factory'), + + /** + * @ngdoc method + * @name angular.Module#service + * @module ng + * @param {string} name service name + * @param {Function} constructor A constructor function that will be instantiated. + * @description + * See {@link auto.$provide#service $provide.service()}. + */ + service: invokeLater('$provide', 'service'), + + /** + * @ngdoc method + * @name angular.Module#value + * @module ng + * @param {string} name service name + * @param {*} object Service instance object. + * @description + * See {@link auto.$provide#value $provide.value()}. + */ + value: invokeLater('$provide', 'value'), + + /** + * @ngdoc method + * @name angular.Module#constant + * @module ng + * @param {string} name constant name + * @param {*} object Constant value. + * @description + * Because the constant are fixed, they get applied before other provide methods. + * See {@link auto.$provide#constant $provide.constant()}. + */ + constant: invokeLater('$provide', 'constant', 'unshift'), + + /** + * @ngdoc method + * @name angular.Module#animation + * @module ng + * @param {string} name animation name + * @param {Function} animationFactory Factory function for creating new instance of an + * animation. + * @description + * + * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. + * + * + * Defines an animation hook that can be later used with + * {@link ngAnimate.$animate $animate} service and directives that use this service. + * + * ```js + * module.animation('.animation-name', function($inject1, $inject2) { + * return { + * eventName : function(element, done) { + * //code to run the animation + * //once complete, then run done() + * return function cancellationFunction(element) { + * //code to cancel the animation + * } + * } + * } + * }) + * ``` + * + * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and + * {@link ngAnimate ngAnimate module} for more information. + */ + animation: invokeLater('$animateProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#filter + * @module ng + * @param {string} name Filter name. + * @param {Function} filterFactory Factory function for creating new instance of filter. + * @description + * See {@link ng.$filterProvider#register $filterProvider.register()}. + */ + filter: invokeLater('$filterProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#controller + * @module ng + * @param {string|Object} name Controller name, or an object map of controllers where the + * keys are the names and the values are the constructors. + * @param {Function} constructor Controller constructor function. + * @description + * See {@link ng.$controllerProvider#register $controllerProvider.register()}. + */ + controller: invokeLater('$controllerProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#directive + * @module ng + * @param {string|Object} name Directive name, or an object map of directives where the + * keys are the names and the values are the factories. + * @param {Function} directiveFactory Factory function for creating new instance of + * directives. + * @description + * See {@link ng.$compileProvider#directive $compileProvider.directive()}. + */ + directive: invokeLater('$compileProvider', 'directive'), + + /** + * @ngdoc method + * @name angular.Module#config + * @module ng + * @param {Function} configFn Execute this function on module load. Useful for service + * configuration. + * @description + * Use this method to register work which needs to be performed on module loading. + * For more about how to configure services, see + * {@link providers#providers_provider-recipe Provider Recipe}. + */ + config: config, + + /** + * @ngdoc method + * @name angular.Module#run + * @module ng + * @param {Function} initializationFn Execute this function after injector creation. + * Useful for application initialization. + * @description + * Use this method to register work which should be performed when the injector is done + * loading all modules. + */ + run: function(block) { + runBlocks.push(block); + return this; + } + }; + + if (configFn) { + config(configFn); + } + + return moduleInstance; + + /** + * @param {string} provider + * @param {string} method + * @param {String=} insertMethod + * @returns {angular.Module} + */ + function invokeLater(provider, method, insertMethod) { + return function() { + invokeQueue[insertMethod || 'push']([provider, method, arguments]); + return moduleInstance; + }; + } + }); + }; + }); + +} + +setupModuleLoader(window); +})(window); + +/** + * Closure compiler type information + * + * @typedef { { + * requires: !Array., + * invokeQueue: !Array.>, + * + * service: function(string, Function):angular.Module, + * factory: function(string, Function):angular.Module, + * value: function(string, *):angular.Module, + * + * filter: function(string, Function):angular.Module, + * + * init: function(Function):angular.Module + * } } + */ +angular.Module; + + +},{}],3:[function(require,module,exports){ +//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-events'); +require('angular-hint-interpolation'); +require('angular-hint-modules'); + +// List of all possible modules +// The default ng-hint behavior loads all modules +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. +// In this case `resumeBootstrap` should be patched to load the hint modules. +if (window.name === 'NG_DEFER_BOOTSTRAP!') { + var originalResumeBootstrap; + Object.defineProperty(angular, 'resumeBootstrap', { + get: function() { + return function(modules) { + return originalResumeBootstrap.call(angular, modules.concat(loadModules())); + }; + }, + set: function(resumeBootstrap) { + originalResumeBootstrap = resumeBootstrap; + } + }); +} +//If this is not a test, defer bootstrapping +else { + window.name = 'NG_DEFER_BOOTSTRAP!'; + + // determine which modules to load and resume bootstrap + document.addEventListener('DOMContentLoaded', maybeBootstrap); +} + +function maybeBootstrap() { + // we don't know if angular is loaded + if (!angular.resumeBootstrap) { + return setTimeout(maybeBootstrap, 1); + } + + var modules = loadModules(); + angular.resumeBootstrap(modules); +} + +function loadModules() { + var modules = [], elt; + + if ((elt = document.querySelector('[ng-hint-include]'))) { + modules = hintModulesFromElement(elt); + } else if (elt = document.querySelector('[ng-hint-exclude]')) { + modules = excludeModules(hintModulesFromElement(elt)); + } 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'); + } + return modules; +} + +function excludeModules(modulesToExclude) { + return allModules.filter(function(module) { + return modulesToExclude.indexOf(module) === -1; + }); +} + +function hintModulesFromElement (elt) { + var selectedModules = (elt.attributes['ng-hint-include'] || + elt.attributes['ng-hint-exclude']).value.split(' '); + + return selectedModules.map(hintModuleName).filter(function (name) { + return (allModules.indexOf(name) > -1) || + angular.hint.logMessage('##General## Module ' + name + ' could not be found'); + }); +} + +function hintModuleName(name) { + return 'ngHint' + title(name); +} + +function title(str) { + return str[0].toUpperCase() + str.substr(1); +} + +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-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 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(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); + }; + }]); + }]); + + +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 { + 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){ +/** + *@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]; +}; + +},{}],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,'-'); +}; + +},{}],31:[function(require,module,exports){ + +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.'); + } + options = options || {}; + options.directiveTypes = options.directiveTypes || defaultTypes; + options.tolerance = options.tolerance || 4; + if(customDirectives && customDirectives.length){ + setCustomDirectives(customDirectives); + } + 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'; + +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 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; +} + +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; +} + +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; + }, {}); +} + +module.exports = function (obj) { + if (typeof obj === 'string') { + return dashCase(obj); + } + return walk(obj); +}; + +},{}],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) { + nameToConstructorMappings[controllerName] = controllerConstructor; + return originalController.apply(this, arguments); + }; + return module; +}; + +},{"angular-hint-log":59,"dom-interceptor":36}],36:[function(require,module,exports){ +'use strict'; + +/** +* 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) {}; + +/** +* 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; +} + +/** +* Object to preserve all the original properties +* that will be restored after patching. +**/ +var originalProperties = {}; + +/** +* 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; +} + +/** +* 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; +} + +/** +* 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; +} + +/** +* 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.addManipulationListener = addManipulationListener; +module.exports.removeManipulationListener = removeManipulationListener; +module.exports.patchOnePrototype = patchOnePrototype; +module.exports.unpatchOnePrototype = unpatchOnePrototype; +module.exports.enableLineNumbers = enableLineNumbers; + + +},{}],37:[function(require,module,exports){ +'use strict'; + +var hintLog = angular.hint = require('angular-hint-log'); +var ngEventDirectives = require('./lib/getEventDirectives')(); + +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'; + + $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); + }; + }; + return $delegate; + }]); + } + }]); +},{"./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 props = getValidProps(messageObj.scope); + var suggestion = getSuggestion(messageObj.boundFunc, props); + messageObj['match'] = suggestion; + }); + return messages; +}; + +},{"./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; +}; + +},{}],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 : ''; + 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":38,"angular-hint-log":47}],41:[function(require,module,exports){ +var ngEventDirectives = require('./getEventDirectives')(); + +module.exports = function getEventAttribute(attrs) { + for(var attr in attrs) { + if(ngEventDirectives[attr]) { + return attr; + } + } +}; + +},{"./getEventDirectives":42}],42:[function(require,module,exports){ +module.exports = function getEventDirectives() { + var list = 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '); + var eventDirHash = {}; + list.map(function(x){ + var name = 'ng'+x.charAt(0).toUpperCase()+x.substring(1); + eventDirHash[name]=name; + }); + return eventDirHash; +}; + +},{}],43:[function(require,module,exports){ +module.exports = function getFunctionNames(str) { + 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; + } + }).filter(function(x){return x;}); + return results; +}; + +},{}],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') { + props.push(prop); + } + } + return props; +}; + +},{}],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) +},{}],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) +},{}],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 = {}; + +/** +* Add a message to the HintLog message queue. Messages are organized into categories +* 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(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(message); +} + +/** +* Return and empty the current queue of messages. +**/ +function flush() { + var flushMessages = queuedMessages; + queuedMessages = {}; + return flushMessages; +} + +module.exports.onMessage = function(message) {}; +module.exports.logMessage = logMessage; +module.exports.flush = flush; +},{}],60:[function(require,module,exports){ +'use strict'; + +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('*')); +var originalAngularModule = angular.module; +var modules = {}; + +storeNgAppAndView(doc); + +angular.module = function() { + var module = originalAngularModule.apply(this,arguments); + var name = module.name; + modules[name] = module; + 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[module.name].push(module); + } + modData.createdModules[module.name] = module; + return module; +}; + +angular.module('ngHintModules', []).config(function() { + var ngAppMod = modules[modData.ngAppMod]; + storeUsedModules(ngAppMod, modules); + start(); +}); + +},{"./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(unusedModules) { + unusedModules.forEach(function(module) { + hintLog.logMessage('##Modules## ' + module.message); + }); +}; + +},{"angular-hint-log":59}],63:[function(require,module,exports){ +var modData = require('./moduleData'); + +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 multi = modData.createdMulti[modName]; + var details = { + existingModule: multi[multi.length - 1], + overwrittenModules: multi.slice(0,multi.length-1) + }; + multiLoaded.push({module:details, message:message}); + } + return multiLoaded; +}; + +},{"./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":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; +}; + +},{"./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) { + var cModule = getModule(module, true); + if(!cModule) { + 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":64,"./getSuggestion":66}],68:[function(require,module,exports){ +var getModule = require('./getModule'); + +module.exports = function(createdModules) { + var unusedModules = []; + for(var module in createdModules) { + if(!getModule(module)) { + var cModule = createdModules[module]; + var message = 'Module "'+cModule.name+'" was created but never loaded.'; + unusedModules.push({module:cModule, message:message}); + } + } + return unusedModules; +}; + +},{"./getModule":64}],69:[function(require,module,exports){ +var normalizeAttribute = require('./normalizeAttribute'); + +module.exports = function(attrs) { + 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":73}],70:[function(require,module,exports){ +module.exports=require(56) +},{}],71:[function(require,module,exports){ +module.exports = { + createdModules: {}, + createdMulti: {}, + loadedModules: {} + }; + +},{}],72:[function(require,module,exports){ +var modData = require('./moduleData'); +var getModule = require('./getModule'); + +module.exports = function() { + if(modData.ngViewExists && !getModule('ngRoute')) { + return {message: 'Directive "ngView" was used in the application however "ngRoute" was not loaded into any module.'}; + } +}; + +},{"./getModule":64,"./moduleData":71}],73:[function(require,module,exports){ +module.exports = function(attribute) { + return attribute.replace(/^(?:data|x)[-_:]/,'').replace(/[:_]/g,'-'); +}; + +},{}],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); + 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); + if(noNgRoute) { + toSend = toSend.concat(noNgRoute); + } + display(toSend); + } +}; + +},{"./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) { + var name = module.name || module; + if(!isNgAppMod){ + module.requires.forEach(function(dependency){ + modData.loadedModules[dependency] = dependency; + }); + } + else { + modData.loadedModules[name] = name; + modData.ngAppMod = name; + } +}; + +},{"./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, + ngViewFound, + elem, + isElemName, + isInAttrsOrClasses, + ngAppMod; + + for(var i = 0; i < doms.length; i++) { + elem = doms[i]; + isElemName = elem.nodeName.toLowerCase() === 'ng-view'; + isInAttrsOrClasses = inAttrsOrClasses(elem.attributes); + + ngViewFound = isElemName || isInAttrsOrClasses; + ngAppMod = getNgAppMod(elem.attributes); + + if(ngAppMod) { + storeDependencies(ngAppMod, true); + } + modData.ngViewExists = ngViewFound ? true : modData.ngViewExists; + + if(bothFound) { + break; + } + } +}; + +},{"./getNgAppMod":65,"./inAttrsOrClasses":69,"./moduleData":71,"./storeDependencies":75}],77:[function(require,module,exports){ +var storeDependencies = require('./storeDependencies'); + +var storeUsedModules = module.exports = function(module, modules){ + if(module) { + storeDependencies(module); + module.requires.forEach(function(modName) { + var mod = modules[modName]; + storeUsedModules(mod, modules); + }); + } +} +},{"./storeDependencies":75}]},{},[1]); diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..71d4d59 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,16 @@ +var gulp = require('gulp'); +var source = require('vinyl-source-stream'); +var browserify = require('browserify'); + +var main = require('./package.json').main; + +gulp.task('watch', function(){ + gulp.watch(['hint.js', '!./dist/*.js'], ['browserify']); +}); + +gulp.task('browserify', function() { + var bundleStream = browserify('./' + main).bundle().pipe(source(main)); + return bundleStream.pipe(gulp.dest('./dist')); +}); + +gulp.task('default', ['browserify']); \ No newline at end of file diff --git a/hint.bundle.js b/hint.bundle.js index 463200f..68931a7 100644 --- a/hint.bundle.js +++ b/hint.bundle.js @@ -1,75 +1,902 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o} */ + var modules = {}; + + /** + * @ngdoc function + * @name angular.module + * @module ng + * @description + * + * The `angular.module` is a global place for creating, registering and retrieving Angular + * modules. + * All modules (angular core or 3rd party) that should be available to an application must be + * registered using this mechanism. + * + * When passed two or more arguments, a new module is created. If passed only one argument, an + * existing module (the name passed as the first argument to `module`) is retrieved. + * + * + * # Module + * + * A module is a collection of services, directives, controllers, filters, and configuration information. + * `angular.module` is used to configure the {@link auto.$injector $injector}. + * + * ```js + * // Create a new module + * var myModule = angular.module('myModule', []); + * + * // register a new service + * myModule.value('appName', 'MyCoolApp'); + * + * // configure existing services inside initialization blocks. + * myModule.config(['$locationProvider', function($locationProvider) { + * // Configure existing providers + * $locationProvider.hashPrefix('!'); + * }]); + * ``` + * + * Then you can create an injector and load your modules like this: + * + * ```js + * var injector = angular.injector(['ng', 'myModule']) + * ``` + * + * However it's more likely that you'll just use + * {@link ng.directive:ngApp ngApp} or + * {@link angular.bootstrap} to simplify this process for you. + * + * @param {!string} name The name of the module to create or retrieve. + * @param {!Array.=} requires If specified then new module is being created. If + * unspecified then the module is being retrieved for further configuration. + * @param {Function=} configFn Optional configuration function for the module. Same as + * {@link angular.Module#config Module#config()}. + * @returns {module} new module with the {@link angular.Module} api. + */ + return function module(name, requires, configFn) { + var assertNotHasOwnProperty = function(name, context) { + if (name === 'hasOwnProperty') { + throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); + } + }; - var excludeModules = function(modulesToExclude) { - var selected = allModules.filter(function(name) { - var notFound = true; - modulesToExclude.forEach(function(element) { - if(('ngHint' + element[0].toUpperCase() + element.substring(1)) == name) { - notFound = false; + assertNotHasOwnProperty(name, 'module'); + if (requires && modules.hasOwnProperty(name)) { + modules[name] = null; + } + return ensure(modules, name, function() { + if (!requires) { + throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + + "the module name or forgot to load it. If registering a module ensure that you " + + "specify the dependencies as the second argument.", name); + } + + /** @type {!Array.>} */ + var invokeQueue = []; + + /** @type {!Array.} */ + var runBlocks = []; + + var config = invokeLater('$injector', 'invoke'); + + /** @type {angular.Module} */ + var moduleInstance = { + // Private state + _invokeQueue: invokeQueue, + _runBlocks: runBlocks, + + /** + * @ngdoc property + * @name angular.Module#requires + * @module ng + * @returns {Array.} List of module names which must be loaded before this module. + * @description + * Holds the list of modules which the injector will load before the current module is + * loaded. + */ + requires: requires, + + /** + * @ngdoc property + * @name angular.Module#name + * @module ng + * @returns {string} Name of the module. + * @description + */ + name: name, + + + /** + * @ngdoc method + * @name angular.Module#provider + * @module ng + * @param {string} name service name + * @param {Function} providerType Construction function for creating new instance of the + * service. + * @description + * See {@link auto.$provide#provider $provide.provider()}. + */ + provider: invokeLater('$provide', 'provider'), + + /** + * @ngdoc method + * @name angular.Module#factory + * @module ng + * @param {string} name service name + * @param {Function} providerFunction Function for creating new instance of the service. + * @description + * See {@link auto.$provide#factory $provide.factory()}. + */ + factory: invokeLater('$provide', 'factory'), + + /** + * @ngdoc method + * @name angular.Module#service + * @module ng + * @param {string} name service name + * @param {Function} constructor A constructor function that will be instantiated. + * @description + * See {@link auto.$provide#service $provide.service()}. + */ + service: invokeLater('$provide', 'service'), + + /** + * @ngdoc method + * @name angular.Module#value + * @module ng + * @param {string} name service name + * @param {*} object Service instance object. + * @description + * See {@link auto.$provide#value $provide.value()}. + */ + value: invokeLater('$provide', 'value'), + + /** + * @ngdoc method + * @name angular.Module#constant + * @module ng + * @param {string} name constant name + * @param {*} object Constant value. + * @description + * Because the constant are fixed, they get applied before other provide methods. + * See {@link auto.$provide#constant $provide.constant()}. + */ + constant: invokeLater('$provide', 'constant', 'unshift'), + + /** + * @ngdoc method + * @name angular.Module#animation + * @module ng + * @param {string} name animation name + * @param {Function} animationFactory Factory function for creating new instance of an + * animation. + * @description + * + * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. + * + * + * Defines an animation hook that can be later used with + * {@link ngAnimate.$animate $animate} service and directives that use this service. + * + * ```js + * module.animation('.animation-name', function($inject1, $inject2) { + * return { + * eventName : function(element, done) { + * //code to run the animation + * //once complete, then run done() + * return function cancellationFunction(element) { + * //code to cancel the animation + * } + * } + * } + * }) + * ``` + * + * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and + * {@link ngAnimate ngAnimate module} for more information. + */ + animation: invokeLater('$animateProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#filter + * @module ng + * @param {string} name Filter name. + * @param {Function} filterFactory Factory function for creating new instance of filter. + * @description + * See {@link ng.$filterProvider#register $filterProvider.register()}. + */ + filter: invokeLater('$filterProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#controller + * @module ng + * @param {string|Object} name Controller name, or an object map of controllers where the + * keys are the names and the values are the constructors. + * @param {Function} constructor Controller constructor function. + * @description + * See {@link ng.$controllerProvider#register $controllerProvider.register()}. + */ + controller: invokeLater('$controllerProvider', 'register'), + + /** + * @ngdoc method + * @name angular.Module#directive + * @module ng + * @param {string|Object} name Directive name, or an object map of directives where the + * keys are the names and the values are the factories. + * @param {Function} directiveFactory Factory function for creating new instance of + * directives. + * @description + * See {@link ng.$compileProvider#directive $compileProvider.directive()}. + */ + directive: invokeLater('$compileProvider', 'directive'), + + /** + * @ngdoc method + * @name angular.Module#config + * @module ng + * @param {Function} configFn Execute this function on module load. Useful for service + * configuration. + * @description + * Use this method to register work which needs to be performed on module loading. + * For more about how to configure services, see + * {@link providers#providers_provider-recipe Provider Recipe}. + */ + config: config, + + /** + * @ngdoc method + * @name angular.Module#run + * @module ng + * @param {Function} initializationFn Execute this function after injector creation. + * Useful for application initialization. + * @description + * Use this method to register work which should be performed when the injector is done + * loading all modules. + */ + run: function(block) { + runBlocks.push(block); + return this; + } + }; + + if (configFn) { + config(configFn); + } + + return moduleInstance; + + /** + * @param {string} provider + * @param {string} method + * @param {String=} insertMethod + * @returns {angular.Module} + */ + function invokeLater(provider, method, insertMethod) { + return function() { + invokeQueue[insertMethod || 'push']([provider, method, arguments]); + return moduleInstance; + }; } }); - if(notFound) { - return name; + }; + }); + +} + +setupModuleLoader(window); +})(window); + +/** + * Closure compiler type information + * + * @typedef { { + * requires: !Array., + * invokeQueue: !Array.>, + * + * service: function(string, Function):angular.Module, + * factory: function(string, Function):angular.Module, + * value: function(string, *):angular.Module, + * + * filter: function(string, Function):angular.Module, + * + * init: function(Function):angular.Module + * } } + */ +angular.Module; + + +},{}],2:[function(require,module,exports){ + +require('./bower_components/angular-loader/angular-loader.js'); +require('angular-hint'); +var eventProxyElement = document.getElementById('__ngDebugElement'); + +var customEvent = document.createEvent('Event'); +customEvent.initEvent('myCustomEvent', true, true); + +angular.hint.onMessage = function (data) { + eventProxyElement.innerText = data; + eventProxyElement.dispatchEvent(customEvent); +}; + +},{"./bower_components/angular-loader/angular-loader.js":1,"angular-hint":3}],3:[function(require,module,exports){ +//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-events'); +require('angular-hint-interpolation'); +require('angular-hint-modules'); + +// List of all possible modules +// The default ng-hint behavior loads all modules +var allModules = ['ngHintControllers', 'ngHintDirectives', 'ngHintDom', 'ngHintEvents', + 'ngHintInterpolation', 'ngHintModules']; + +var SEVERITY_WARNING = 2; + +// Determine whether this run is by protractor. +// If protractor is running, the bootstrap will already be deferred. +// In this case `resumeBootstrap` should be patched to load the hint modules. +if (window.name === 'NG_DEFER_BOOTSTRAP!') { + var originalResumeBootstrap; + Object.defineProperty(angular, 'resumeBootstrap', { + get: function() { + return function(modules) { + return originalResumeBootstrap.call(angular, modules.concat(loadModules())); + }; + }, + set: function(resumeBootstrap) { + originalResumeBootstrap = resumeBootstrap; + } + }); +} +//If this is not a test, defer bootstrapping +else { + window.name = 'NG_DEFER_BOOTSTRAP!'; + + // determine which modules to load and resume bootstrap + document.addEventListener('DOMContentLoaded', maybeBootstrap); +} + +function maybeBootstrap() { + // we don't know if angular is loaded + if (!angular.resumeBootstrap) { + return setTimeout(maybeBootstrap, 1); + } + + var modules = loadModules(); + angular.resumeBootstrap(modules); +} + +function loadModules() { + var modules = [], elt; + + if ((elt = document.querySelector('[ng-hint-include]'))) { + modules = hintModulesFromElement(elt); + } else if (elt = document.querySelector('[ng-hint-exclude]')) { + modules = excludeModules(hintModulesFromElement(elt)); + } 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); + } + return modules; +} + +function excludeModules(modulesToExclude) { + return allModules.filter(function(module) { + return modulesToExclude.indexOf(module) === -1; + }); +} + +function hintModulesFromElement (elt) { + var selectedModules = (elt.attributes['ng-hint-include'] || + elt.attributes['ng-hint-exclude']).value.split(' '); + + return selectedModules.map(hintModuleName).filter(function (name) { + return (allModules.indexOf(name) > -1) || + angular.hint.logMessage('General', 'Module ' + name + ' could not be found', SEVERITY_WARNING); + }); +} + +function hintModuleName(name) { + return 'ngHint' + title(name); +} + +function title(str) { + return str[0].toUpperCase() + str.substr(1); +} + +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]); + if(log[groups[i]]['Error Messages']) { + logGroup(log[groups[i]]['Error Messages'], 'Error Messages'); + } + if(log[groups[i]]['Warning Messages']) { + logGroup(log[groups[i]]['Warning Messages'], 'Warning Messages'); } + if(log[groups[i]]['Suggestion Messages']) { + logGroup(log[groups[i]]['Suggestion Messages'], 'Suggestion Messages'); + } + console.groupEnd && console.groupEnd(); + } +} +setInterval(flush, 2); + +function logGroup(group, type) { + console.group? console.group(type + ':') : console.log(type + ':'); + for(var i = 0, ii = group.length; i < ii; i++) { + console.log(group[i]); + } + console.group && console.groupEnd(); +} + +},{"angular-hint-controllers":4,"angular-hint-directives":5,"angular-hint-dom":40,"angular-hint-events":42,"angular-hint-interpolation":51,"angular-hint-log":60,"angular-hint-modules":61}],4:[function(require,module,exports){ +'use strict'; + +var nameToControllerMatch = {}, + controllers = {}, + hintLog = angular.hint = require('angular-hint-log'), + MODULE_NAME = 'Controllers', + SEVERITY_ERROR = 1, + SEVERITY_WARNING = 2; + +/** +* Decorates $controller with a patching function to +* log a message if the controller is instantiated on the window +*/ +angular.module('ngHintControllers', []). + 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') { + if(nameToControllerMatch[ctrl]) { + ctrl = nameToControllerMatch[ctrl]; + } else { + //If the controller function cannot be found, check for it on the window + checkUppercaseName(ctrl); + checkControllerInName(ctrl); + ctrl = window[ctrl] || ctrl; + if(typeof ctrl === 'string') { + throw new Error('The controller function for ' + ctrl + ' could not be found.' + + ' Is the function registered under that name?'); + } + } + } + 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]) && + ctrl.toString().indexOf('@name ngModel.NgModelController#$render') === -1 && + ctrl.toString().indexOf('@name form.FormController') === -1) { + if(angular.version.minor <= 2) { + hintLog.logMessage(MODULE_NAME, 'It is against Angular best practices to ' + + 'instantiate a controller on the window. This behavior is deprecated in Angular' + + ' 1.3.0', SEVERITY_WARNING); + } else { + hintLog.logMessage(MODULE_NAME, 'Global instantiation of controllers was deprecated' + + ' in Angular 1.3.0. Define the controller on a module.', SEVERITY_ERROR); + } + } + var ctrlInstance = $delegate.apply(this, [ctrl, locals]); + return ctrlInstance; + }; }); - return selected; +}); + +/** +* 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; + +function checkUppercaseName(controllerName) { + var firstLetter = controllerName.charAt(0); + if(firstLetter !== firstLetter.toUpperCase() && firstLetter === firstLetter.toLowerCase()) { + hintLog.logMessage(MODULE_NAME, 'The best practice is to name controllers with an' + + ' uppercase first letter. Check the name of \'' + controllerName + '\'.', SEVERITY_WARNING); + } +} + +function checkControllerInName(controllerName) { + var splitName = controllerName.split('Controller'); + if(splitName.length === 1 || splitName[splitName.length - 1] !== '') { + hintLog.logMessage(MODULE_NAME, 'The best practice is to name controllers ending with ' + + '\'Controller\'. Check the name of \'' + controllerName + '\'.', SEVERITY_WARNING); + } +} + +angular.module = function() { + var module = originalModule.apply(this, arguments), + originalController = module.controller; + module.controller = function(controllerName, controllerConstructor) { + nameToControllerMatch[controllerName] = controllerConstructor; + controllers[controllerConstructor] = controllerConstructor; + checkUppercaseName(controllerName); + checkControllerInName(controllerName); + return originalController.apply(this, arguments); + }; + return module; +}; + +},{"angular-hint-log":60}],5:[function(require,module,exports){ +'use strict'; + +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); + }; + }]); + }]); + +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; +}; - elts = document.querySelectorAll('[ng-hint-include]'); - if(elts.length > 0) { - selectedModules = includeModules(elts[0].attributes['ng-hint-include'].value.split(' ')); +},{"./lib/checkPrelimErrors":18,"./lib/ddLib-data":19,"./lib/getKeysAndValues":26,"./lib/search":34,"dasherize":36}],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; } - else { - elts = document.querySelectorAll('[ng-hint-exclude]'); - if(elts.length > 0) { - selectedModules = excludeModules(elts[0].attributes['ng-hint-exclude'].value.split(' ')); + 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 { - elts = document.querySelectorAll('[ng-hint]'); - if(elts.length > 0) { - selectedModules = allModules; + 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'; } } - } - if(selectedModules != undefined) { - angular.resumeBootstrap(selectedModules); - } - else { - angular.resumeBootstrap(); - } -}); + }); + var typeError = wrongUse? 'wronguse':'' || !anyTrue ? 'nonexsisting' : '' || ''; + return {exsists: anyTrue, wrongUse: wrongUse, typeError: typeError}; +}; -},{"angular-hint-directives":4,"angular-hint-dom":5}],3:[function(require,module,exports){ -(function (ddLib) { +},{"./ddLib-data":19}],8:[function(require,module,exports){ +var ddLibData = require('./ddLib-data'), + SEVERITY_ERROR = 1; -'use strict'; +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 deprecated directive "' + error + '". Use an alternative solution.'; + return [message, SEVERITY_ERROR]; +}; + +},{"./ddLib-data":19}],9:[function(require,module,exports){ +var SEVERITY_ERROR = 1; + +module.exports = function(info, id, type) { + var s = info.missing.length === 1 ? ' ' : 's '; + var waswere = info.missing.length === 1 ? 'is ' : 'are '; + var missing = ''; + info.missing.forEach(function(str){ + missing += '"' + str + '",'; + }); + missing = '[' + missing.substring(0,missing.length-1) + '] '; + var message = 'Attribute' + s + missing + waswere + 'missing in ' + type + ' element' + id + '.'; + return [message, SEVERITY_ERROR]; +}; + +},{}],10:[function(require,module,exports){ +var isMutExclusiveDir = require('./isMutExclusiveDir'), + SEVERITY_ERROR = 1; + +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, SEVERITY_ERROR]; +}; + +},{"./isMutExclusiveDir":31}],11:[function(require,module,exports){ +var hintLog = require('angular-hint-log'), + MODULE_NAME = 'Directives', + SEVERITY_SUGGESTION = 3; + +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(MODULE_NAME, message, SEVERITY_SUGGESTION); +}; + +},{"angular-hint-log":60}],12:[function(require,module,exports){ +var SEVERITY_SUGGESTION = 3; + +module.exports = function(info, id, type) { + var ngDir = 'ng-' + info.error.substring(2), + message = 'Use Angular version of "' + info.error + '" in ' + type + ' element' + id + + '. Try: "' + ngDir + '"'; + return [message, SEVERITY_SUGGESTION]; +}; + +},{}],13:[function(require,module,exports){ +var SEVERITY_ERROR = 1; +module.exports = function(info, id, type) { + var message = 'ngRepeat in '+type+' element'+id+' was used incorrectly. '+info.suggestion; + return [message, SEVERITY_ERROR]; +}; + +},{}],14:[function(require,module,exports){ +var ddLibData = require('./ddLib-data'), + SEVERITY_ERROR = 1; + +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, SEVERITY_ERROR]; +}; + +},{"./ddLib-data":19}],15:[function(require,module,exports){ +var hintLog = angular.hint = require('angular-hint-log'), + MODULE_NAME = 'Directives', + SEVERITY_ERROR = 1; + +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(MODULE_NAME, message, SEVERITY_ERROR); +}; + +},{"angular-hint-log":60}],16:[function(require,module,exports){ +var ddLibData = require('./ddLib-data'), + SEVERITY_ERROR = 1; + +module.exports = function(info, id, type) { + var message = ddLibData.directiveTypes[info.directiveType].message + type + ' element' + + id + '. ', + error = (info.error.charAt(0) === '*') ? info.error.substring(1): info.error, + aecmType = (info.wrongUse.indexOf('attribute') > -1)? 'Element' : 'Attribute'; + message += aecmType + ' name "' + error + '" is reserved for ' + info.wrongUse + ' names only.'; + return [message, SEVERITY_ERROR]; +}; + +},{"./ddLib-data":19}],17:[function(require,module,exports){ + +module.exports = function(attrVal){ + var suggestion, + error = false;; + var trackMatch = attrVal.match(/track\s+by\s+\S*/); + var filterMatch = attrVal.match(/filter\s*:\s*\w+(?:\.\w+)*/); + var breakIndex = attrVal.indexOf('|') > -1 ? attrVal.indexOf('|') : Infinity; + if(!trackMatch && filterMatch && breakIndex === Infinity) { + return 'Try: " | '+filterMatch[0]+'"'; + } + if(trackMatch && filterMatch) { + var trackInd = attrVal.indexOf(trackMatch[0]); + var filterInd = attrVal.indexOf(filterMatch[0]); + if(!(breakIndex < filterInd && filterInd < trackInd)) { + return 'Try: " | '+filterMatch[0]+' '+trackMatch[0]+'"'; + } + } +} +},{}],18:[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); + } +}; -ddLib.directiveDetails = { +},{"./buildNameSpace":11,"./buildReplaceOption":15,"./hasNameSpace":29,"./hasReplaceOption":30}],19:[function(require,module,exports){ +module.exports = { directiveTypes : { 'html-directives': { message: 'There was an HTML error in ', @@ -119,23 +946,23 @@ ddLib.directiveDetails = { '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', + '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', @@ -178,6 +1005,7 @@ ddLib.directiveDetails = { 'ng-cut': 'A', 'ng-dblclick': 'A', 'ng-disabled': 'A', + 'ng-dirty': 'A', 'ng-focus': 'A', 'ng-form': 'A', 'ng-hide': 'A', @@ -188,6 +1016,7 @@ ddLib.directiveDetails = { 'ng-if': 'A', 'ng-include': 'A', 'ng-init': 'A', + 'ng-invalid': 'A', 'ng-keydown': 'A', 'ng-keypress': 'A', 'ng-keyup': 'A', @@ -195,7 +1024,7 @@ ddLib.directiveDetails = { 'ng-maxlength': 'A', 'ng-minlength': 'A', 'ng-model': 'A', - 'ng-modeloptions': 'A', + 'ng-model-options': 'A', 'ng-mousedown': 'A', 'ng-mouseenter': 'A', 'ng-mouseleave': 'A', @@ -208,8 +1037,11 @@ ddLib.directiveDetails = { 'ng-paste': 'A', 'ng-pattern': 'A', 'ng-pluralize': 'A', + 'ng-pristine': 'A', 'ng-readonly': 'A', 'ng-repeat': 'A', + 'ng-repeat-start': 'A', + 'ng-repeat-end': 'A', 'ng-required': 'A', 'ng-selected': 'A', 'ng-show': 'A', @@ -223,73 +1055,124 @@ ddLib.directiveDetails = { '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: { } + }, + 'angular-deprecated-directives': { + message: 'There was an AngularJS error in ', + directives: { + 'ng-bind-html-unsafe': 'deprecated' + } } } -} +}; + +},{}],20:[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. * - *@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 + *@return {} with Levenshtein Distance and name of the closest match to given attribute. **/ -ddLib.beginSearch = function(scopeElements, customDirectives, options) { - if(!Array.isArray(scopeElements)) { - throw new Error("Function beginSearch must be passed an array."); +module.exports = function(directiveTypeData, attribute) { + if(typeof attribute !== 'string') { + throw new Error('Function must be passed a string as second parameter.'); } - options = options || {}; - options.directiveTypes = options.directiveTypes || - ['html-directives','angular-default-directives','angular-custom-directives'];; - options.tolerance = options.tolerance || 4; - if(customDirectives) { - ddLib.setCustomDirectives(customDirectives); + if((directiveTypeData === null || directiveTypeData === undefined) || + typeof directiveTypeData !== 'object') { + throw new Error('Function must be passed a defined object as first parameter.'); } - var failedElements = ddLib.findFailedElements(scopeElements, options); - var messages = ddLib.formatResults(failedElements); - return messages; + 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}; }; -ddLib.findFailedElements = function(scopeElements, options) { - return scopeElements.map(ddLib.getFailedAttributesOfElement.bind(null,options)) - .filter(function(x) {return x;}); -} +},{"./areSimilarEnough":6,"./levenshtein":32}],21:[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":25}],22:[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":19}],23:[function(require,module,exports){ +var hintLog = angular.hint = require('angular-hint-log'), + MODULE_NAME = 'Directives'; + +var build = { + deprecated: require('./buildDeprecated'), + missingrequired: require('./buildMissingRequired'), + mutuallyexclusive: require('./buildMutuallyExclusive'), + ngevent: require('./buildNgEvent'), + ngrepeatformat: require('./buildNgRepeatFormat'), + nonexsisting: require('./buildNonExsisting'), + wronguse: require('./buildWrongUse') +}; /** - *@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 + *@param failedElements: [] of {}s of all failed elements with their failed attributes and closest + *matches or restrict properties * - *@return {} of html element and [] of failed attributes + *@return [] of failed messages. **/ -ddLib.getFailedAttributesOfElement = function(options, element) { - if(element.attributes.length) { - var elementAttributes = Array.prototype.slice.call(element.attributes); - elementAttributes.push({nodeName: "*"+element.nodeName.toLowerCase()}); - var failedAttributes = ddLib.getFailedAttributes(elementAttributes, options); - if(failedAttributes.length) { - return { - domElement: element, - data: failedAttributes - }; - } - } +module.exports = function(failedElements) { + failedElements.forEach(function(obj) { + obj.data.forEach(function(info) { + var id = (obj.domElement.id) ? ' with id: #' + obj.domElement.id : '', + type = obj.domElement.nodeName, + messageAndSeverity = build[info.typeError](info, id, type); + hintLog.logMessage(MODULE_NAME, messageAndSeverity[0], messageAndSeverity[1]); + }); + }); }; +},{"./buildDeprecated":8,"./buildMissingRequired":9,"./buildMutuallyExclusive":10,"./buildNgEvent":12,"./buildNgRepeatFormat":13,"./buildNonExsisting":14,"./buildWrongUse":16,"angular-hint-log":60}],24:[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'); +var checkNgRepeatFormat = require('./checkNgRepeatFormat'); /** *@param attributes: [] of attributes from element (includes tag name of element, e.g. DIV, P, etc.) @@ -297,327 +1180,513 @@ ddLib.getFailedAttributesOfElement = function(options, element) { * *@return [] of failedAttributes with their respective suggestions and directiveTypes **/ -ddLib.getFailedAttributes = function(attributes, options) { - var failedAttributes = []; - for(var i = 0; i < attributes.length; i++) { - var attr = ddLib.normalizeAttribute(attributes[i].nodeName); - var result = ddLib.attributeExsistsInTypes(attr,options); - if(!result.exsists) { - var suggestion = ddLib.getSuggestions(attr,options); - if(suggestion){ - failedAttributes. - push({match: suggestion.match, error: attr, directiveType:suggestion.directiveType}); +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] || + ddLibData.directiveTypes['angular-deprecated-directives'].directives[attr] || ''; + + if(dirVal === 'deprecated') { + failedAttrs.push({ + error: attr, + directiveType: 'angular-deprecated-directives', + typeError: 'deprecated' + }); + } + + //if attr is a event attr. Html event directives are prefixed with ! in ddLibData + 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 attrVal = attributes[i].value || ''; + if(attr === 'ng-repeat') { + var result = checkNgRepeatFormat(attrVal); + if(result) { + failedAttrs.push({ + error: attr, + suggestion: result, + directiveType: 'angular-default-directives', + typeError: 'ngrepeatformat' + }); } } - else if(result.wrongUse) { - failedAttributes. - push({wrongUse:result.wrongUse, error: attr, directiveType: 'angular-custom-directives'}); + + 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 failedAttributes; + return failedAttrs; }; +},{"./attributeExsistsInTypes":7,"./checkNgRepeatFormat":17,"./ddLib-data":19,"./getSuggestions":27,"./hasMutExclusivePair":28,"./isMutExclusiveDir":31,"./normalizeAttribute":33}],25:[function(require,module,exports){ +var getFailedAttributes = require('./getFailedAttributes'); +var findMissingAttrs = require('./findMissingAttrs'); + /** - *@param attribute: attribute name as string e.g. 'ng-click', 'width', 'src', etc. - *@param options: {} options object from beginSearch. + *@description + *Adds element tag name (DIV, P, SPAN) to list of attributes with '*' prepended + *for identification later. * - *@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. + *@param options: {} options object from beginSearch + *@param element: HTML element to check attributes of * - *@return {} with attribute exsistance and wrong use e.g. restrict property set to elements only. + *@return {} of html element and [] of failed attributes **/ -ddLib.attributeExsistsInTypes = function(attribute, options) { - var allTrue = false, wrongUse = ''; - options.directiveTypes.forEach(function(directiveType) { - var isTag = attribute.charAt(0) == '*'; - var isCustomDir = directiveType == 'angular-custom-directives'; - if(!isTag) { - var directive = ddLib.directiveDetails.directiveTypes[directiveType].directives[attribute]; - if(directive) { - if(directive.indexOf('E') > -1 && directive.indexOf('A') < 0) { - wrongUse = 'element'; - } - if(directive.indexOf('C') > -1 && directive.indexOf('A') < 0) { - wrongUse = (wrongUse) ? 'element and class' : 'class'; - } - allTrue = allTrue || true; - } - } - else if(isTag && isCustomDir){ - var directive = ddLib.directiveDetails.directiveTypes[directiveType].directives[attribute.substring(1)]; - if(directive){ - allTrue = allTrue || true; - if(directive && directive.indexOf('A') > -1 && directive.indexOf('E') < 0) { - wrongUse = 'attribute'; - } +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 + }; } - }); - return {exsists: allTrue, wrongUse: wrongUse}; + } }; +},{"./findMissingAttrs":22,"./getFailedAttributes":24}],26:[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; +}; + +},{}],27:[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. **/ -ddLib.getSuggestions = function(attribute, options) { - var min_levDist = Infinity, match = '', dirType = ''; +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 = ddLib.directiveDetails.directiveTypes[directiveType].directives - var tempMatch = ddLib.findClosestMatchIn(directiveTypeData, attribute); - if(tempMatch.min_levDist < options.tolerance && tempMatch.min_levDist < min_levDist) { + 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 (match)? {match:match, directiveType:dirType}: null; + return { + match: match, + directiveType: dirType + }; +}; + +},{"./ddLib-data":19,"./findClosestMatchIn":20}],28:[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":31}],29:[function(require,module,exports){ +var dasherize = require('dasherize'); +var validate = require('validate-element-name'); + +module.exports = function(str) { + var dashStr = dasherize(str); + var validated = !validate(dashStr).message ? true : false; + //Check for message definition because validate-element-name returns true for things starting + //with ng-, polymer-, and data- but message is defined for those and errors. + return validated && str.toLowerCase() !== str; }; +},{"dasherize":36,"validate-element-name":37}],30:[function(require,module,exports){ +module.exports = function(facStr) { + return facStr.match(/replace\s*:/); +}; + +},{}],31:[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]; +}; + +},{}],32:[function(require,module,exports){ /** - *@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. + *@param s: first string to compare for Levenshtein Distance. + *@param t: second string to compare for Levenshtein Distance. * - *@return {} with Levenshtein Distance and name of the closest match to given attribute. + *@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) **/ -ddLib.findClosestMatchIn = 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.'); +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 min_levDist = Infinity, closestMatch = ''; - for(var directive in directiveTypeData){ - if(ddLib.areSimilarEnough(attribute,directive)) { - var currentlevDist = ddLib.levenshteinDistance(attribute, directive); - var closestMatch = (currentlevDist < min_levDist)? directive : closestMatch; - var min_levDist = (currentlevDist < min_levDist)? currentlevDist : min_levDist; + 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 {min_levDist: min_levDist, match: closestMatch}; + return d[n][m]; }; +},{}],33:[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 **/ -ddLib.normalizeAttribute = function(attribute) { - return attribute.replace(/^(?:data|x)[-_:]/,"").replace(/[:_]/g,'-'); +module.exports = function(attribute) { + return attribute.replace(/^(?:data|x)[-_:]/,'').replace(/[:_]/g,'-'); }; +},{}],34:[function(require,module,exports){ + +var formatResults = require('./formatResults'); +var findFailedElements = require('./findFailedElements'); +var setCustomDirectives = require('./setCustomDirectives'); +var defaultTypes = [ + 'html-directives', + 'angular-default-directives', + 'angular-custom-directives', + 'angular-deprecated-directives' +]; + + /** - *@param failedElements: [] of {}s of all failed elements with their failed attributes and closest - *matches or restrict properties * - *@return [] of failed messages. + *@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 **/ -ddLib.formatResults = function(failedElements) { - var messages = []; - failedElements.forEach(function(obj) { - obj.data.forEach(function(attr) { - var id = (obj.domElement.id) ? ' with id: #'+obj.domElement.id : ''; - var type = obj.domElement.nodeName; - var message = ddLib.directiveDetails.directiveTypes[attr.directiveType].message+type+' element'+id+'. '; - var error = (attr.error.charAt(0) == '*') ? attr.error.substring(1): attr.error; - if(!attr.wrongUse) { - message +='Found incorrect attribute "'+error+'" try "'+attr.match+'".'; - } - else { - var aecmType = (attr.wrongUse.indexOf('attribute') > -1)? 'Element' : 'Attribute'; - message += aecmType+' name "'+error+'" is reserved for '+attr.wrongUse+' names only.'; - } - messages.push({message:message, domElement: obj.domElement}) - }) - }) - return messages; +module.exports = function(scopeElements, customDirectives, options) { + if(!Array.isArray(scopeElements)) { + throw new Error('Function search must be passed an array.'); + } + options = options || {}; + options.directiveTypes = options.directiveTypes || defaultTypes; + options.tolerance = options.tolerance || 4; + if(customDirectives && customDirectives.length){ + setCustomDirectives(customDirectives); + } + var failedElements = findFailedElements(scopeElements, options); + formatResults(failedElements); }; -/** - *@param customDirectives: [] of custom directive objects from $compile decorator - **/ -ddLib.setCustomDirectives = function(customDirectives) { +},{"./findFailedElements":21,"./formatResults":23,"./setCustomDirectives":35}],35:[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(); - ddLib.directiveDetails.directiveTypes['angular-custom-directives'] - .directives[directiveName] = directive.restrict; - }) -} + ddLibData.directiveTypes['angular-custom-directives'] + .directives[directiveName] = directive; + }); +}; -/** - *@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. - */ -ddLib.areSimilarEnough = function(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; -} +},{"../lib/ddLib-data":19}],36:[function(require,module,exports){ +'use strict'; -/** - *@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. - **/ -ddLib.levenshteinDistance = 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 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); - } - } +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 d[n][m]; + } + return keys; }; -/** - * @param str: string to convert formatting from camelCase to lowercase with dash after ng. - **/ -ddLib.camelToDashes = function(str) { - return str.replace(/([A-Z])/g, function($1){return "-"+$1.toLowerCase();}); +function dashCase(str) { + return str.replace(/([A-Z])/g, function ($1) { + return '-' + $1.toLowerCase(); + }); } -}((typeof module !== 'undefined' && module && module.exports) ? - (module.exports = window.ddLib = {}) : (window.ddLib = {}) )); +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; +} +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; +} +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; + }, {}); +} +module.exports = function (obj) { + if (typeof obj === 'string') { + return dashCase(obj); + } + return walk(obj); +}; -},{}],4:[function(require,module,exports){ +},{}],37:[function(require,module,exports){ 'use strict'; +var ncname = require('ncname'); + +var reservedNames = [ + 'annotation-xml', + 'color-profile', + 'font-face', + 'font-face-src', + 'font-face-uri', + 'font-face-format', + 'font-face-name', + 'missing-glyph' +]; + +function hasError(name) { + if (!name) { + return 'Missing element name.'; + } + + if (/[A-Z]/.test(name)) { + return 'Custom element names must not contain uppercase ASCII characters.'; + } + + if (name.indexOf('-') === -1) { + return 'Custom element names must contain a hyphen. Example: unicorn-cake'; + } + + if (/^\d/i.test(name)) { + return 'Custom element names must not start with a digit.'; + } + + if (/^-/i.test(name)) { + return 'Custom element names must not start with a hyphen.'; + } + + // http://www.w3.org/TR/custom-elements/#concepts + if (!ncname.test(name)) { + return 'Invalid element name.'; + } + + if (reservedNames.indexOf(name) !== -1) { + return 'The supplied element name is reserved and can\'t be used.\nSee: http://www.w3.org/TR/custom-elements/#concepts'; + } +}; -var ddLib = require('./dd-lib/dd-lib'); -var customDirectives = []; +function hasWarning(name) { + if (/^polymer-/i.test(name)) { + return 'Custom element names should not start with `polymer-`.\nSee: http://webcomponents.github.io/articles/how-should-i-name-my-element'; + } + if (/^x-/i.test(name)) { + return 'Custom element names should not start with `x-`.\nSee: http://webcomponents.github.io/articles/how-should-i-name-my-element/'; + } -angular.module('ngHintDirectives', ['ngLocale']) - .config(['$provide', function($provide) { - $provide.decorator('$compile', ['$delegate','$timeout', function($delegate, $timeout) { - return function(elem) { - var messages=[]; - for(var i = 0; i < elem.length; i+=2){ - if(elem[i].getElementsByTagName){ - var toSend = Array.prototype.slice.call(elem[i].getElementsByTagName('*')); - var result = ddLib.beginSearch(toSend,customDirectives); - messages = messages.concat(result); - } - } - if(messages.length) { - console.groupCollapsed('Angular Hint: Directives'); - messages.forEach(function(error) { - console.warn(error.message); - console.log(error.domElement); - }) - console.groupEnd(); - } - return $delegate.apply(this,arguments); - }; - }]); - }]); -angular.module('ngLocale').config(function($provide) { - var originalProvider = $provide.provider; - $provide.provider = function(token, provider) { - var provider = originalProvider.apply($provide, arguments); - if (token === '$compile') { - var originalProviderDirective = provider.directive; - provider.directive = function(dirsObj) { - for(var prop in dirsObj){ - var propDashed = ddLib.camelToDashes(prop); - if(isNaN(+propDashed) && - !ddLib.directiveDetails.directiveTypes['angular-default-directives'].directives[propDashed] && - !ddLib.directiveDetails.directiveTypes['html-directives'].directives[propDashed]) { - var matchRestrict = dirsObj[prop].toString().match(/restrict:\s*'(.+?)'/) || 'ACME'; - ddLib.directiveDetails.directiveTypes['angular-default-directives'] - .directives[propDashed] = matchRestrict[1]; - } - }; - 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 directive = {directiveName: directiveName, restrict: 'AE'} - customDirectives.push(directive); - var matchRestrict = originalDirectiveFactory.toString().match(/restrict:\s*'(.+?)'/); - var matchScope = originalDirectiveFactory.toString().match(/scope:\s*?{\s*?(\w+):\s*?'(.+?)'/); - if(matchScope) { - var name = (matchScope[2]=='=')? matchScope[1] : matchScope[2].substring(1); - customDirectives.push({directiveName: name , restrict:'A'}) - } - if (matchRestrict) { - directive.restrict = matchRestrict[1]; - } - arguments[1][0] = function () { - var ddo = originalDirectiveFactory.apply(this, arguments); - directive.restrict = ddo.restrict || 'A'; - return ddo; - }; - return originalDirective.apply(this, arguments); - }; - return module; + if (/^ng-/i.test(name)) { + return 'Custom element names should not start with `ng-`.\nSee: http://docs.angularjs.org/guide/directive#creating-directives'; + } + + if (/^xml/i.test(name)) { + return 'Custom element names should not start with `xml`.'; + } + + if (/^[^a-z]/i.test(name)) { + return 'This element name is only valid in XHTML, not in HTML. First character should be in the range a-z.'; + } + + if (/[^a-z0-9]$/i.test(name)) { + return 'Custom element names should not end with a non-alpha character.'; + } + + if (/[\.]/.test(name)) { + return 'Custom element names should not contain a dot character as it would need to be escaped in a CSS selector.'; + } + + if (/[^\x20-\x7E]/.test(name)) { + return 'Custom element names should not contain non-ASCII characters.'; + } + + if (/--/.test(name)) { + return 'Custom element names should not contain consecutive hyphens.'; + } + + if (/[^a-z0-9]{2}/i.test(name)) { + return 'Custom element names should not contain consecutive non-alpha characters.'; + } } +module.exports = function (name) { + var errMsg = hasError(name); -},{"./dd-lib/dd-lib":3}],5:[function(require,module,exports){ + return { + isValid: !errMsg, + message: errMsg || hasWarning(name) + }; +}; +},{"ncname":38}],38:[function(require,module,exports){ 'use strict'; +var xmlChars = require('xml-char-classes'); -var domInterceptor = require('dom-interceptor'); +function getRange(re) { + return re.source.slice(1, -1); +} -var nameToConstructorMappings = {}; +// http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName +module.exports = new RegExp('^[' + getRange(xmlChars.letter) + '_][' + getRange(xmlChars.letter) + getRange(xmlChars.digit) + '\\.\\-_' + getRange(xmlChars.combiningChar) + getRange(xmlChars.extender) + ']*$'); + +},{"xml-char-classes":39}],39:[function(require,module,exports){ +exports.baseChar = /[A-Za-z\xC0-\xD6\xD8-\xF6\xF8-\u0131\u0134-\u013E\u0141-\u0148\u014A-\u017E\u0180-\u01C3\u01CD-\u01F0\u01F4\u01F5\u01FA-\u0217\u0250-\u02A8\u02BB-\u02C1\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03D6\u03DA\u03DC\u03DE\u03E0\u03E2-\u03F3\u0401-\u040C\u040E-\u044F\u0451-\u045C\u045E-\u0481\u0490-\u04C4\u04C7\u04C8\u04CB\u04CC\u04D0-\u04EB\u04EE-\u04F5\u04F8\u04F9\u0531-\u0556\u0559\u0561-\u0586\u05D0-\u05EA\u05F0-\u05F2\u0621-\u063A\u0641-\u064A\u0671-\u06B7\u06BA-\u06BE\u06C0-\u06CE\u06D0-\u06D3\u06D5\u06E5\u06E6\u0905-\u0939\u093D\u0958-\u0961\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8B\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AE0\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B36-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB5\u0BB7-\u0BB9\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60\u0D61\u0E01-\u0E2E\u0E30\u0E32\u0E33\u0E40-\u0E45\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD\u0EAE\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0F40-\u0F47\u0F49-\u0F69\u10A0-\u10C5\u10D0-\u10F6\u1100\u1102\u1103\u1105-\u1107\u1109\u110B\u110C\u110E-\u1112\u113C\u113E\u1140\u114C\u114E\u1150\u1154\u1155\u1159\u115F-\u1161\u1163\u1165\u1167\u1169\u116D\u116E\u1172\u1173\u1175\u119E\u11A8\u11AB\u11AE\u11AF\u11B7\u11B8\u11BA\u11BC-\u11C2\u11EB\u11F0\u11F9\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2126\u212A\u212B\u212E\u2180-\u2182\u3041-\u3094\u30A1-\u30FA\u3105-\u312C\uAC00-\uD7A3]/; + +exports.ideographic = /[\u3007\u3021-\u3029\u4E00-\u9FA5]/; + +exports.letter = /[A-Za-z\xC0-\xD6\xD8-\xF6\xF8-\u0131\u0134-\u013E\u0141-\u0148\u014A-\u017E\u0180-\u01C3\u01CD-\u01F0\u01F4\u01F5\u01FA-\u0217\u0250-\u02A8\u02BB-\u02C1\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03D6\u03DA\u03DC\u03DE\u03E0\u03E2-\u03F3\u0401-\u040C\u040E-\u044F\u0451-\u045C\u045E-\u0481\u0490-\u04C4\u04C7\u04C8\u04CB\u04CC\u04D0-\u04EB\u04EE-\u04F5\u04F8\u04F9\u0531-\u0556\u0559\u0561-\u0586\u05D0-\u05EA\u05F0-\u05F2\u0621-\u063A\u0641-\u064A\u0671-\u06B7\u06BA-\u06BE\u06C0-\u06CE\u06D0-\u06D3\u06D5\u06E5\u06E6\u0905-\u0939\u093D\u0958-\u0961\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8B\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AE0\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B36-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB5\u0BB7-\u0BB9\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60\u0D61\u0E01-\u0E2E\u0E30\u0E32\u0E33\u0E40-\u0E45\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD\u0EAE\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0F40-\u0F47\u0F49-\u0F69\u10A0-\u10C5\u10D0-\u10F6\u1100\u1102\u1103\u1105-\u1107\u1109\u110B\u110C\u110E-\u1112\u113C\u113E\u1140\u114C\u114E\u1150\u1154\u1155\u1159\u115F-\u1161\u1163\u1165\u1167\u1169\u116D\u116E\u1172\u1173\u1175\u119E\u11A8\u11AB\u11AE\u11AF\u11B7\u11B8\u11BA\u11BC-\u11C2\u11EB\u11F0\u11F9\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2126\u212A\u212B\u212E\u2180-\u2182\u3007\u3021-\u3029\u3041-\u3094\u30A1-\u30FA\u3105-\u312C\u4E00-\u9FA5\uAC00-\uD7A3]/; + +exports.combiningChar = /[\u0300-\u0345\u0360\u0361\u0483-\u0486\u0591-\u05A1\u05A3-\u05B9\u05BB-\u05BD\u05BF\u05C1\u05C2\u05C4\u064B-\u0652\u0670\u06D6-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0901-\u0903\u093C\u093E-\u094D\u0951-\u0954\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u0A02\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A70\u0A71\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0B01-\u0B03\u0B3C\u0B3E-\u0B43\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B82\u0B83\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C01-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C82\u0C83\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0D02\u0D03\u0D3E-\u0D43\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86-\u0F8B\u0F90-\u0F95\u0F97\u0F99-\u0FAD\u0FB1-\u0FB7\u0FB9\u20D0-\u20DC\u20E1\u302A-\u302F\u3099\u309A]/; + +exports.digit = /[0-9\u0660-\u0669\u06F0-\u06F9\u0966-\u096F\u09E6-\u09EF\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0BE7-\u0BEF\u0C66-\u0C6F\u0CE6-\u0CEF\u0D66-\u0D6F\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29]/; + +exports.extender = /[\xB7\u02D0\u02D1\u0387\u0640\u0E46\u0EC6\u3005\u3031-\u3035\u309D\u309E\u30FC-\u30FE]/; +},{}],40:[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) { + var moduleName = 'DOM', + severityWarning = 2; + hintLog.logMessage(moduleName, message, severityWarning); +}; /** * Decorates $controller with a patching function to @@ -629,69 +1698,87 @@ angular.module('ngHintDom', []). $provide.decorator('$controller', function($delegate, $injector) { var patchedServices = {}; + var patchedDependencies = {}; return function(ctrl, locals) { - if(typeof ctrl == 'string') { - ctrl = nameToConstructorMappings[ctrl]; + //If this controller is the NgModelController created by Angular + //There is no need to detect its manipulation of the DOM + if(ctrl.toString().indexOf('@name ngModel.NgModelController#$render') > -1 || + ctrl.toString().indexOf('@name form.FormController') > -1) { + return $delegate.apply(this, [ctrl, locals]); + } + + //If the controller method is given only the controller's name, + //find the matching controller method from the controller list + if(typeof ctrl === 'string') { + ctrl = nameToConstructorMappings[ctrl] || window[ctrl] || ctrl; + if(typeof ctrl === 'string') { + throw new Error('The controller function for ' + ctrl + ' could not be found.' + + ' Is the function registered under that name?'); + } } var dependencies = $injector.annotate(ctrl); // patch methods on $scope - if (!locals) { - locals = {}; - } + locals = locals || {}; dependencies.forEach(function (dep) { if (typeof dep === 'string' && !locals[dep]) { locals[dep] = patchedServices[dep] || - (patchedServices[dep] = patchService($injector.get('$timeout'))); + (patchedServices[dep] = patchService($injector.get(dep))); } }); + function disallowedContext(fn) { return function () { - domInterceptor.addManipulationListener(); + domInterceptor.addManipulationListener(INTERCEPTOR_FUNCTION); var ret = fn.apply(this, arguments); domInterceptor.removeManipulationListener(); return ret; - } + }; } function patchArguments (fn) { return function () { - for (var i = 0; i < arguments.length; i++) { + 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') { + } else if (obj !== null && typeof obj === 'object') { return Object.keys(obj).reduce(function (obj, prop) { + if(patchedDependencies[obj[prop]]) { + return obj[prop], obj; + } + patchedDependencies[obj[prop]] = obj[prop]; return obj[prop] = patchService(obj[prop]), obj; }, obj); } return obj; } - // body of controller - domInterceptor.addManipulationListener(false, false, false, true); + //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(); - // controller.test + //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') { @@ -699,11 +1786,17 @@ angular.module('ngHintDom', []). } }); } + 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); @@ -715,184 +1808,98 @@ angular.module = function() { return module; }; -},{"dom-interceptor":6}],6:[function(require,module,exports){ -(function (domInterceptor) { - +},{"angular-hint-log":60,"dom-interceptor":41}],41:[function(require,module,exports){ 'use strict'; /** -* Controls the patching process by patching all necessary -* prototypes as well as triggering the patching of individual -* HTML elements. -**/ -domInterceptor.addManipulationListener = function(loudError, debugStatement, propOnly, includeLine) { - domInterceptor.listener = domInterceptor._listener; - domInterceptor.setListenerDefaults(loudError, debugStatement, propOnly, includeLine); - domInterceptor.collectUnalteredPrototypeProperties(Element, 'Element'); - domInterceptor.patchOnePrototype(Element); - domInterceptor.collectUnalteredPrototypeProperties(Node, 'Node'); - domInterceptor.patchOnePrototype(Node); - domInterceptor.collectUnalteredPrototypeProperties(EventTarget, 'EventTarget'); - domInterceptor.patchOnePrototype(EventTarget); - domInterceptor.collectUnalteredPrototypeProperties(Document, 'Document'); - domInterceptor.patchOnePrototype(Document); - domInterceptor.listener = domInterceptor.savedListener; -}; - -/** -* Set the listener function to a custom value -* if the provided listener is not undefined and -* is a function. If the parameter does not meet these -* standards, leave domInterceptor.callListenerWithMessage as the default error -* throwing function. +* The DOM-interceptor should not throw errors because +* of its own access to the DOM. Within the interceptor +* the listener should have no behavior. */ -domInterceptor.setListenerDefaults = function(loudError, debugBreak, propOnly, includeLine) { - loudError ? domInterceptor.loudError = true : domInterceptor.loudError = false; - debugBreak ? domInterceptor.debugBreak = true : domInterceptor.debugBreak = false; - propOnly ? domInterceptor.propOnly = true : domInterceptor.propOnly = false; - includeLine ? domInterceptor.includeLine = true : domInterceptor.includeLine = false; -}; - -domInterceptor._listener = domInterceptor.NOOP = function() {}; +var _listener = function() {}; +var listener = savedListener; +var savedListener = function(message) {}; -domInterceptor.listener = domInterceptor.savedListener; - -domInterceptor.savedListener = function(messageProperties) { - domInterceptor.callListenerWithMessage(messageProperties); -}; +/** +* 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; +} /** -* Error function thrown on detection of DOM manipulation. -* May be overriden to throw custom error function if desired. +* The interceptor should give a helpful message when manipulation is detected. */ -domInterceptor.callListenerWithMessage = function(messageProperties) { - var message; - var lineNumber; - if (!domInterceptor.propOnly) { - message = messageProperties['property']; - if (domInterceptor.includeLine) { - var e = new Error(); - //Find the line in the user's program rather than in this service - var lineNum = e.stack.split('\n')[4]; - lineNum = lineNum.split(' ')[1]; - lineNumber = lineNum; - } - } +var explanation = 'Detected Manipulation of DOM API: '; - if(domInterceptor.loudError) { - throw new Error(message + ' ' + lineNumber); - } - else if(domInterceptor.debugBreak) { - debugger; - } - else { - domInterceptor.createMessageTable(message, lineNumber); - } -}; /** -* Default formatting of message to be given on DOM API manipulation from -* a controller. +* 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. */ -domInterceptor.message = 'Angular best practices are to manipulate the DOM in the view.' + -' See: (https://github.com/angular/angular-hint-dom/blob/master/README.md) ' + -'Expand to view manipulated properties and line numbers.'; - -domInterceptor.givenMessages = {}; -domInterceptor.currentMessages = []; -domInterceptor.lines = []; -domInterceptor.createMessageTable = function(warning, lineNumber) { - if(!domInterceptor.givenMessages[lineNumber]) { - domInterceptor.givenMessages[lineNumber] = lineNumber; - domInterceptor.currentMessages.push(warning); - domInterceptor.lines.push(lineNumber); +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); } -}; +} /** -* Buffer console messages and release them at reasonable time intervals. -* Use the console.group message to organize information where available. -* Default to other console methods if the browser does not support console.group. +* Finds the line number where access of a DOM API was detected */ -setTimeout(function() { - if(console.group) { - if(domInterceptor.currentMessages.length > 1) { - console.group(domInterceptor.message); - for(var i = 0; i < domInterceptor.currentMessages.length; i++) { - console.log(domInterceptor.currentMessages[i] + ' ' + domInterceptor.lines[i]); +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]; } - console.groupEnd(); - } - else if(domInterceptor.currentMessages.length > 0) { - console.log(domInterceptor.message); - console.log(domInterceptor.currentMessages[0]); - } - } - else if(console.warn) { - console.warn(domInterceptor.message); - for(var i = 0; i < domInterceptor.currentMessages.length; i++) { - console.warn(domInterceptor.currentMessages[i] + ' ' + domInterceptor.lines[i]); - } - } - else { - console.log(domInterceptor.message); - for(var i = 0; i < domInterceptor.currentMessages.length; i++) { - console.log(domInterceptor.currentMessages[i] + ' ' + domInterceptor.lines[i]); - } } - domInterceptor.currentMessages = []; - domInterceptor.lines = []; -}, 3000); + lineNum = lineNum.split(' ')[1] || lineNum; + return lineNum; +} /** * Object to preserve all the original properties * that will be restored after patching. **/ -domInterceptor.originalProperties = {}; - -/** -* Helper method to collect all properties of a given prototype. -* When patching is removed, all prototype properties -* are set back to these original values -**/ -domInterceptor.collectUnalteredPrototypeProperties = function(type, typeName) { - domInterceptor.listener = domInterceptor._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) {} - }); - domInterceptor.listener = domInterceptor.savedListener; - domInterceptor.originalProperties[typeName] = objectProperties; - return objectProperties; -}; +var originalProperties = {}; /** * Helper function for patching one prototype. -* Patches the given type with the addition of a -* call to listener, a function passed as a parameter. -* If no listener function is provided, the default listener is used. +* Saves the unaltered state of the prototype using collectUnalteredPrototypeProperties() +* and then patches the given prototype with a call to the listener. */ -domInterceptor.patchOnePrototype = function(type) { - domInterceptor.listener = domInterceptor._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.'); + 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 = undefined; + var desc; try { desc = Object.getOwnPropertyDescriptor(type.prototype, prop); } @@ -903,41 +1910,52 @@ domInterceptor.patchOnePrototype = function(type) { if (typeof desc.value === 'function') { var originalValue = desc.value; desc.value = function () { - domInterceptor.listener({message: '', property: prop}); + listener(explanation + prop + (stackTraceLine ? ' ' + findLineNumber() : '')); return originalValue.apply(this, arguments); }; } - } else { - if (typeof desc.set === 'function') { - var originalSet = desc.set; - desc.set = function () { - domInterceptor.listener('set:' + prop); - return originalSet.apply(this, arguments); - }; - } - if (typeof desc.get === 'function') { - var originalGet = desc.get; - desc.get = function () { - domInterceptor.listener('get:' + prop); - return originalGet.apply(this, arguments); - }; - } } Object.defineProperty(type.prototype, prop, desc); } else if (desc.writable) { - try { - var original = type.prototype[prop]; - type.prototype[prop] = function () { - domInterceptor.listener({message: '', property: prop}); - return original.apply(this, arguments); - }; + try { + var original = type.prototype[prop]; + type.prototype[prop] = function () { + listener(explanation + prop + (stackTraceLine ? ' ' + findLineNumber() : '')); + return original.apply(this, arguments); + }; + } + catch (e) {} } - catch (e) {} - } } }); - domInterceptor.listener = domInterceptor.savedListener; -}; + listener = savedListener; +} + +/** +* 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; +} /** * Controls the unpatching process by unpatching the @@ -945,23 +1963,22 @@ domInterceptor.patchOnePrototype = function(type) { * HTML elements and returning those patched elements to their * original state. **/ -domInterceptor.removeManipulationListener = function() { - domInterceptor.listener = domInterceptor._listener; - domInterceptor.unpatchOnePrototype(Element, 'Element'); - domInterceptor.unpatchOnePrototype(Node, 'Node'); - domInterceptor.unpatchOnePrototype(EventTarget, 'EventTarget'); - domInterceptor.unpatchOnePrototype(Document, 'Document'); - domInterceptor.listener = domInterceptor.savedListener; -}; +function removeManipulationListener() { + listener = _listener; + unpatchOnePrototype(Element, 'Element'); + unpatchOnePrototype(Node, 'Node'); + unpatchOnePrototype(Document, 'Document'); + listener = savedListener; +} /** * Helper function to unpatch one prototype. * Sets all properties of the given type back to the * original values that were collected. **/ -domInterceptor.unpatchOnePrototype = function(type, typeName) { - domInterceptor.listener = domInterceptor._listener; - if(typeName == undefined) { +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); @@ -970,200 +1987,649 @@ domInterceptor.unpatchOnePrototype = function(type, typeName) { try{ var alteredElement = type.prototype[prop]; if(typeof alteredElement === 'function') { - type.prototype[prop] = domInterceptor.originalProperties[typeName][prop]; + type.prototype[prop] = originalProperties[typeName][prop]; } - } - catch(e) {} + } catch(e) {} }); - domInterceptor.listener = domInterceptor.savedListener; -}; + listener = savedListener; +} -/**************************************************************************************************/ -/** EXTRA PATCHING METHODS NOT USED IN MAIN DOM-MANIPULATION DETECTOR **/ +module.exports.addManipulationListener = addManipulationListener; +module.exports.removeManipulationListener = removeManipulationListener; +module.exports.patchOnePrototype = patchOnePrototype; +module.exports.unpatchOnePrototype = unpatchOnePrototype; +module.exports.enableLineNumbers = enableLineNumbers; -/** -* List of DOM API properties to patch on individual elements. -* These are properties not covered by patching of the prototypes -* and must therefore be patched on the elements themselves. -**/ -domInterceptor.propertiesToPatch = ['innerHTML', 'parentElement']; + +},{}],42:[function(require,module,exports){ +'use strict'; /** -* Object to hold original version of patched elements +* Load necessary functions from /lib into variables. */ -domInterceptor.savedElements = {}; +var ngEventDirectives = require('./lib/getEventDirectives')(), + getEventAttribute = require('./lib/getEventAttribute'), + getFunctionNames = require('./lib/getFunctionNames'), + formatResults = require('./lib/formatResults'); /** -* While patching prototypes patches many of the DOM APIs, -* some properties exist only on the elements themselves. This -* function retrieves all the current elements on the page and -* patches them to call the given listener function if manipulated. +* Decorate $provide in order to examine ng-event directives +* and hint about their effective use. */ -domInterceptor.patchExistingElements = function() { - domInterceptor.listener = domInterceptor._listener; - var elements = document.getElementsByTagName('*'); - for(var i = 0; i < elements.length; i++) { - domInterceptor.save(elements[i], i); - domInterceptor.patchElementProperties(elements[i]); +angular.module('ngHintEvents', []) + .config(['$provide', function($provide) { + + for(var directive in ngEventDirectives) { + var dirName = ngEventDirectives[directive] + 'Directive'; + + $provide.decorator(dirName, ['$delegate', '$timeout', '$parse', + function($delegate, $timeout, $parse) { + $delegate[0].compile = function(element, attrs) { + var eventAttrName = getEventAttribute(attrs.$attr), + fn = $parse(attrs[eventAttrName]), + messages = []; + + return function ngEventHandler(scope, element, attrs) { + for(var attr in attrs.$attr) { + var boundFuncs = getFunctionNames(attrs[attr]); + + //For the event functions that are bound, find if they exist on the scope + 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}); + }); + }); + + //Hint about any mistakes found + formatResults(messages); + }; + }; + return $delegate; + } + ]); + } + }]); +},{"./lib/formatResults":44,"./lib/getEventAttribute":45,"./lib/getEventDirectives":46,"./lib/getFunctionNames":47}],43:[function(require,module,exports){ +'use strict'; + +var getValidProps = require('./getValidProps'), + suggest = require('suggest-it'); + +module.exports = function addSuggestions(messages) { + messages.forEach(function(messageObj) { + var dictionary = getValidProps(messageObj.scope), + suggestion = suggest(dictionary)(messageObj.boundFunc); + messageObj['match'] = suggestion; + }); + return messages; +}; + +},{"./getValidProps":48,"suggest-it":50}],44:[function(require,module,exports){ +'use strict'; + +var hintLog = angular.hint = require('angular-hint-log'), + addSuggestions = require('./addSuggestions'), + MODULE_NAME = 'Events', + SEVERITY_ERROR = 1; + +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); + }); } - domInterceptor.listener = domInterceptor.savedListener; }; -/** -* Function to patch specified properties of a given -* element to call the listener function on getting or setting -**/ -domInterceptor.patchElementProperties = function(element) { - domInterceptor.listener = domInterceptor._listener; - var real = {}; - domInterceptor.propertiesToPatch.forEach(function(prop) { - real[prop] = element[prop]; - Object.defineProperty(element, prop, { - configurable: true, - get: function() { - domInterceptor.listener({message: '', property: prop}); - return real[prop]; - }, - set: function(newValue) { - domInterceptor.listener({message: '', property: prop}); - real[prop] = element[prop]; +},{"./addSuggestions":43,"angular-hint-log":60}],45:[function(require,module,exports){ +'use strict'; + +var ngEventDirectives = require('./getEventDirectives')(); + +module.exports = function getEventAttribute(attrs) { + for(var attr in attrs) { + if(ngEventDirectives[attr]) { + return attr; + } + } +}; + +},{"./getEventDirectives":46}],46:[function(require,module,exports){ +'use strict'; + +module.exports = function getEventDirectives() { + var list = 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '); + var eventDirHash = {}; + list.map(function(x) { + var name = 'ng'+x.charAt(0).toUpperCase()+x.substring(1); + eventDirHash[name] = name; + }); + return eventDirHash; +}; + +},{}],47:[function(require,module,exports){ +'use strict'; + +module.exports = function getFunctionNames(str) { + 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; + } + }).filter(function(x){return x;}); + return results; +}; + +},{}],48:[function(require,module,exports){ +'use strict'; + +module.exports = function getValidProps(obj) { + var props = []; + for(var prop in obj) { + if (prop.charAt(0) !== '$' && typeof obj[prop] === 'function') { + props.push(prop); + } + } + return props; +}; + +},{}],49:[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]; +} + + +},{}],50:[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":49}],51:[function(require,module,exports){ +'use strict'; + +var getAllParts = require('./lib/getAllParts'); +var buildMessage = require('./lib/buildMessage'); + +angular.module('ngHintInterpolation', []) + .config(['$provide', function($provide) { + $provide.decorator('$interpolate', ['$delegate', function($delegate) { + 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]); + return result; + }; + }; + angular.extend(interpolateWrapper,$delegate); + return interpolateWrapper; + }]); + }]); + +},{"./lib/buildMessage":52,"./lib/getAllParts":54}],52:[function(require,module,exports){ +var partsEvaluate = require('./partsEvaluate'), + hintLog = angular.hint = require('angular-hint-log'), + MODULE_NAME = 'Interpolation', + SEVERITY_ERROR = 1; + +module.exports = function(allParts, originalInterpolation, scope) { + var res = partsEvaluate(allParts, originalInterpolation, scope); + if(res[1]) { + var suggestion = (res[0]) ? ' Try: "' + res[0] + '"' : '', + part = res[1]; + message = '"' + part + '" was found to be undefined in "' + originalInterpolation + '".' + + suggestion; + hintLog.logMessage(MODULE_NAME, message, SEVERITY_ERROR); + } +}; + +},{"./partsEvaluate":57,"angular-hint-log":60}],53:[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; +}; + +},{}],54:[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); + } + } }); - domInterceptor.listener = domInterceptor.savedListener; - return element; + return comboParts; +}; + +},{"./concatParts":53,"./getInterpolation":55,"./getOperands":56}],55:[function(require,module,exports){ +module.exports = function(text, startSym, endSym) { + var startInd = text.indexOf(startSym) + startSym.length, + endInd = text.indexOf(endSym); + return text.substring(startInd, endInd); }; +},{}],56:[function(require,module,exports){ +module.exports = function(str) { + return str.split(/[\+\-\/\|<\>\^=&!%~]/g); +}; + +},{}],57:[function(require,module,exports){ +var suggest = require('suggest-it'); + +module.exports = function(allParts, originalInterpolation, scope) { + var suggestion, partToSend, found = false; + + allParts.forEach(function(part) { + if(!scope.$eval(part) && !found){ + found = true; + var perInd = part.lastIndexOf('.'), + tempScope = (perInd > -1) ? scope.$eval(part.substring(0, perInd)) : scope, + tempPart = part.substring(part.lastIndexOf('.') + 1), + dictionary = Object.keys(tempScope); + suggestion = suggest(dictionary)(tempPart); + partToSend = part; + } + }); + + return [suggestion, partToSend]; +}; + +},{"suggest-it":59}],58:[function(require,module,exports){ +module.exports=require(49) +},{}],59:[function(require,module,exports){ +module.exports=require(50) +},{"./levenstein_distance":58}],60:[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 Messages', 'Warning Messages', 'Suggestion Messages']; + /** -* Function to save properties that will be patched -* Each element has an object associating with it the patched properties +* Add a message to the HintLog message queue. Messages are organized into categories +* according to their module name and severity. **/ -domInterceptor.save = function(element, index) { - domInterceptor.listener = domInterceptor._listener; - var elementProperties = {}; - domInterceptor.propertiesToPatch.forEach(function(prop) { - elementProperties[prop] = element[prop]; +function logMessage(moduleName, message, severity) { + //If no severity was provided, categorize the message under `Suggestion Messages` + 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); + } + + module.exports.onMessage(message); +} + +/** +* Return and empty the current queue of messages. +**/ +function flush() { + var flushMessages = queuedMessages; + queuedMessages = {}; + return flushMessages; +} + +module.exports.onMessage = function(message) {}; +module.exports.logMessage = logMessage; +module.exports.flush = flush; + +},{}],61:[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 doc = Array.prototype.slice.call(document.getElementsByTagName('*')), + originalAngularModule = angular.module, + modules = {}; + +storeNgAppAndView(doc); + +angular.module = function() { + var requiresOriginal = arguments[1], + module = originalAngularModule.apply(this, arguments), + name = module.name; + module.requiresOriginal = requiresOriginal; + modules[name] = module; + hasNameSpace(module.name); + var modToCheck = getModule(module.name, true); + + if(modToCheck && modToCheck.requiresOriginal !== module.requiresOriginal) { + if(!modData.createdMulti[module.name]) { + modData.createdMulti[module.name] = [getModule(module.name,true)]; + } + modData.createdMulti[module.name].push(module); + } + modData.createdModules[module.name] = module; + return module; +}; + +angular.module('ngHintModules', []).config(function() { + var ngAppMod = modules[modData.ngAppMod]; + storeUsedModules(ngAppMod, modules); + start(); +}); + +},{"./lib/getModule":64,"./lib/hasNameSpace":68,"./lib/moduleData":70,"./lib/start":73,"./lib/storeDependencies":74,"./lib/storeNgAppAndView":75,"./lib/storeUsedModules":76}],62:[function(require,module,exports){ +var hintLog = angular.hint = require('angular-hint-log'), + MODULE_NAME = 'Modules'; + +module.exports = function(modules) { + modules.forEach(function(module) { + hintLog.logMessage(MODULE_NAME, module.message, module.severity); }); - domInterceptor.savedElements[index] = elementProperties; - domInterceptor.listener = domInterceptor.savedListener; }; +},{"angular-hint-log":60}],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 multi = modData.createdMulti[modName]; + var details = { + existingModule: multi[multi.length - 1], + overwrittenModules: multi.slice(0,multi.length-1) + }; + multiLoaded.push({module:details, message:message, name: MODULE_NAME, + severity: SEVERITY_WARNING}); + } + return multiLoaded; +}; + +},{"./moduleData":70}],64:[function(require,module,exports){ +var modData = require('./moduleData'); -/** -* Unpatches all the elements on the page that were patched. -*/ -domInterceptor.unpatchExistingElements = function() { - domInterceptor.listener = domInterceptor._listener; - var elements = document.getElementsByTagName('*'); - for(var i = 0; i < elements.length; i++) { - var originalElement = domInterceptor.savedElements[i]; - domInterceptor.unpatchElementProperties(elements[i], originalElement); +module.exports = function(moduleName, getCreated) { + return (getCreated)? modData.createdModules[moduleName] : modData.loadedModules[moduleName]; +}; + +},{"./moduleData":70}],65:[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); } - domInterceptor.listener = domInterceptor.savedListener; + return attrs['ng-app'] ? attrs['ng-app'].value : undefined; }; -/** -* Helper function to unpatch all properties of a given element -*/ -domInterceptor.unpatchElementProperties = function(element, originalElement) { - domInterceptor.listener = domInterceptor._listener; - domInterceptor.propertiesToPatch.forEach(function(prop) { - Object.defineProperty(element, prop, { - configurable: true, - get: function() { - return originalElement[prop]; - }, - set: function(newValue) { - element.prop = newValue; +},{"angular-hint-log":60}],66:[function(require,module,exports){ +var getModule = require('./getModule'), + dictionary = Object.keys(require('./moduleData').createdModules), + suggest = require('suggest-it')(dictionary), + SEVERITY_ERROR = 1; + +module.exports = function(loadedModules) { + var undeclaredModules = []; + 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}); + } + } + return undeclaredModules; +}; + +},{"./getModule":64,"./moduleData":70,"suggest-it":78}],67:[function(require,module,exports){ +var getModule = require('./getModule'), + IGNORED = ['ngHintControllers', 'ngHintDirectives', 'ngHintDOM', 'ngHintEvents', + 'ngHintInterpolation', 'ngHintModules']; + 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}); } + } + } + return unusedModules; +}; + +},{"./getModule":64}],68:[function(require,module,exports){ +var hintLog = angular.hint = require('angular-hint-log'), + MODULE_NAME = 'Modules', + SEVERITY_SUGGESTION = 3; +module.exports = function(str) { + 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":60}],69:[function(require,module,exports){ +var normalizeAttribute = require('./normalizeAttribute'); + +module.exports = function(attrs) { + 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":72}],70:[function(require,module,exports){ +module.exports = { + createdModules: {}, + createdMulti: {}, + loadedModules: {} +}; + +},{}],71:[function(require,module,exports){ +var modData = require('./moduleData'), + getModule = require('./getModule'); + +module.exports = function() { + if(modData.ngViewExists && !getModule('ngRoute')) { + return {message: 'Directive "ngView" was used in the application however "ngRoute" was not loaded into any module.'}; + } +}; + +},{"./getModule":64,"./moduleData":70}],72:[function(require,module,exports){ +module.exports = function(attribute) { + return attribute.replace(/^(?:data|x)[-_:]/,'').replace(/[:_]/g,'-'); +}; + +},{}],73:[function(require,module,exports){ +var display = require('./display'), + formatMultiLoaded = require('./formatMultiLoaded'), + getUnusedModules = require('./getUnusedModules'), + getUndeclaredModules = require('./getUndeclaredModules'), + modData = require('./moduleData'), + ngViewNoNgRoute = require('./ngViewNoNgRoute'); + +module.exports = function() { + var unusedModules = getUnusedModules(modData.createdModules), + undeclaredModules = getUndeclaredModules(modData.loadedModules), + multiLoaded = formatMultiLoaded(), + noNgRoute = ngViewNoNgRoute(); + if(unusedModules.length || undeclaredModules.length || multiLoaded.length || noNgRoute) { + var toSend = unusedModules.concat(undeclaredModules) + .concat(multiLoaded); + if(noNgRoute) { + toSend = toSend.concat(noNgRoute); + } + display(toSend); + } +}; + +},{"./display":62,"./formatMultiLoaded":63,"./getUndeclaredModules":66,"./getUnusedModules":67,"./moduleData":70,"./ngViewNoNgRoute":71}],74:[function(require,module,exports){ +var modData = require('./moduleData'); + +module.exports = function(module, isNgAppMod) { + var name = module.name || module; + if(!isNgAppMod){ + module.requires.forEach(function(dependency){ + modData.loadedModules[dependency] = dependency; }); - }); - domInterceptor.listener = domInterceptor.savedListener; -}; - - -// /** -// * Methods to patch DOM Access based on the harmony-reflect library and -// * the use of proxies. Currently proxies are considered experimental javascript. -// * In chrome, proxies can be enabled with the enable-javascript-harmony flag. -// * When support of proxies is more common, these functions could be used to patch -// * DOM elements on retrieval so that only the proxies are patched. -// */ -// domInterceptor.accessFunctions = ['getElementsByClassName', 'getElementsByName', -// 'getElementsByTagName', 'getElementsByTagNameNS']; -// domInterceptor.unpatchedFunctions = {}; -// domInterceptor.patchAccess = function() { -// var originalIndividual = Document.prototype['getElementById']; -// domInterceptor.unpatchedFunctions['getElementById'] = originalIndividual; -// Document.prototype['getElementById'] = function() { -// return domInterceptor.getProxy(originalIndividual.apply(this, arguments)); -// } -// domInterceptor.accessFunctions.forEach(function(accessFunction) { -// var originalFunction = Document.prototype[accessFunction]; -// domInterceptor.unpatchedFunctions[accessFunction] = originalFunction; -// Document.prototype[accessFunction] = function() { -// return domInterceptor.getProxyList(originalFunction.apply(this, arguments)); -// } -// }); -// }; - -// /** -// * Attempts to create a proxy element in place of a created element when the method -// * is called. Currently causes the proxy to be null. -// */ -// domInterceptor.patchCreation = function() { -// var originalCreate = Document.prototype['createElement']; -// domInterceptor.unpatchedFunctions['createElement'] = Document.prototype['createElement']; -// Document.prototype['createElement'] = function() { -// return domInterceptor.getProxy(originalCreate.apply(this, arguments)); -// } -// } - -// /** -// * Helper method to get a list of proxies for methods that access -// * lists of DOM elements such as getElementsByTagName() -// */ -// domInterceptor.getProxyList = function(elementList) { -// var elems = {}; -// for(var i = 0; i < Object.keys(elementList).length - 1; i++) { -// if(elementList[i]) { -// elems[i] = domInterceptor.getProxy(elementList[i]); -// } -// } -// return elems; -// }; - -// /** -// * Creates a proxy element that is accessed instead of a given DOM element. -// * This proxy is patched to call the desired listener function. -// * Hence, the proxy has the functionality necessary to detect DOM manipulation, -// * but the original element is still fully functional. -// */ -// domInterceptor.getProxy = function(element) { -// var proxyElement = new Proxy(element, { -// get: function(target, name, receiver) { -// domInterceptor.savedListener({message: '', property: name}); -// return Reflect.get(target, name, receiver); -// }, -// set: function(target, name, value, receiver) { -// domInterceptor.savedListener({message: '', property: name}); -// return Reflect.set(target, name, value, receiver); -// } -// }); -// return proxyElement; -// }; - -// /** -// * Removes proxies of elements. -// */ -// domInterceptor.unPatchAccess = function() { -// Document.prototype['getElementById'] = domInterceptor.unpatchedFunctions['getElementById']; -// domInterceptor.accessFunctions.forEach(function(accessFunction) { -// Document.prototype[accessFunction] = domInterceptor.unpatchedFunctions[accessFunction]; -// }); -// }; - -}((typeof module !== 'undefined' && module && module.exports) ? - (module.exports = window.domInterceptor = {}) : (window.domInterceptor = {}) )); - -},{}]},{},[1]) \ No newline at end of file + } + else { + modData.loadedModules[name] = name; + modData.ngAppMod = name; + } +}; + +},{"./moduleData":70}],75:[function(require,module,exports){ +var getNgAppMod = require('./getNgAppMod'), + inAttrsOrClasses = require('./inAttrsOrClasses'), + storeDependencies = require('./storeDependencies'), + modData = require('./moduleData'); + +module.exports = function(doms) { + var bothFound, + ngViewFound, + elem, + isElemName, + isInAttrsOrClasses, + ngAppMod; + + for(var i = 0; i < doms.length; i++) { + elem = doms[i]; + isElemName = elem.nodeName.toLowerCase() === 'ng-view'; + isInAttrsOrClasses = inAttrsOrClasses(elem.attributes); + + ngViewFound = isElemName || isInAttrsOrClasses; + + ngAppMod = getNgAppMod(elem.attributes, modData.ngAppFound); + modData.ngAppFound = modData.ngAppFound || ngAppMod; + + if(ngAppMod) { + storeDependencies(ngAppMod, true); + } + modData.ngViewExists = ngViewFound ? true : modData.ngViewExists; + + if(bothFound) { + break; + } + } +}; + +},{"./getNgAppMod":65,"./inAttrsOrClasses":69,"./moduleData":70,"./storeDependencies":74}],76:[function(require,module,exports){ +var storeDependencies = require('./storeDependencies'); + +var storeUsedModules = module.exports = function(module, modules){ + if(module) { + storeDependencies(module); + module.requires.forEach(function(modName) { + var mod = modules[modName]; + storeUsedModules(mod, modules); + }); + } +}; +},{"./storeDependencies":74}],77:[function(require,module,exports){ +module.exports=require(49) +},{}],78:[function(require,module,exports){ +module.exports=require(50) +},{"./levenstein_distance":77}]},{},[2]); diff --git a/hint.html b/hint.html index 28fcbb7..3ff681d 100644 --- a/hint.html +++ b/hint.html @@ -7,8 +7,6 @@ - - diff --git a/hint.js b/hint.js index d195ef1..f3675fe 100644 --- a/hint.js +++ b/hint.js @@ -1,23 +1,12 @@ require('./bower_components/angular-loader/angular-loader.js'); require('angular-hint'); +var eventProxyElement = document.getElementById('__ngDebugElement'); -// afterThisGetsDefined(window, 'angular', function () { -// afterThisGetsDefined(angular, 'module', function () { -// require('angular-hint'); -// }); -// }); +var customEvent = document.createEvent('Event'); +customEvent.initEvent('myCustomEvent', true, true); -// function afterThisGetsDefined(obj, prop, fn) { -// Object.defineProperty(obj, prop, { -// set: function (val) { -// Object.defineProperty(obj, prop, { -// configurable: true, -// writable: true -// }); -// obj[prop] = val; -// fn(); -// }, -// configurable: true -// }); -// } +angular.hint.onMessage = function (data) { + eventProxyElement.innerText = data; + eventProxyElement.dispatchEvent(customEvent); +}; diff --git a/inject.js b/inject.js index 1f85f8e..3e5a7c4 100644 --- a/inject.js +++ b/inject.js @@ -1,10 +1,20 @@ +var html = document.getElementsByTagName('html')[0]; + +var eventProxyElement = document.createElement('div'); +eventProxyElement.id = '__ngDebugElement'; +eventProxyElement.style.display = 'none'; +html.appendChild(eventProxyElement); // inject into the application context from the content script context var script = window.document.createElement('script'); script.src = chrome.extension.getURL('hint.bundle.js'); -var html = document.getElementsByTagName('html')[0]; +eventProxyElement.addEventListener('myCustomEvent', function () { + var eventData = eventProxyElement.innerText; + chrome.extension.sendMessage(eventData); +}); + html.setAttribute('ng-hint', ''); html.appendChild(script); diff --git a/js/devtoolsBackground.js b/js/devtoolsBackground.js index bc8a557..e421816 100644 --- a/js/devtoolsBackground.js +++ b/js/devtoolsBackground.js @@ -5,3 +5,4 @@ var angularPanel = panels.create( "img/angular.png", "hint.html" ); + diff --git a/manifest.json b/manifest.json index 50a058a..b4c725b 100644 --- a/manifest.json +++ b/manifest.json @@ -8,15 +8,20 @@ "tabs", "" ], - // "content_scripts": [ - // { - // "matches": [""], - // "js": ["inject.js"], - // "run_at": "document_start" - // } - // ], + "content_scripts": [ + { + "matches": [""], + "js": ["inject.js"], + "run_at": "document_start" + } + ], + "background": { + "scripts": [ + "background.js" + ] + }, "web_accessible_resources": [ - // "hint.bundle.js" + "hint.bundle.js" ], "minimum_chrome_version": "21.0.1180.57" } \ No newline at end of file diff --git a/package.json b/package.json index cb1d125..437d5e6 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,11 @@ "name": "angularjs-batarang", "version": "0.5.0", "description": "chrome extension for inspecting angular apps", - "main": "index.js", + "main": "hint.js", "dependencies": { - "angular-hint": "^0.0.0", - "browserify": "^5.9.1" + "browserify": "^5.9.1", + "gulp": "^3.8.7", + "vinyl-source-stream": "^0.1.1" }, "devDependencies": {}, "scripts": {