New rule: similar colors

This commit is contained in:
Gaël Métais
2015-09-20 01:06:42 +02:00
parent bdbd1a0f08
commit d0257fea8f
9 changed files with 235 additions and 3 deletions
+11
View File
@@ -214,6 +214,17 @@
z-index: 2;
border: 0.2em solid #f1c40f;
}
.similarColors {
margin: 1em;
width: 20em;
height: 6em;
}
.similarColors > div {
display: inline-block;
width: 10em;
height: 3.5em;
padding-top: 2.5em;
}
.totalWeightPie {
max-width: 39em;
margin: 2em auto 4em;
+13
View File
@@ -234,6 +234,19 @@
}
}
.similarColors {
margin: 1em;
width: 20em;
height: 6em;
> div {
display: inline-block;
width: 10em;
height: 3.5em;
padding-top: 2.5em;
}
}
.totalWeightPie {
max-width: 39em;
margin: 2em auto 4em;
+4
View File
@@ -96,6 +96,10 @@
</span>
</div>
<div ng-if="policyName === 'similarColors'">
<div class="similarColors checker"><div ng-style="{'background-color': offender.color1, 'color': offender.isDark ? '#FFF' : '#000'}">{{offender.color1}}</div><div ng-style="{'background-color': offender.color2, 'color': offender.isDark ? '#FFF' : '#000'}">{{offender.color2}}</div></div>
</div>
<div ng-if="policyName === 'cssParsingErrors'">
<b>{{offender.error}}</b>
<file-and-line file="offender.file" line="offender.line" column="offender.column"></file-and-line>
+17 -2
View File
@@ -463,7 +463,7 @@ var policies = {
},
"cssColors": {
"tool": "phantomas",
"label": "Different colors",
"label": "Colors count",
"message": "<p>This is the number of different colors defined in CSS.</p><p>Your CSS project will be easier to maintain if you keep a small color set.</p>",
"isOkThreshold": 30,
"isBadThreshold": 150,
@@ -507,6 +507,21 @@ var policies = {
};
}
},
"similarColors": {
"tool": "colorDiff",
"label": "Similar colors",
"message": "<p>This is the list of colors found in the stylesheets, that are very close to each other. The eye can barely see the difference.</p><p>Use this list to reduce the number of colors in your palette, it will be easier to maintain.</p>",
"isOkThreshold": 0,
"isBadThreshold": 40,
"isAbnormalThreshold": 80,
"hasOffenders": true,
"offendersTransformFn": function(offenders) {
return {
count: offenders.length,
list: offenders
};
}
},
"cssImports": {
"tool": "phantomas",
"label": "Uses of @import",
@@ -970,7 +985,7 @@ var policies = {
"tool": "phantomas",
"label": "Hidden images",
"message": "<p>List of all images that have a display:none property, or one of their parents. These images are loaded by the browser even if they're not visible. You might be able to find a way to lazy-load them, only when they get visible.</p><p>Trackers are an exception, you'd better hide them.</p>",
"isOkThreshold": 3,
"isOkThreshold": 1,
"isBadThreshold": 12,
"isAbnormalThreshold": 30,
"hasOffenders": true
+2 -1
View File
@@ -70,7 +70,8 @@
"cssRules": 2,
"cssComplexSelectors": 2,
"cssComplexSelectorsByAttribute": 1.5,
"cssColors": 0.5
"cssColors": 0.5,
"similarColors": 0.5
}
},
"badCSS": {
+4
View File
@@ -3,6 +3,7 @@ var debug = require('debug')('ylt:runner');
var phantomasWrapper = require('./tools/phantomas/phantomasWrapper');
var jsExecutionTransformer = require('./tools/jsExecutionTransformer');
var colorDiff = require('./tools/colorDiff');
var weightChecker = require('./tools/weightChecker/weightChecker');
var rulesChecker = require('./rulesChecker');
var scoreCalculator = require('./scoreCalculator');
@@ -28,6 +29,9 @@ var Runner = function(params) {
// Treat the JS Execution Tree from offenders
data = jsExecutionTransformer.transform(data);
// Compare colors
data = colorDiff.compareAllColors(data);
// Redownload every file
return weightChecker.recheckAllFiles(data);
+90
View File
@@ -0,0 +1,90 @@
var debug = require('debug')('ylt:colorDiff');
var diff = require('color-diff');
var parse = require('parse-color');
var colorDiff = function() {
var COLOR_DIFF_THRESHOLD = 0.9;
this.compareAllColors = function(data) {
debug('Starting to compare all colors...');
var offenders = data.toolsResults.phantomas.offenders.cssColors;
var colors = (offenders) ? this.parseAllColors(offenders) : [];
var result = [];
// Compare colors to each other
for (var i = 0; i < colors.length - 1 ; i++) {
for (var j = i + 1; j < colors.length; j++) {
debug('Comparing color %s with color %s', colors[i].original, colors[j].original);
if (this.compareTwoColors(colors[i], colors[j])) {
debug('Colors are similar!');
result.push({
color1: colors[i].original,
color2: colors[j].original,
isDark: (colors[i].Lab.L < 60)
});
}
}
}
data.toolsResults.colorDiff = {
metrics: {
similarColors: result.length
},
offenders: {
similarColors: result
}
};
return data;
};
this.parseAllColors = function(offenders) {
var parsedOffenders = offenders.map(this.parseOffender);
var deduplicatedColors = {};
parsedOffenders.forEach(function(color) {
deduplicatedColors[color] = color;
});
return Object.keys(deduplicatedColors).map(this.parseColor);
};
this.parseOffender = function(offender) {
var regexResult = /^(.*) \(\d+ times\)$/.exec(offender);
return regexResult[1];
};
this.parseColor = function(color) {
var colorArray = parse(color).rgba;
var obj = {
R: colorArray[0],
G: colorArray[1],
B: colorArray[2],
A: colorArray[3]
};
obj.Lab = diff.rgb_to_lab(obj);
obj.original = color;
return obj;
};
this.compareTwoColors = function(color1, color2) {
if (color1.A !== color2.A) {
debug('Comparison not possible, because the alpha channel is different');
return false;
}
var delta = diff.diff(color1.Lab, color2.Lab);
debug('Delta is %d', delta);
return delta <= COLOR_DIFF_THRESHOLD;
};
};
module.exports = new colorDiff();
+2
View File
@@ -28,6 +28,7 @@
"body-parser": "1.13.3",
"chart.js": "1.0.2",
"clean-css": "3.4.2",
"color-diff": "0.1.7",
"compression": "1.5.2",
"cors": "2.7.1",
"debug": "2.2.0",
@@ -38,6 +39,7 @@
"lwip": "0.0.7",
"meow": "3.3.0",
"minimize": "1.7.1",
"parse-color": "1.0.0",
"phantomas": "1.12.0",
"ps-node": "0.0.4",
"q": "1.4.1",
+92
View File
@@ -0,0 +1,92 @@
var should = require('chai').should();
var colorDiff = require('../../lib/tools/colorDiff');
describe('colorDiff', function() {
it('should parse offenders correctly', function() {
colorDiff.parseOffender('#000 (1 times)').should.equal('#000');
colorDiff.parseOffender('#5bc0de (2 times)').should.equal('#5bc0de');
colorDiff.parseOffender('rgba(0,0,0,0.075) (100 times)').should.equal('rgba(0,0,0,0.075)');
colorDiff.parseOffender('rgb(91,192,222) (1000 times)').should.equal('rgb(91,192,222)');
});
it('should parse colors correctly', function() {
colorDiff.parseColor('#000').should.deep.equal({R: 0, G: 0, B: 0, A: 1, Lab: {L: 0, a: 0, b: 0}, original: '#000'});
colorDiff.parseColor('rgba(255,255,255,0.5)').should.deep.equal({R: 255, G: 255, B: 255, A: 0.5, Lab: {L: 100, a: 0.00526049995830391, b: -0.010408184525267927}, original: 'rgba(255,255,255,0.5)'});
});
it('should not compare colors with different alpha', function() {
var color1 = colorDiff.parseColor("rgba(0, 0, 0, 1)");
var color2 = colorDiff.parseColor("rgba(0, 0, 0, 0.5)");
colorDiff.compareTwoColors(color1, color2).should.equal(false);
});
it('should find that two colors are similar', function() {
var color1 = colorDiff.parseColor("rgba(0, 0, 0, 1)");
var color2 = colorDiff.parseColor("rgba(0, 0, 1, 1)");
colorDiff.compareTwoColors(color1, color2).should.equal(true);
});
it('should find that two colors are similar even with alpha', function() {
var color1 = colorDiff.parseColor("rgba(0, 0, 0, 0.3)");
var color2 = colorDiff.parseColor("rgba(0, 0, 2, 0.3)");
colorDiff.compareTwoColors(color1, color2).should.equal(true);
});
it('should find that two colors are different', function() {
var color1 = colorDiff.parseColor("rgba(0, 0, 0, 1)");
var color2 = colorDiff.parseColor("rgba(99, 99, 99, 1)");
colorDiff.compareTwoColors(color1, color2).should.equal(false);
});
it('should compare all colors to each other', function() {
var colors = [
'#000 (1 times)',
'#5bc0de (2 times)',
'rgba(0,0,0,0.075) (100 times)',
'rgb(91,192,222) (1000 times)',
'rgba(0,0,2,1) (1 times)',
'rgba(99,99,99,1) (1 times)',
'rgba(100,100,100,1) (1 times)'
];
var data = {
toolsResults: {
phantomas: {
offenders: {
cssColors: colors
}
}
}
};
newData = colorDiff.compareAllColors(data);
newData.toolsResults.should.have.a.property('colorDiff');
newData.toolsResults.colorDiff.should.have.a.property('metrics');
newData.toolsResults.colorDiff.metrics.should.have.a.property('similarColors').that.equals(3);
newData.toolsResults.colorDiff.should.have.a.property('offenders');
newData.toolsResults.colorDiff.offenders.should.have.a.property('similarColors').that.deep.equals([
{
color1: '#000',
color2: 'rgba(0,0,2,1)',
isDark: true
},
{
color1: '#5bc0de',
color2: 'rgb(91,192,222)',
isDark: false
},
{
color1: 'rgba(99,99,99,1)',
color2: 'rgba(100,100,100,1)',
isDark: true
}
]);
});
});