diff --git a/lib/metadata/policies.js b/lib/metadata/policies.js index f1c9d7d..82ce5b7 100644 --- a/lib/metadata/policies.js +++ b/lib/metadata/policies.js @@ -38,7 +38,7 @@ var policies = { "isAbnormalThreshold": 10, "offendersTransformFn": function(offenders) { return offenders.map(function(offender) { - var parts = /^(.*): ?(\d) ?occurrences$/.exec(offender); + var parts = /^(.*): ?(\d+) ?occurrences$/.exec(offender); if (!parts) { debug('DOMidDuplicated offenders transform function error with "%s"', offender); @@ -107,7 +107,7 @@ var policies = { "takeOffendersFrom": "DOMqueriesDuplicated", "offendersTransformFn": function(offenders) { return offenders.map(function(offender) { - var parts = /^.* "(.*)" ?with ?(.*) ?\(in ?context ?(.*)\): ?(.*)\s?queries$/.exec(offender); + var parts = /^[^"]* ?"(.*)" ?with ?(.*) ?\(in ?context ?(.*)\): ?(.*)\s?queries$/.exec(offender); if (!parts) { debug('DOMqueriesAvoidable offenders transform function error with "%s"', offender); @@ -265,7 +265,34 @@ var policies = { "message": "

Yellow Lab Tools failed to parse a CSS file. I doubt the problem comes from the css parser.

Maybe a CSS validator can help you.

", "isOkThreshold": 0, "isBadThreshold": 1, - "isAbnormalThreshold": 1 + "isAbnormalThreshold": 1, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var parts = /^(([^ \(]*|) ?)?(?:\((((?! @ ).)*)( @ (\d+):(\d+))?\))?$/.exec(offender); + + if (!parts) { + debug('cssParsingErrors offenders transform function error with "%s"', offender); + return offender; + } + + var html = (parts[3]) ? parts[3] : ''; + + if (parts[2]) { + html += (parts[2] === '') ? ' <inline CSS>' : ' ' + offendersHelpers.urlToLink(parts[2]); + } + + if (parts[5]) { + html += ' @ ' + parts[6] + ':' + parts[7]; + } + + if (parts[2] && parts[2] !== '') { + var w3cDirectUrl = 'http://jigsaw.w3.org/css-validator/validator?uri=' + encodeURIComponent(parts[2]) + '&profile=css3&usermedium=all&warning=no'; + html += ' (Check on the W3C validator)'; + } + + return html; + }); + } }, "cssRules": { "tool": "phantomas", @@ -281,7 +308,14 @@ var policies = { "message": "

Complex selectors are CSS selectors with 4 or more expressions, like \"#header ul li .foo\".

They are adding more work for the browser, and this could be avoided by simplifying selectors.

", "isOkThreshold": 0, "isBadThreshold": 500, - "isAbnormalThreshold": 2000 + "isAbnormalThreshold": 2000, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + + return '' + splittedOffender.offender + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; + }); + } }, "cssComplexSelectorsByAttribute": { "tool": "phantomas", @@ -289,7 +323,16 @@ var policies = { "message": "

Complex attributes selectors are one of these:

  • .foo[type*=bar] (contains bar)
  • .foo[type^=bar] (starts with bar)
  • .foo[type|=bar] (starts with bar or bar-)
  • .foo[type$=bar] (ends with bar)
  • .foo[type~=bar baz] (bar or baz)

Their matching process needs more CPU and it has a cost on performances.

", "isOkThreshold": 0, "isBadThreshold": 50, - "isAbnormalThreshold": 100 + "isAbnormalThreshold": 100, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + + var boldedOffender = splittedOffender.offender.replace(/(\[[^ ]+[~\|\^\$\*]="[^"]+"\])/g, '$1'); + + return boldedOffender + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; + }); + } }, "cssImports": { "tool": "phantomas", @@ -297,7 +340,14 @@ var policies = { "message": "

It’s bad for performance to use @import because CSS files don't get downloaded in parallel.

You should use <link rel='stylesheet' href='a.css'> instead.

", "isOkThreshold": 0, "isBadThreshold": 1, - "isAbnormalThreshold": 1 + "isAbnormalThreshold": 1, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + + return '' + splittedOffender.offender + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; + }); + } }, "cssDuplicatedSelectors": { "tool": "phantomas", @@ -305,7 +355,19 @@ var policies = { "message": "

This is when two or more selectors are strictly identical and should be merged.

", "isOkThreshold": 0, "isBadThreshold": 40, - "isAbnormalThreshold": 80 + "isAbnormalThreshold": 80, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var parts = /^(.*) \((\d+) times\)$/.exec(offender); + + if (!parts) { + debug('cssDuplicatedSelectors offenders transform function error with "%s"', offender); + return offender; + } + + return parts[1] + ' (x' + parts[2] + ')'; + }); + } }, "cssDuplicatedProperties": { "tool": "phantomas", @@ -313,7 +375,21 @@ var policies = { "message": "

This is the number of property definitions duplicated within a selector.

", "isOkThreshold": 0, "isBadThreshold": 50, - "isAbnormalThreshold": 100 + "isAbnormalThreshold": 100, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + + var parts = /^([^{]+) {([^ ]+): (.+)}$/.exec(splittedOffender.offender); + + if (!parts) { + debug('cssDuplicatedProperties offenders transform function error with "%s"', offender); + return offender; + } + + return 'Property ' + parts[2] + ' duplicated in ' + parts[1] + ' { } @ ' + splittedOffender.line + ':' + splittedOffender.character; + }); + } }, "cssEmptyRules": { "tool": "phantomas", @@ -321,7 +397,13 @@ var policies = { "message": "

Very easy to fix: remove all empty rules.

", "isOkThreshold": 0, "isBadThreshold": 40, - "isAbnormalThreshold": 100 + "isAbnormalThreshold": 100, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + return '' + splittedOffender.offender + ' { } @ ' + splittedOffender.line + ':' + splittedOffender.character; + }); + } }, "cssExpressions": { "tool": "phantomas", @@ -329,7 +411,21 @@ var policies = { "message": "

Such as: expression( document.body.clientWidth > 600 ? \"600px\" : \"auto\" )

This is a bad practice as it slows down browsers. There are some simpler CSS3 methods for doing this.

", "isOkThreshold": 0, "isBadThreshold": 1, - "isAbnormalThreshold": 20 + "isAbnormalThreshold": 20, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + + var parts = /^(.*) {([^ ]+): expression\((.*)\)}$/.exec(splittedOffender.offender); + + if (!parts) { + debug('cssExpressions offenders transform function error with "%s"', offender); + return offender; + } + + return parts[1] + ' {' + parts[2] + ': expression(' + parts[3] + ')} @ ' + splittedOffender.line + ':' + splittedOffender.character; + }); + } }, "cssImportants": { "tool": "phantomas", @@ -337,7 +433,21 @@ var policies = { "message": "

It can be useful, but only as a last resort. It is a bad practice because it overrides the normal cascading logic. The more you use !important, the more you need it again to over-override. This conducts to a poor maintainability.

", "isOkThreshold": 0, "isBadThreshold": 50, - "isAbnormalThreshold": 150 + "isAbnormalThreshold": 150, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + + var parts = /^(.*) {([^ ]+): (.*) ?\!important}$/.exec(splittedOffender.offender); + + if (!parts) { + debug('cssImportants offenders transform function error with "%s"', offender); + return offender; + } + + return parts[1] + ' {' + parts[2] + ': ' + parts[3] + ' !important} @ ' + splittedOffender.line + ':' + splittedOffender.character; + }); + } }, "cssOldIEFixes": { "tool": "phantomas", @@ -345,7 +455,49 @@ var policies = { "message": "

What browser do you need to support? Once you've got the answer, take a look at these old rules that pollute your CSS code and remove them.

IE6:

  • * html
  • html > body (everything but IE6)

IE7:

  • *height: 123px;
  • height: 123px !ie;

IE9:

  • -ms-filter
  • progid:DXImageTransform.Microsoft

", "isOkThreshold": 0, "isBadThreshold": 50, - "isAbnormalThreshold": 300 + "isAbnormalThreshold": 300, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + + var parts = /^([^{]*)( {([^ ]+): (.*)})?$/.exec(splittedOffender.offender); + + if (!parts) { + debug('cssOldIEFixes offenders transform function error with "%s"', offender); + return offender; + } + + 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'; + } + + browser = browser ? '' + browser + ' fix: ' : ''; + var propertyAndValue = (property && value) ? ' {' + property + ': ' + value + '}' : ''; + + return browser + rule + propertyAndValue + ' @ ' + splittedOffender.line + ':' + splittedOffender.character; + }); + } }, "cssOldPropertyPrefixes": { "tool": "phantomas", @@ -353,7 +505,21 @@ var policies = { "message": "

Many property prefixes such as -moz- or -webkit- are not needed anymore, or by very few people. You can remove them or replace them with the non-prefixed version. This will help reducing your stylesheets weight.

", "isOkThreshold": 0, "isBadThreshold": 50, - "isAbnormalThreshold": 300 + "isAbnormalThreshold": 300, + "offendersTransformFn": function(offenders) { + return offenders.map(function(offender) { + var splittedOffender = offendersHelpers.cssOffenderPattern(offender); + + var parts = /^([^{]*)(?: { ([^ ]+): (.*) }) \/\/ (.*)$/.exec(splittedOffender.offender); + + if (!parts) { + debug('cssOldPropertyPrefixes offenders transform function error with "%s"', offender); + return offender; + } + + return '
' + parts[1] + ' {' + parts[2] + ': ' + parts[3] + '} @ ' + splittedOffender.line + ':' + splittedOffender.character + '
' + parts[4] + '
'; + }); + } }, "cssUniversalSelectors": { "tool": "phantomas", diff --git a/lib/offendersHelpers.js b/lib/offendersHelpers.js index 39a048f..66b11f3 100644 --- a/lib/offendersHelpers.js +++ b/lib/offendersHelpers.js @@ -136,9 +136,10 @@ var OffendersHelpers = function() { } var html = '
backtrace
'; + var that = this; backtraceArray.forEach(function(backtraceObj) { var functionName = (backtraceObj.functionName) ? backtraceObj.functionName + '() ' : ''; - html += '
' + functionName + '' + backtraceObj.file + ' line ' + backtraceObj.line + '
'; + html += '
' + functionName + that.urlToLink(backtraceObj.file) + ' line ' + backtraceObj.line + '
'; }); return html + '
'; }; @@ -150,6 +151,27 @@ var OffendersHelpers = function() { }); }; + this.urlToLink = function(url) { + var shortUrl = (url.length > 110) ? url.substr(0, 47) + ' ... ' + url.substr(-48) : url; + return '' + shortUrl + ''; + }; + + this.cssOffenderPattern = function(offender) { + var parts = /^(.*) @ (\d+):(\d+)$/.exec(offender); + + if (!parts) { + return { + offender: offender + }; + } else { + return { + offender: parts[1], + line: parseInt(parts[2], 10), + character: parseInt(parts[3], 10) + }; + } + }; + }; module.exports = new OffendersHelpers(); \ No newline at end of file diff --git a/test/core/offendersHelpersTest.js b/test/core/offendersHelpersTest.js index ce23d07..072d2d1 100644 --- a/test/core/offendersHelpersTest.js +++ b/test/core/offendersHelpersTest.js @@ -175,7 +175,7 @@ describe('offendersHelpers', function() { } ]); - result.should.equal(''); + result.should.equal(''); }); it('should display "no backtrace"', function() { @@ -228,4 +228,43 @@ describe('offendersHelpers', function() { }); + describe('urlToLink', function() { + + it('should transform an url into an html link', function() { + var result = offendersHelpers.urlToLink('http://www.google.com/js/main.js'); + + result.should.equal('http://www.google.com/js/main.js'); + }); + + it('should ellypsis the url if too long', function() { + var result = offendersHelpers.urlToLink('http://www.google.com/js/longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong/main.js'); + + result.should.equal('http://www.google.com/js/longlonglonglonglonglo ... longlonglonglonglonglonglonglonglonglong/main.js'); + }); + + }); + + + describe('cssOffenderPattern', function() { + + it('should transform a css offender into an object', function() { + var result = offendersHelpers.cssOffenderPattern('.pagination .plus ul li @ 30:31862'); + + result.should.deep.equal({ + offender: '.pagination .plus ul li', + line: 30, + character: 31862 + }); + }); + + it('should handle the case where line and char are not here', function() { + var result = offendersHelpers.cssOffenderPattern('.pagination .plus ul li'); + + result.should.deep.equal({ + offender: '.pagination .plus ul li' + }); + }); + + }); + });