diff --git a/front/src/css/rule.css b/front/src/css/rule.css index 80474b4..b66c470 100644 --- a/front/src/css/rule.css +++ b/front/src/css/rule.css @@ -120,17 +120,18 @@ .offenders .offenderButton .domTree > div div { margin-left: 1em; } -.offenders .offenderButton .backtrace { +.offenders .offenderButton .backtrace, +.offenders .offenderButton .cssFileAndLine { white-space: nowrap; padding: 0.5em; } -.offenders .offenderButton:hover { +.offenders .offenderButton.opens.mouseOver { border-bottom-left-radius: 0; border-bottom-right-radius: 0; background: #ffe0cc; z-index: 2; } -.offenders .offenderButton:hover > div { +.offenders .offenderButton.opens.mouseOver > div { display: block; } .offendersHtml { diff --git a/front/src/js/app.js b/front/src/js/app.js index 0adc65f..7ddabb4 100644 --- a/front/src/js/app.js +++ b/front/src/js/app.js @@ -11,6 +11,7 @@ var yltApp = angular.module('YellowLabTools', [ 'resultsFactory', 'menuService', 'gradeDirective', + 'offendersDirectives' ]); yltApp.run(['$rootScope', '$location', function($rootScope, $location) { diff --git a/front/src/js/controllers/ruleCtrl.js b/front/src/js/controllers/ruleCtrl.js index e63adc1..7ff0622 100644 --- a/front/src/js/controllers/ruleCtrl.js +++ b/front/src/js/controllers/ruleCtrl.js @@ -22,10 +22,6 @@ ruleCtrl.controller('RuleCtrl', ['$scope', '$rootScope', '$routeParams', '$locat function init() { $scope.rule = $scope.result.rules[$scope.policyName]; - - if (angular.isString($scope.rule.offenders)) { - $scope.htmlOffenders = $scope.rule.offenders; - } } $scope.backToDashboard = function() { diff --git a/front/src/js/directives/offendersDirectives.js b/front/src/js/directives/offendersDirectives.js new file mode 100644 index 0000000..cb691d0 --- /dev/null +++ b/front/src/js/directives/offendersDirectives.js @@ -0,0 +1,120 @@ +var offendersDirectives = angular.module('offendersDirectives', []); + +offendersDirectives.directive('domTree', function() { + return { + restrict: 'E', + scope: { + tree: '=' + }, + template: '
', + replace: true, + link: function(scope, element, attrs) { + + function recursiveHtmlBuilder(tree) { + var html = ''; + var keys = Object.keys(tree); + + keys.forEach(function(key) { + if (isNaN(tree[key])) { + html += 'This rule reached the abnormality threshold, which means there is a real problem you should care about.
-A high number of DOM elements means a lot of work for the browser to render the page.
It also slows down JavaScript DOM queries, as there are more elements to search through.
", "isOkThreshold": 1000, "isBadThreshold": 2500, - "isAbnormalThreshold": 4000 + "isAbnormalThreshold": 4000, + "hasOffenders": false }, "DOMelementMaxDepth": { "tool": "phantomas", @@ -17,8 +18,14 @@ var policies = { "isOkThreshold": 10, "isBadThreshold": 20, "isAbnormalThreshold": 28, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offendersHelpers.listOfDomPathsToHTML(offenders); + var domArrays = offenders.map(offendersHelpers.domPathToArray); + + return { + count: offenders.length, + tree: offendersHelpers.listOfDomArraysToTree(domArrays) + }; } }, "iframesCount": { @@ -27,7 +34,8 @@ var policies = { "message": "iFrames are the most complex HTML elements. They are pages, just like the main page, and the browser needs to create a new page context, which has a cost.
", "isOkThreshold": 2, "isBadThreshold": 15, - "isAbnormalThreshold": 30 + "isAbnormalThreshold": 30, + "hasOffenders": false }, "DOMidDuplicated": { "tool": "phantomas", @@ -36,17 +44,26 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 5, "isAbnormalThreshold": 10, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var parts = /^(.*): ?(\d+) ?occurrences$/.exec(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var parts = /^(.*): ?(\d+) ?occurrences$/.exec(offender); - if (!parts) { - debug('DOMidDuplicated offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('DOMidDuplicated offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - return '#' + parts[1] + ': ' + parts[2] + ' occurrences'; - }); + return { + id: parts[1], + occurrences: parseInt(parts[2], 10) + }; + }) + }; } }, "DOMinserts": { @@ -56,17 +73,26 @@ var policies = { "isOkThreshold": 10, "isBadThreshold": 400, "isAbnormalThreshold": 1000, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var parts = /^"(.*)" ?appended ?to ?"(.*)"$/.exec(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var parts = /^"(.*)" ?appended ?to ?"(.*)"$/.exec(offender); - if (!parts) { - debug('DOMinserts offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('DOMinserts offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - return offendersHelpers.domPathToButton(parts[1]) + ' appended to ' + offendersHelpers.domPathToButton(parts[2]); - }); + return { + insertedElement: offendersHelpers.domPathToDomElementObj(parts[1]), + receiverElement: offendersHelpers.domPathToDomElementObj(parts[2]) + }; + }) + }; } }, "DOMqueries": { @@ -75,7 +101,8 @@ var policies = { "message": "DOM queries are like looking in a large catalog of items. Even if the browsers made progress on the performances of queries, websites often make hundreds of them.
Try to reduce the number of queries by refactoring your JavaScript code.
Avoid also to have a read query between two write queries. To be able to reduce the number repaints and optimize performances, browsers buffer the DOM writing operations and treat them in bulk. But each time a DOM reading is asked, the browser needs to empty the buffer. This can be particularly slow inside a loop.
", "isOkThreshold": 50, "isBadThreshold": 1000, - "isAbnormalThreshold": 2000 + "isAbnormalThreshold": 2000, + "hasOffenders": false }, "DOMqueriesWithoutResults": { "tool": "phantomas", @@ -84,17 +111,27 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 100, "isAbnormalThreshold": 200, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var parts = /^(.*) ?\(in ?(.*)\) ?using ?(.*)$/.exec(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var parts = /^(.*) ?\(in ?(.*)\) ?using ?(.*)$/.exec(offender); - if (!parts) { - debug('DOMqueriesWithoutResults offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('DOMqueriesWithoutResults offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - return '' + parts[1] + ' (in ' + offendersHelpers.domPathToButton(parts[2]) + ') using ' + parts[3]; - }); + return { + query: parts[1], + context: offendersHelpers.domPathToDomElementObj(parts[2]), + fn: parts[3] + }; + }) + }; } }, "DOMqueriesAvoidable": { @@ -104,18 +141,29 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 200, "isAbnormalThreshold": 500, + "hasOffenders": true, "takeOffendersFrom": "DOMqueriesDuplicated", "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var parts = /^[^"]* ?"(.*)" ?with ?(.*) ?\(in ?context ?(.*)\): ?(.*)\s?queries$/.exec(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var parts = /^[^"]* ?"(.*)" ?with ?(.*) ?\(in ?context ?(.*)\): ?(.*)\s?queries$/.exec(offender); - if (!parts) { - debug('DOMqueriesAvoidable offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('DOMqueriesAvoidable offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - return '' + parts[1] + ' (in ' + offendersHelpers.domPathToButton(parts[3]) + ') using ' + parts[2] + ': ' + parts[4] + ' queries'; - }); + return { + query: parts[1], + context: offendersHelpers.domPathToDomElementObj(parts[3]), + fn: parts[2], + count: parseInt(parts[4], 10) + }; + }) + }; } }, "eventsBound": { @@ -125,17 +173,26 @@ var policies = { "isOkThreshold": 100, "isBadThreshold": 800, "isAbnormalThreshold": 1500, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var parts = /^"(.*)" ?bound ?to ?"(.*)"$/.exec(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var parts = /^"(.*)" ?bound ?to ?"(.*)"$/.exec(offender); - if (!parts) { - debug('eventsBound offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('eventsBound offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - return '' + parts[1] + ' bound to ' + offendersHelpers.domPathToButton(parts[2]); - }); + return { + eventName: parts[1], + element: offendersHelpers.domPathToDomElementObj(parts[2]) + }; + }) + }; } }, "jsErrors": { @@ -145,22 +202,28 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 1, "isAbnormalThreshold": 4, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var parts = /^(.*) - (.*)$/.exec(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var parts = /^(.*) - (.*)$/.exec(offender); - if (!parts) { - debug('jsErrors offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('jsErrors offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - var backtraceArray = offendersHelpers.backtraceToArray(parts[2]); - if (backtraceArray === null) { - return offender; - } else { - return '' + parts[1] + ' ' + offendersHelpers.backtraceArrayToHtml(backtraceArray); - } - }); + var backtraceArray = offendersHelpers.backtraceToArray(parts[2]); + + return { + error: parts[1], + backtrace: backtraceArray || [] + }; + }) + }; } }, "evalCalls": { @@ -169,7 +232,8 @@ var policies = { "message": "The 'eval' function is slow and is a bad coding practice. Try to get rid of it.
", "isOkThreshold": 0, "isBadThreshold": 10, - "isAbnormalThreshold": 20 + "isAbnormalThreshold": 20, + "hasOffenders": false }, "documentWriteCalls": { "tool": "phantomas", @@ -177,7 +241,8 @@ var policies = { "message": "They slow down the page construction, especially if they are used to insert scripts in the page. Remove them ASAP.
If you cannot remove them because they come from a third-party script (such as ads), have a look at PostScribe.
", "isOkThreshold": 0, "isBadThreshold": 10, - "isAbnormalThreshold": 20 + "isAbnormalThreshold": 20, + "hasOffenders": false }, "consoleMessages": { "tool": "phantomas", @@ -185,7 +250,8 @@ var policies = { "message": "Try to keep your console clean when in production. Debugging is good for development only.
Writing in the console has a cost, especially when dumping large object variables.
There is also a problem with Internet Explorer 8, not knowing the console object.
", "isOkThreshold": 0, "isBadThreshold": 10, - "isAbnormalThreshold": 25 + "isAbnormalThreshold": 25, + "hasOffenders": false }, "globalVariables": { "tool": "phantomas", @@ -194,17 +260,22 @@ var policies = { "isOkThreshold": 10, "isBadThreshold": 50, "isAbnormalThreshold": 200, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offendersHelpers.sortVarsLikeChromeDevTools(offenders); + return { + count: offenders.length, + list: offendersHelpers.sortVarsLikeChromeDevTools(offenders) + }; } }, "jQueryVersion": { "label": "jQuery version", "message": "Current latest versions of jQuery are 1.11 (with support for old IE versions) and 2.1 (without).
Each new version of jQuery optimizes performances. Do not keep an old version of jQuery. Updating can sometimes break a few things, but it is generally quite easy to fix them up. So don't hesitate.
", + "hasOffenders": false, "scoreFn": function(data) { var differentVersions = data.toolsResults.phantomas.metrics.jQueryDifferentVersions; - if (differentVersions === 0 || differentVersions > 1) { + if (differentVersions === 0 || differentVersions > 1 || !data.toolsResults.phantomas.metrics.jQueryVersion) { // Not applicable return null; } else { @@ -257,7 +328,8 @@ var policies = { "message": "jQuery is a heavy library. You should never load jQuery more than once on the same page.
", "isOkThreshold": 1, "isBadThreshold": 2, - "isAbnormalThreshold": 2 + "isAbnormalThreshold": 2, + "hasOffenders": true }, "cssParsingErrors": { "tool": "phantomas", @@ -266,32 +338,28 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 1, "isAbnormalThreshold": 1, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var parts = /^(([^ \(]*|Having a huge number of CSS rules hurts performances. If the number of CSS rules is higher than the number of DOM elements, there is clearly a problem.
Huge stylesheets generally occur when the different pages of a website load all the CSS, concatenated in a single stylesheet, even if a large part of the rules are page-specific. Solution is to create one main CSS file with global rules and one custom files per page.
", "isOkThreshold": 500, "isBadThreshold": 2500, - "isAbnormalThreshold": 4000 + "isAbnormalThreshold": 4000, + "hasOffenders": false }, "cssComplexSelectors": { "tool": "phantomas", @@ -309,12 +378,15 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 500, "isAbnormalThreshold": 2000, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - - return '' + splittedOffender.offender + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return splittedOffender; + }) + }; } }, "cssComplexSelectorsByAttribute": { @@ -324,14 +396,18 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 50, "isAbnormalThreshold": 100, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - var boldedOffender = splittedOffender.offender.replace(/(\[[^ ]+[~\|\^\$\*]="[^"]+"\])/g, '$1'); + splittedOffender.bolded = splittedOffender.css.replace(/(\[[^ ]+[~\|\^\$\*]="[^"]+"\])/g, '$1'); - return boldedOffender + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; - }); + return splittedOffender; + }) + }; } }, "cssImports": { @@ -341,12 +417,15 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 1, "isAbnormalThreshold": 1, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - - return '' + splittedOffender.offender + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return splittedOffender; + }) + }; } }, "cssDuplicatedSelectors": { @@ -356,17 +435,26 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 40, "isAbnormalThreshold": 80, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var parts = /^(.*) \((\d+) times\)$/.exec(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var parts = /^(.*) \((\d+) times\)$/.exec(offender); - if (!parts) { - debug('cssDuplicatedSelectors offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('cssDuplicatedSelectors offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - return parts[1] + ' (x' + parts[2] + ')'; - }); + return { + rule: parts[1], + occurrences: parseInt(parts[2], 10) + }; + }) + }; } }, "cssDuplicatedProperties": { @@ -376,19 +464,31 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 50, "isAbnormalThreshold": 100, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - var parts = /^([^{]+) {([^ ]+): (.+)}$/.exec(splittedOffender.offender); + var parts = /^([^{]+) {([^ ]+): (.+)}$/.exec(splittedOffender.css); - if (!parts) { - debug('cssDuplicatedProperties offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('cssDuplicatedProperties offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - return 'Property ' + parts[2] + ' duplicated in ' + parts[1] + ' { } @ ' + splittedOffender.line + ':' + splittedOffender.character; - }); + return { + property: parts[2], + rule: parts[1], + file: splittedOffender.file, + line: splittedOffender.line, + column: splittedOffender.column + }; + }) + }; } }, "cssEmptyRules": { @@ -398,11 +498,16 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 40, "isAbnormalThreshold": 100, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - return '' + splittedOffender.offender + ' { } @ ' + splittedOffender.line + ':' + splittedOffender.character; - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + + return splittedOffender; + }) + }; } }, "cssExpressions": { @@ -412,19 +517,32 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 1, "isAbnormalThreshold": 20, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - var parts = /^(.*) {([^ ]+): expression\((.*)\)}$/.exec(splittedOffender.offender); + var parts = /^(.*) {([^ ]+): expression\((.*)\)}$/.exec(splittedOffender.css); - if (!parts) { - debug('cssExpressions offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('cssExpressions offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - return parts[1] + ' {' + parts[2] + ': expression(' + parts[3] + ')} @ ' + splittedOffender.line + ':' + splittedOffender.character; - }); + return { + rule: parts[1], + property: parts[2], + expression: parts[3], + file: splittedOffender.file, + line: splittedOffender.line, + column: splittedOffender.column + }; + }) + }; } }, "cssImportants": { @@ -434,19 +552,32 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 50, "isAbnormalThreshold": 150, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - var parts = /^(.*) {([^ ]+): (.*) ?\!important}$/.exec(splittedOffender.offender); + var parts = /^(.*) {([^ ]+): (.*) ?\!important}$/.exec(splittedOffender.css); - if (!parts) { - debug('cssImportants offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('cssImportants offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - return parts[1] + ' {' + parts[2] + ': ' + parts[3] + ' !important} @ ' + splittedOffender.line + ':' + splittedOffender.character; - }); + return { + rule: parts[1], + property: parts[2], + value: parts[3], + file: splittedOffender.file, + line: splittedOffender.line, + column: splittedOffender.column + }; + }) + }; } }, "cssOldIEFixes": { @@ -456,47 +587,54 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 50, "isAbnormalThreshold": 300, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - var parts = /^([^{]*)( {([^ ]+): (.*)})?$/.exec(splittedOffender.offender); + var parts = /^([^{]*)( {([^ ]+): (.*)})?$/.exec(splittedOffender.css); - if (!parts) { - debug('cssOldIEFixes offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('cssOldIEFixes offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - var rule = parts[1]; - var property = parts[3]; - var value = parts[4]; - var browser = null; + var rule = parts[1]; + var property = parts[3]; + var value = parts[4]; + var browser = null; - if (rule.indexOf('* html') === 0) { - rule = rule.replace(/^\* html/, '* html'); - browser = 'IE6'; - } else if (rule.indexOf('html>body') === 0) { - rule = rule.replace(/^html>body/, 'html>body'); - browser = 'IE6'; - } else if (property.indexOf('*') === 0) { - property = '' + property + ''; - browser = 'IE7'; - } else if (value.match(/\!ie$/)) { - value = value.replace(/\!ie$/, '!ie'); - browser = 'IE7'; - } else if (property === '-ms-filter') { - property = '-ms-filter'; - browser = 'IE9'; - } else if (value.indexOf('progid:DXImageTransform.Microsoft') === 0) { - value = value.replace(/^progid:DXImageTransform\.Microsoft/, 'progid:DXImageTransform.Microsoft'); - browser = 'IE9'; - } + if (rule.indexOf('* html') === 0) { + rule = rule.replace(/^\* html/, '* html'); + browser = 'IE6'; + } else if (rule.indexOf('html>body') === 0) { + rule = rule.replace(/^html>body/, 'html>body'); + browser = 'IE6'; + } else if (property.indexOf('*') === 0) { + property = '' + property + ''; + browser = 'IE7'; + } else if (value.match(/\!ie$/)) { + value = value.replace(/\!ie$/, '!ie'); + browser = 'IE7'; + } else if (property === '-ms-filter') { + property = '-ms-filter'; + browser = 'IE9'; + } else if (value.indexOf('progid:DXImageTransform.Microsoft') >= 0) { + value = value.replace(/progid:DXImageTransform\.Microsoft/, 'progid:DXImageTransform.Microsoft'); + browser = 'IE9'; + } - browser = browser ? '' + browser + ' fix: ' : ''; - var propertyAndValue = (property && value) ? ' {' + property + ': ' + value + '}' : ''; + var propertyAndValue = (property && value) ? ' {' + property + ': ' + value + '}' : ''; + splittedOffender.bolded = rule + propertyAndValue; + splittedOffender.browser = browser; - return browser + rule + propertyAndValue + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; - }); + return splittedOffender; + }) + }; } }, "cssOldPropertyPrefixes": { @@ -506,19 +644,32 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 50, "isAbnormalThreshold": 300, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - var parts = /^([^{]*)(?: ?{ ?([^ ]+): (.*) ?}) \/\/ (.*)$/.exec(splittedOffender.offender); + var parts = /^([^{]*)(?: ?{ ?([^ ]+): (.*) ?}) \/\/ (.*)$/.exec(splittedOffender.css); - if (!parts) { - debug('cssOldPropertyPrefixes offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('cssOldPropertyPrefixes offenders transform function error with "%s"', offender); + return { + parseError: offender + }; + } - return 'Universal selectors are the most expensive CSS selectors.
More informations here.
", "isOkThreshold": 0, "isBadThreshold": 40, - "isAbnormalThreshold": 150 + "isAbnormalThreshold": 150, + "hasOffenders": true, + "offendersTransformFn": function(offenders) { + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return splittedOffender; + }) + }; + } }, "cssRedundantBodySelectors": { "tool": "phantomas", @@ -536,15 +697,18 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 50, "isAbnormalThreshold": 200, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - var rule = splittedOffender.offender; - rule = rule.replace(/body/, 'body'); + splittedOffender.bolded = splittedOffender.css.replace(/body/, 'body'); - return rule + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; - }); + return splittedOffender; + }) + }; } }, "cssRedundantChildNodesSelectors": { @@ -554,25 +718,31 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 50, "isAbnormalThreshold": 200, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); - var rule = splittedOffender.offender; - var redundanters = [ - ['ul', 'li'], - ['ol', 'li'], - ['select', 'option'], - ['table', 'tr'], - ['table', 'th'], - ]; + var rule = splittedOffender.css; + var redundanters = [ + ['ul', 'li'], + ['ol', 'li'], + ['select', 'option'], + ['table', 'tr'], + ['table', 'th'], + ]; - redundanters.forEach(function(couple) { - rule = rule.replace(new RegExp('(^| |>)' + couple[0] + '([^ >]*)?([ >]| > )' + couple[1] + '([^\\w-]|$)', 'g'), '$1' + couple[0] + '$2$3' + couple[1] + '$4'); - }); + redundanters.forEach(function(couple) { + rule = rule.replace(new RegExp('(^| |>)' + couple[0] + '([^ >]*)?([ >]| > )' + couple[1] + '([^\\w-]|$)', 'g'), '$1' + couple[0] + '$2$3' + couple[1] + '$4'); + }); - return rule + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; - }); + splittedOffender.bolded = rule; + + return splittedOffender; + }) + }; } }, "requests": { @@ -582,16 +752,18 @@ var policies = { "isOkThreshold": 15, "isBadThreshold": 100, "isAbnormalThreshold": 200, + "hasOffenders": true, "takeOffendersFrom": ["htmlCount", "jsCount", "cssCount", "imageCount", "webfontCount", "videoCount", "jsonCount", "jsonCount"], "offendersTransformFn": function(offenders) { - return offenders - .map(function(offender) { - return offendersHelpers.fileWithSizePattern(offender); - }).sort(function(a, b) { - return b.size - a.size; - }).map(function(fileObj) { - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders + .map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }).sort(function(a, b) { + return b.size - a.size; + }) + }; } }, "htmlCount": { @@ -601,11 +773,14 @@ var policies = { "isOkThreshold": 10, "isBadThreshold": 20, "isAbnormalThreshold": 30, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var fileObj = offendersHelpers.fileWithSizePattern(offender); - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }) + }; } }, "jsCount": { @@ -615,11 +790,14 @@ var policies = { "isOkThreshold": 5, "isBadThreshold": 15, "isAbnormalThreshold": 30, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var fileObj = offendersHelpers.fileWithSizePattern(offender); - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }) + }; } }, "cssCount": { @@ -629,11 +807,14 @@ var policies = { "isOkThreshold": 3, "isBadThreshold": 10, "isAbnormalThreshold": 22, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var fileObj = offendersHelpers.fileWithSizePattern(offender); - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }) + }; } }, "imageCount": { @@ -643,11 +824,14 @@ var policies = { "isOkThreshold": 15, "isBadThreshold": 40, "isAbnormalThreshold": 70, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var fileObj = offendersHelpers.fileWithSizePattern(offender); - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }) + }; } }, "webfontCount": { @@ -657,11 +841,14 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 3, "isAbnormalThreshold": 5, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var fileObj = offendersHelpers.fileWithSizePattern(offender); - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }) + }; } }, "videoCount": { @@ -671,11 +858,14 @@ var policies = { "isOkThreshold": 1, "isBadThreshold": 5, "isAbnormalThreshold": 15, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var fileObj = offendersHelpers.fileWithSizePattern(offender); - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }) + }; } }, "jsonCount": { @@ -685,11 +875,14 @@ var policies = { "isOkThreshold": 2, "isBadThreshold": 10, "isAbnormalThreshold": 25, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var fileObj = offendersHelpers.fileWithSizePattern(offender); - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }) + }; } }, "otherCount": { @@ -699,11 +892,14 @@ var policies = { "isOkThreshold": 5, "isBadThreshold": 20, "isAbnormalThreshold": 40, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var fileObj = offendersHelpers.fileWithSizePattern(offender); - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }) + }; } }, "smallJsFiles": { @@ -713,11 +909,14 @@ var policies = { "isOkThreshold": 2, "isBadThreshold": 10, "isAbnormalThreshold": 16, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var fileObj = offendersHelpers.fileWithSizePattern(offender); - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }) + }; } }, "smallCssFiles": { @@ -727,11 +926,14 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 8, "isAbnormalThreshold": 12, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var fileObj = offendersHelpers.fileWithSizePattern(offender); - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }) + }; } }, "smallImages": { @@ -741,11 +943,14 @@ var policies = { "isOkThreshold": 2, "isBadThreshold": 17, "isAbnormalThreshold": 30, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var fileObj = offendersHelpers.fileWithSizePattern(offender); - return offendersHelpers.urlToLink(fileObj.file) + ((fileObj.size !== undefined) ? ' (' + fileObj.size + ' kB)' : ''); - }); + return { + count: offenders.length, + list: offenders.map(function(offender) { + return offendersHelpers.fileWithSizePattern(offender); + }) + }; } }, "notFound": { @@ -755,11 +960,7 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 1, "isAbnormalThreshold": 1, - "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - return offendersHelpers.urlToLink(offender); - }); - } + "hasOffenders": true }, "closedConnections": { "tool": "phantomas", @@ -768,11 +969,7 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 8, "isAbnormalThreshold": 20, - "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - return offendersHelpers.urlToLink(offender); - }); - } + "hasOffenders": true }, "multipleRequests": { "tool": "phantomas", @@ -781,11 +978,7 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 3, "isAbnormalThreshold": 10, - "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - return offendersHelpers.urlToLink(offender); - }); - } + "hasOffenders": true }, "cachingDisabled": { "tool": "phantomas", @@ -794,11 +987,7 @@ var policies = { "isOkThreshold": 0, "isBadThreshold": 12, "isAbnormalThreshold": 25, - "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - return offendersHelpers.urlToLink(offender); - }); - } + "hasOffenders": true }, "cachingNotSpecified": { "tool": "phantomas", @@ -807,11 +996,7 @@ var policies = { "isOkThreshold": 5, "isBadThreshold": 20, "isAbnormalThreshold": 40, - "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - return offendersHelpers.urlToLink(offender); - }); - } + "hasOffenders": true }, "cachingTooShort": { "tool": "phantomas", @@ -820,45 +1005,56 @@ var policies = { "isOkThreshold": 5, "isBadThreshold": 20, "isAbnormalThreshold": 40, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders - .map(function(offender) { - var parts = /^([^ ]*) cached for (-?\d+(\.\d+)?) s$/.exec(offender); + return { + count: offenders.length, + list: offenders + .map(function(offender) { + var parts = /^([^ ]*) cached for (-?\d+(\.\d+)?) s$/.exec(offender); + + if (!parts) { + debug('cachingTooShort offenders transform function error with "%s"', offender); + return { + file: offender + }; + } - if (!parts) { - debug('cachingTooShort offenders transform function error with "%s"', offender); return { - file: offender + file: parts[1], + ttl: Math.round(parseFloat(parts[2])) }; - } - return { - file: parts[1], - ttl: Math.round(parseFloat(parts[2])) - }; - }).sort(function(a, b) { - return a.ttl - b.ttl; - }).map(function(obj) { - var duration = obj.ttl; - var unit = 'seconds'; + }).sort(function(a, b) { - if (duration >= 120) { - duration = Math.round(duration / 60); - unit = 'minutes'; - } + return a.ttl - b.ttl; - if (duration >= 120) { - duration = Math.round(duration / 60); - unit = 'hours'; - } + }).map(function(obj) { - if (duration >= 48) { - duration = Math.round(duration / 24); - unit = 'days'; - } + var duration = obj.ttl; + var unit = 'seconds'; - return offendersHelpers.urlToLink(obj.file) + ' cached for ' + duration + ' ' + unit + ''; - }); + if (duration >= 120) { + duration = Math.round(duration / 60); + unit = 'minutes'; + } + + if (duration >= 120) { + duration = Math.round(duration / 60); + unit = 'hours'; + } + + if (duration >= 48) { + duration = Math.round(duration / 24); + unit = 'days'; + } + + obj.ttlWithUnit = duration; + obj.unit = unit; + + return obj; + }) + }; } }, "domains": { @@ -868,17 +1064,27 @@ var policies = { "isOkThreshold": 10, "isBadThreshold": 25, "isAbnormalThreshold": 50, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.map(function(offender) { - var parts = /^([^ ]*): (\d+) request\(s\)$/.exec(offender); + return { + count: offenders.length, + list: offenders.map(function(offender) { + var parts = /^([^ ]*): (\d+) request\(s\)$/.exec(offender); - if (!parts) { - debug('domains offenders transform function error with "%s"', offender); - return offender; - } + if (!parts) { + debug('domains offenders transform function error with "%s"', offender); + return { + file: offender + }; + } - return '' + parts[1] + ' (' + parts[2] + ' ' + ((parseInt(parts[2], 10) > 1) ? 'requests' : 'request') + ')'; - }); + return { + domain: parts[1], + requests: parseInt(parts[2]) + }; + + }) + }; } } }; diff --git a/lib/offendersHelpers.js b/lib/offendersHelpers.js index 36151c6..f2dde54 100644 --- a/lib/offendersHelpers.js +++ b/lib/offendersHelpers.js @@ -31,70 +31,69 @@ var OffendersHelpers = function() { return result; }; - this.domTreeToHTML = function(domTree) { - - function recursiveHtmlBuilder(tree) { - var html = ''; - var keys = Object.keys(tree); - - keys.forEach(function(key) { - if (isNaN(tree[key])) { - html += 'A deep DOM makes the CSS matching with DOM elements difficult.
It also slows down JavaScript modifications to the DOM because changing the dimensions of an element makes the browser re-calculate the dimensions of it's parents. Same thing for JavaScript events, that bubble up to the document root.
", "isOkThreshold": 10, "isBadThreshold": 20, - "isAbnormalThreshold": 28 + "isAbnormalThreshold": 28, + "hasOffenders": true }, "value": 1, "bad": false, "abnormal": false, "score": 100, "abnormalityScore": 0, - "offenders": "This is from another tool!
", "isOkThreshold": 0, "isBadThreshold": 3, - "isAbnormalThreshold": 11 + "isAbnormalThreshold": 11, + "hasOffenders": false }, "value": 22, "bad": true, diff --git a/test/fixtures/rulesCheckerPolicies.js b/test/fixtures/rulesCheckerPolicies.js index a4fecab..2391781 100644 --- a/test/fixtures/rulesCheckerPolicies.js +++ b/test/fixtures/rulesCheckerPolicies.js @@ -6,7 +6,8 @@ var policies = { "message": "A great message", "isOkThreshold": 1000, "isBadThreshold": 3000, - "isAbnormalThreshold": 5000 + "isAbnormalThreshold": 5000, + "hasOffenders": false }, "metric2": { "tool": "tool1", @@ -16,8 +17,12 @@ var policies = { "isBadThreshold": 3000, "isAbnormalThreshold": 5000, "takeOffendersFrom": "metric3", + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.join(' - '); + return { + count: 2, + str: offenders.join(' - ') + }; } }, "metric3": { @@ -27,8 +32,12 @@ var policies = { "isOkThreshold": 1000, "isBadThreshold": 3000, "isAbnormalThreshold": 5000, + "hasOffenders": true, "offendersTransformFn": function(offenders) { - return offenders.join('/'); + return { + count: 2, + test: offenders.join('/') + }; } }, "metric4": { @@ -37,7 +46,8 @@ var policies = { "message": "A great message", "isOkThreshold": 1000, "isBadThreshold": 3000, - "isAbnormalThreshold": 5000 + "isAbnormalThreshold": 5000, + "hasOffenders": true, }, "metric5": { "tool": "tool1", @@ -46,6 +56,7 @@ var policies = { "isOkThreshold": 1000, "isBadThreshold": 3000, "isAbnormalThreshold": 5000, + "hasOffenders": true, "takeOffendersFrom": ["metric3", "metric4"] }, "metric6": { @@ -71,7 +82,8 @@ var policies = { "message": "This is from another tool!
", "isOkThreshold": 0, "isBadThreshold": 3, - "isAbnormalThreshold": 11 + "isAbnormalThreshold": 11, + "hasOffenders": false, }, "unexistantMetric": { @@ -80,13 +92,15 @@ var policies = { "message": "", "isOkThreshold": 1000, "isBadThreshold": 3000, - "isAbnormalThreshold": 5000 + "isAbnormalThreshold": 5000, + "hasOffenders": true }, "unexistantTool": { "tool": "unexistant", "isOkThreshold": 1000, "isBadThreshold": 3000, - "isAbnormalThreshold": 5000 + "isAbnormalThreshold": 5000, + "hasOffenders": false } };