You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
angularjs-batarang/hint.bundle.js

1169 lines
39 KiB
JavaScript

(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<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var hint = require('angular-hint');
},{"angular-hint":2}],2:[function(require,module,exports){
// decorate angular.bootstrap to check for ng-hint=
//
// everything on by default
require('angular-hint-dom');
require('angular-hint-directives');
var allModules = ['ngHintDirectives', 'ngHintDom'];
window.name = 'NG_DEFER_BOOTSTRAP!';
// determine which modules to load and resume bootstrap
angular.element(document).ready(function() {
var selectedModules;
var elts;
var includeModules = function(modulesToInclude) {
var selected = modulesToInclude.map(function(name) {
return 'ngHint' + name[0].toUpperCase() + name.substring(1);
});
return selected;
};
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;
}
});
if(notFound) {
return name;
}
});
return selected;
};
elts = document.querySelectorAll('[ng-hint-include]');
if(elts.length > 0) {
selectedModules = includeModules(elts[0].attributes['ng-hint-include'].value.split(' '));
}
else {
elts = document.querySelectorAll('[ng-hint-exclude]');
if(elts.length > 0) {
selectedModules = excludeModules(elts[0].attributes['ng-hint-exclude'].value.split(' '));
}
else {
elts = document.querySelectorAll('[ng-hint]');
if(elts.length > 0) {
selectedModules = allModules;
}
}
}
if(selectedModules != undefined) {
angular.resumeBootstrap(selectedModules);
}
else {
angular.resumeBootstrap();
}
});
},{"angular-hint-directives":4,"angular-hint-dom":5}],3:[function(require,module,exports){
(function (ddLib) {
'use strict';
ddLib.directiveDetails = {
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-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-keydown': 'A',
'ng-keypress': 'A',
'ng-keyup': 'A',
'ng-list': 'A',
'ng-maxlength': 'A',
'ng-minlength': 'A',
'ng-model': 'A',
'ng-modeloptions': '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-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-view': 'A',
'required': 'A',
'when': 'A'
}
},
'angular-custom-directives': {
message: 'There was an AngularJS error in ',
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
**/
ddLib.beginSearch = function(scopeElements, customDirectives, options) {
if(!Array.isArray(scopeElements)) {
throw new Error("Function beginSearch must be passed an array.");
}
options = options || {};
options.directiveTypes = options.directiveTypes ||
['html-directives','angular-default-directives','angular-custom-directives'];;
options.tolerance = options.tolerance || 4;
if(customDirectives) {
ddLib.setCustomDirectives(customDirectives);
}
var failedElements = ddLib.findFailedElements(scopeElements, options);
var messages = ddLib.formatResults(failedElements);
return messages;
};
ddLib.findFailedElements = function(scopeElements, options) {
return scopeElements.map(ddLib.getFailedAttributesOfElement.bind(null,options))
.filter(function(x) {return x;});
}
/**
*@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
**/
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
};
}
}
};
/**
*@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
**/
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});
}
}
else if(result.wrongUse) {
failedAttributes.
push({wrongUse:result.wrongUse, error: attr, directiveType: 'angular-custom-directives'});
}
}
return failedAttributes;
};
/**
*@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.
**/
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';
}
}
}
});
return {exsists: allTrue, wrongUse: wrongUse};
};
/**
*@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 = '';
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) {
match = tempMatch.match;
dirType = directiveType;
min_levDist = tempMatch.min_levDist;
}
}
});
return (match)? {match:match, directiveType:dirType}: null;
};
/**
*@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.
**/
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.');
}
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;
}
}
return {min_levDist: min_levDist, match: closestMatch};
};
/**
*@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,'-');
};
/**
*@param failedElements: [] of {}s of all failed elements with their failed attributes and closest
*matches or restrict properties
*
*@return [] of failed messages.
**/
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;
};
/**
*@param customDirectives: [] of custom directive objects from $compile decorator
**/
ddLib.setCustomDirectives = 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;
})
}
/**
*@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;
}
/**
*@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);
}
}
}
return d[n][m];
};
/**
* @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();});
}
}((typeof module !== 'undefined' && module && module.exports) ?
(module.exports = window.ddLib = {}) : (window.ddLib = {}) ));
},{}],4:[function(require,module,exports){
'use strict';
var ddLib = require('./dd-lib/dd-lib');
var customDirectives = [];
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;
}
},{"./dd-lib/dd-lib":3}],5:[function(require,module,exports){
'use strict';
var domInterceptor = require('dom-interceptor');
var nameToConstructorMappings = {};
/**
* 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
if (!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();
var ret = fn.apply(this, arguments);
domInterceptor.removeManipulationListener();
return ret;
}
}
function patchArguments (fn) {
return function () {
for (var i = 0; i < arguments.length; 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;
}
// body of controller
domInterceptor.addManipulationListener(false, false, false, true);
var ctrlInstance = $delegate.apply(this, [ctrl, locals]);
domInterceptor.removeManipulationListener();
// controller.test
Object.keys(ctrlInstance).forEach(function (prop) {
if (prop[0] !== '$' && typeof ctrlInstance[prop] === 'function') {
ctrlInstance[prop] = disallowedContext(ctrlInstance[prop]);
}
});
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;
};
});
});
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;
};
},{"dom-interceptor":6}],6:[function(require,module,exports){
(function (domInterceptor) {
'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.
*/
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() {};
domInterceptor.listener = domInterceptor.savedListener;
domInterceptor.savedListener = function(messageProperties) {
domInterceptor.callListenerWithMessage(messageProperties);
};
/**
* Error function thrown on detection of DOM manipulation.
* May be overriden to throw custom error function if desired.
*/
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('<anonymous> ')[1];
lineNumber = lineNum;
}
}
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.
*/
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);
}
};
/**
* 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.
*/
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]);
}
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);
/**
* 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;
};
/**
* 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.
*/
domInterceptor.patchOnePrototype = function(type) {
domInterceptor.listener = domInterceptor._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 = undefined;
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 () {
domInterceptor.listener({message: '', property: prop});
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);
};
}
catch (e) {}
}
}
});
domInterceptor.listener = domInterceptor.savedListener;
};
/**
* 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.
**/
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;
};
/**
* 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) {
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] = domInterceptor.originalProperties[typeName][prop];
}
}
catch(e) {}
});
domInterceptor.listener = domInterceptor.savedListener;
};
/**************************************************************************************************/
/** EXTRA PATCHING METHODS NOT USED IN MAIN DOM-MANIPULATION DETECTOR **/
/**
* 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'];
/**
* Object to hold original version of patched elements
*/
domInterceptor.savedElements = {};
/**
* 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.
*/
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]);
}
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];
}
});
});
domInterceptor.listener = domInterceptor.savedListener;
return element;
};
/**
* Function to save properties that will be patched
* Each element has an object associating with it the patched properties
**/
domInterceptor.save = function(element, index) {
domInterceptor.listener = domInterceptor._listener;
var elementProperties = {};
domInterceptor.propertiesToPatch.forEach(function(prop) {
elementProperties[prop] = element[prop];
});
domInterceptor.savedElements[index] = elementProperties;
domInterceptor.listener = domInterceptor.savedListener;
};
/**
* 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);
}
domInterceptor.listener = domInterceptor.savedListener;
};
/**
* 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;
}
});
});
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])