Skip to content

Commit 05dbb82

Browse files
committed
Merge pull request #96 from L0stSoul/dev
Option: vendor-prefix-align (close #17)
2 parents 555a6eb + 7ee9c2d commit 05dbb82

14 files changed

+368
-16
lines changed

.csscomb.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"stick-brace": "\n",
1818
"strip-spaces": true,
1919
"unitless-zero": true,
20+
"vendor-prefix-align": true,
2021
"sort-order": [
2122
[
2223
"font",

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,30 @@ img { border: 0px }
404404
img { border: 0 }
405405
```
406406
407+
### vendor-prefix-align
408+
409+
Available value: `{Boolean}` `true`
410+
411+
Example: `{ "vendor-prefix-align": true }`
412+
413+
```css
414+
/* before */
415+
a
416+
{
417+
-webkit-border-radius: 3px;
418+
-moz-border-radius: 3px;
419+
border-radius: 3px;
420+
}
421+
422+
/* after */
423+
a
424+
{
425+
-webkit-border-radius: 3px;
426+
-moz-border-radius: 3px;
427+
border-radius: 3px;
428+
}
429+
```
430+
407431
## Tests
408432
409433
Run `npm test` for tests.

lib/csscomb.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ var Comb = function() {
2626
'rule-indent',
2727
'block-indent',
2828
'unitless-zero',
29-
'sort-order'
29+
'sort-order',
30+
'vendor-prefix-align'
3031
];
3132
this._exclude = null;
3233
};
@@ -70,7 +71,13 @@ Comb.prototype = {
7071
* @returns {Array}
7172
*/
7273
processTree: function(tree) {
73-
this.processNode(['tree', tree], 0);
74+
75+
// We walk across complete tree for each handler,
76+
// because we need strictly maintain order in which handlers work,
77+
// despite fact that handlers work on different level of the tree.
78+
this._handlers.forEach(function(handler) {
79+
this.processNode(['tree', tree], 0, handler);
80+
}, this);
7481
return tree;
7582
},
7683

@@ -79,19 +86,17 @@ Comb.prototype = {
7986
* @param {Array} node Tree node
8087
* @param {Number} level Indent level
8188
*/
82-
processNode: function(node, level) {
89+
processNode: function(node, level, handler) {
8390
node.forEach(function(node) {
8491
if (!Array.isArray(node)) return;
8592

8693
var nodeType = node.shift();
87-
this._handlers.forEach(function(handler) {
88-
handler.process(nodeType, node, level);
89-
});
94+
handler.process(nodeType, node, level);
9095
node.unshift(nodeType);
9196

9297
if (nodeType === 'atrulers') level++;
9398

94-
this.processNode(node, level);
99+
this.processNode(node, level, handler);
95100
}, this);
96101
},
97102

lib/options/vendor-prefix-align.js

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
module.exports = {
2+
3+
/**
4+
* Internal
5+
*
6+
* Containt vendor-prefixes list.
7+
*/
8+
_prefixesList: [
9+
'webkit',
10+
'moz',
11+
'ms',
12+
'o'
13+
],
14+
15+
/**
16+
* Internal
17+
*
18+
* Create object which contains info about vendor prefix used in propertyName.
19+
* @param {String} propertyName property name
20+
* @returns {Object|undefined}
21+
*/
22+
_getPrefixInfo: function(propertyName) {
23+
if (!propertyName) return;
24+
25+
var result = { baseName: propertyName, prefixLength: 0 };
26+
27+
this._prefixesList.some(function(prefix) {
28+
prefix = '-' + prefix + '-';
29+
if (propertyName.indexOf(prefix) !== 0) return;
30+
result = {
31+
baseName: propertyName.substr(prefix.length),
32+
prefixLength: prefix.length
33+
};
34+
return true;
35+
});
36+
37+
return result;
38+
},
39+
40+
/**
41+
* Internal
42+
*
43+
* Walk across nodes, and call payload for every node that pass selector check.
44+
* @param {node} node
45+
* @param {function} selector
46+
* @param {function} payload
47+
*/
48+
_walk: function(node, selector, payload) {
49+
node.forEach(function(item, i) {
50+
var info = this._getPrefixInfo(selector(item));
51+
if (!info) return;
52+
payload(info, i);
53+
}, this);
54+
},
55+
56+
/**
57+
* Internal
58+
*
59+
* Selector for property name.
60+
* @param {node} item
61+
* @returns {String|false|undefined}
62+
*/
63+
_declName: function(item) {
64+
return item[0] === 'declaration' && item[1][1][1];
65+
},
66+
67+
/**
68+
* Internal
69+
*
70+
* Selector for value name.
71+
* @param {node} item
72+
* @returns {String|false|undefined}
73+
*/
74+
_valName: function(item) {
75+
return item[0] === 'declaration' && item[2] && item[2][2] &&
76+
item[2][2][0] === 'funktion' && item[2][2][1][0] === 'ident' &&
77+
item[2][2][1][1];
78+
},
79+
80+
/**
81+
* Internal
82+
*
83+
* Update dict which contains info about items align.
84+
* @param {Object} info,
85+
* @param {Object} dict,
86+
* @param {String} whitespaceNode
87+
*/
88+
_updateDict: function(info, dict, whitespaceNode) {
89+
if (info.prefixLength === 0) return;
90+
91+
var indent = dict[info.baseName] || { prefixLength: 0, baseLength: 0 };
92+
93+
dict[info.baseName] = indent.prefixLength > info.prefixLength ?
94+
indent :
95+
{
96+
prefixLength: info.prefixLength,
97+
baseLength: whitespaceNode.substr(whitespaceNode.lastIndexOf('\n') + 1).length
98+
};
99+
},
100+
101+
/**
102+
* Return string with correct number of spaces for info.baseName property.
103+
* @param {Object} info,
104+
* @param {Object} dict,
105+
* @param {String} whitespaceNode
106+
* @returns {String}
107+
*/
108+
_updateIndent: function(info, dict, whitespaceNode) {
109+
if (!dict[info.baseName])
110+
return whitespaceNode;
111+
112+
var firstPart = whitespaceNode.substr(0, whitespaceNode.lastIndexOf('\n') + 1 );
113+
var extraIndent = new Array(
114+
dict[info.baseName].prefixLength -
115+
info.prefixLength +
116+
dict[info.baseName].baseLength + 1).join(' ');
117+
118+
return firstPart.concat(extraIndent);
119+
},
120+
121+
/**
122+
* Sets handler value.
123+
*
124+
* @param {Array} value Option value
125+
* @returns {Object|undefined}
126+
*/
127+
setValue: function(value) {
128+
return value ? this : undefined;
129+
},
130+
131+
/**
132+
* Processes tree node.
133+
* @param {String} nodeType
134+
* @param {node} node
135+
*/
136+
process: function(nodeType, node) {
137+
if (nodeType !== 'block') return;
138+
139+
var dict = {};
140+
var _this = this;
141+
142+
// Gathering Info
143+
this._walk(node, this._declName, function(info, i) {
144+
_this._updateDict(info, dict, node[i - 1][1]);
145+
});
146+
this._walk(node, this._valName, function(info, i) {
147+
_this._updateDict(info, dict, node[i][2][1][1]);
148+
});
149+
150+
// Update nodes
151+
this._walk(node, this._declName, function(info, i) {
152+
node[i - 1][1] = _this._updateIndent(info, dict, node[i - 1][1]);
153+
});
154+
this._walk(node, this._valName, function(info, i) {
155+
node[i][2][1][1] = _this._updateIndent(info, dict, node[i][2][1][1]);
156+
});
157+
}
158+
159+
};

test/integral.expect.css

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,28 @@
55
{
66
background: rgba(0,0,0,.4);
77
background: -webkit-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
8-
background: -moz-linear-gradient(top, rgba(0,0,0,.2) 0, rgba(0,0,0,.4) 100%);
9-
background: -o-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
10-
background: linear-gradient(to bottom, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
8+
background: -moz-linear-gradient(top, rgba(0,0,0,.2) 0, rgba(0,0,0,.4) 100%);
9+
background: -o-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
10+
background: linear-gradient(to bottom, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
1111
-moz-box-shadow: 0 1px 0 rgba(0,0,0,.07);
12-
box-shadow: 0 1px 0 rgba(0,0,0,.07);
12+
box-shadow: 0 1px 0 rgba(0,0,0,.07);
1313
}
1414

1515
/* :after — фон */
1616
.radio-button_theme_normal .radio-button__radio:after
1717
{
1818
background: #fff;
1919
background: -webkit-linear-gradient(top, #fff 0,#eee 100%);
20-
background: -moz-linear-gradient(top, #fff 0, #eee 100%);
21-
background: -o-linear-gradient(top, #fff 0,#eee 100%);
22-
background: linear-gradient(to bottom, #fff 0,#eee 100%);
20+
background: -moz-linear-gradient(top, #fff 0, #eee 100%);
21+
background: -o-linear-gradient(top, #fff 0,#eee 100%);
22+
background: linear-gradient(to bottom, #fff 0,#eee 100%);
2323
}
2424

2525
/* _focused_yes */
2626
.radio-button_theme_normal .radio-button__radio_focused_yes:before
2727
{
2828
-moz-box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
29-
box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
29+
box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
3030
}
3131
}
3232

@@ -85,7 +85,7 @@ div p em
8585
.input__control
8686
{
8787
-moz-box-sizing: border-box;
88-
box-sizing: border-box;
88+
box-sizing: border-box;
8989
padding: .4em 0;
9090

9191
border: 0;

test/vendor-prefix-align.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
var Comb = require('../lib/csscomb');
2+
var fs = require('fs');
3+
var assert = require('assert');
4+
5+
describe('options/vendor-prefix-align', function() {
6+
var comb;
7+
8+
beforeEach(function() {
9+
var config = {
10+
'vendor-prefix-align': true
11+
};
12+
13+
comb = new Comb();
14+
comb.configure(config);
15+
});
16+
17+
it('Should correctly align prefixes in properties', function() {
18+
var input = fs.readFileSync('./test/vendor-prefix-align/property-align.css', 'utf8');
19+
var expected = fs.readFileSync('./test/vendor-prefix-align/property-align.expected.css', 'utf8');
20+
21+
assert.equal(comb.processString(input), expected);
22+
});
23+
24+
it('Should correctly align prefixes in values', function() {
25+
var input = fs.readFileSync('./test/vendor-prefix-align/value-align.css', 'utf8');
26+
var expected = fs.readFileSync('./test/vendor-prefix-align/value-align.expected.css', 'utf8');
27+
28+
assert.equal(comb.processString(input), expected);
29+
});
30+
31+
it('Should not touch already align prefixes', function() {
32+
var input = fs.readFileSync('./test/vendor-prefix-align/already-aligned.css', 'utf8');
33+
var expected = fs.readFileSync('./test/vendor-prefix-align/already-aligned.expected.css', 'utf8');
34+
35+
assert.equal(comb.processString(input), expected);
36+
});
37+
38+
it('Should always correctly align prefixes', function() {
39+
var input = fs.readFileSync('./test/vendor-prefix-align/complex.css', 'utf8');
40+
var expected = fs.readFileSync('./test/vendor-prefix-align/complex.expected.css', 'utf8');
41+
42+
assert.equal(comb.processString(input), expected);
43+
});
44+
45+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.radio-button_theme_normal .radio-button__radio:before
2+
{
3+
background: rgba(0,0,0,.4);
4+
background: -webkit-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
5+
background: -moz-linear-gradient(top, rgba(0,0,0,.2) 0, rgba(0,0,0,.4) 100%);
6+
background: -o-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
7+
background: linear-gradient(to bottom, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
8+
9+
-moz-box-shadow: 0 1px 0 rgba(0,0,0,.07);
10+
box-shadow: 0 1px 0 rgba(0,0,0,.07);
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.radio-button_theme_normal .radio-button__radio:before
2+
{
3+
background: rgba(0,0,0,.4);
4+
background: -webkit-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
5+
background: -moz-linear-gradient(top, rgba(0,0,0,.2) 0, rgba(0,0,0,.4) 100%);
6+
background: -o-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
7+
background: linear-gradient(to bottom, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
8+
9+
-moz-box-shadow: 0 1px 0 rgba(0,0,0,.07);
10+
box-shadow: 0 1px 0 rgba(0,0,0,.07);
11+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
@media all and (min-width:0)
2+
{
3+
.radio-button_theme_normal .radio-button__radio:before
4+
{
5+
background: rgba(0,0,0,.4);
6+
background: -webkit-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
7+
background: -moz-linear-gradient(top, rgba(0,0,0,.2) 0, rgba(0,0,0,.4) 100%);
8+
background: -o-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
9+
background: linear-gradient(to bottom, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%);
10+
-moz-box-shadow: 0 1px 0 rgba(0,0,0,.07);
11+
box-shadow: 0 1px 0 rgba(0,0,0,.07);
12+
}
13+
14+
/* :after — фон */
15+
.radio-button_theme_normal .radio-button__radio:after
16+
{
17+
background: #fff;
18+
background: -webkit-linear-gradient(top, #fff 0,#eee 100%);
19+
background: -moz-linear-gradient(top, #fff 0, #eee 100%);
20+
background: -o-linear-gradient(top, #fff 0,#eee 100%);
21+
background: linear-gradient(to bottom, #fff 0,#eee 100%);
22+
}
23+
24+
/* _focused_yes */
25+
.radio-button_theme_normal .radio-button__radio_focused_yes:before
26+
{
27+
-moz-box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
28+
box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07);
29+
}
30+
}

0 commit comments

Comments
 (0)