UNPKG

2.82 kBJavaScriptView Raw
1'use strict';
2
3var collection = require('../lib/collection'),
4 debug = require('debug')('analyze-css:duplicated'),
5 format = require('util').format;
6
7function rule(analyzer) {
8 var selectors = new collection(),
9 mediaQueryStack = [],
10 browserPrefixRegEx = /^-(moz|o|webkit|ms)-/;
11
12 analyzer.setMetric('duplicatedSelectors');
13 analyzer.setMetric('duplicatedProperties');
14
15 // handle nested media queries
16 analyzer.on('media', function(query) {
17 mediaQueryStack.push(query);
18 debug('push: %j', mediaQueryStack);
19 });
20
21 analyzer.on('mediaEnd', function(query) {
22 mediaQueryStack.pop(query);
23 debug('pop: %j', mediaQueryStack);
24 });
25
26 // register each rule's selectors
27 analyzer.on('rule', function(rule) {
28 selectors.push(
29 // @media foo
30 (mediaQueryStack.length > 0 ? '@media ' + mediaQueryStack.join(' @media ') + ' ' : '') +
31 // #foo
32 rule.selectors.join(', ')
33 );
34 });
35
36 // find duplicated properties (issue #60)
37 analyzer.on('rule', function(rule) {
38 var propertiesHash = {};
39
40 if (rule.declarations) {
41 rule.declarations.forEach(function(declaration) {
42 var propertyName;
43
44 if (declaration.type === 'declaration') {
45 propertyName = declaration.property;
46
47 // skip properties that require browser prefixes
48 // background-image:-moz-linear-gradient(...)
49 // background-image:-webkit-gradient(...)
50 if (browserPrefixRegEx.test(declaration.value) === true) {
51 return;
52 }
53
54 // property was already used in the current selector - report it
55 if (propertiesHash[propertyName] === true) {
56 // report the position of the offending property
57 analyzer.setCurrentPosition(declaration.position);
58
59 analyzer.incrMetric('duplicatedProperties');
60 analyzer.addOffender('duplicatedProperties', format('%s {%s: %s}', rule.selectors.join(', '), declaration.property, declaration.value));
61 } else {
62 // mark given property as defined in the context of the current selector
63 propertiesHash[propertyName] = true;
64 }
65 }
66 });
67 }
68 });
69
70 // special handling for @font-face (#52)
71 // include URL when detecting duplicates
72 analyzer.on('font-face', function(rule) {
73 rule.declarations.forEach(function(declaration) {
74 if (declaration.property === 'src') {
75 selectors.push('@font-face src: ' + declaration.value);
76
77 debug('special handling for @font-face, provided src: %s', declaration.value);
78 return false;
79 }
80 });
81 });
82
83 analyzer.on('report', function() {
84 analyzer.setCurrentPosition(undefined);
85
86 selectors.sort().forEach(function(selector, cnt) {
87 if (cnt > 1) {
88 analyzer.incrMetric('duplicatedSelectors');
89 analyzer.addOffender('duplicatedSelectors', format('%s (%d times)', selector, cnt));
90 }
91 });
92 });
93}
94
95rule.description = 'Reports duplicated CSS selectors and properties';
96module.exports = rule;