diff --git a/.csscomb.json b/.csscomb.json index 90b3c0d3..4aca5e9c 100644 --- a/.csscomb.json +++ b/.csscomb.json @@ -8,6 +8,7 @@ "colon-space": true, "color-case": "lower", "color-shorthand": true, + "combinator-space": true, "element-case": "lower", "leading-zero": false, "rule-indent": true, diff --git a/README.md b/README.md index c0f4c46c..dcffd14c 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,44 @@ b { color: #ffcc00 } b { color: #fc0 } ``` +### combinator-space + +Available values: + * `{Boolean}`: `true` sets one space, `false` removes the spaces. + * `{String}`: any combination of whitespaces. + * `{Array}` with two `{String}` values: for setting left and right whitespace. + +Example: `{ "combinator-space": true }` + +```css +/* before */ +a>b { color: red } + +/* after */ +a > b { color: red } +``` + +Example: `{ "combinator-space": "" }` + +```css +/* before */ +a > b { color: red } + +/* after */ +a>b { color: red } +``` + +Example: `{ "combinator-space": [" ", "\n"] }` + +```css +/* before */ +a>b { color: red } + +/* after */ +a > +b { color: red } +``` + ### element-case Available values: `{String}` `lower` or `upper` diff --git a/lib/csscomb.js b/lib/csscomb.js index c91d5d27..5ec11086 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -19,6 +19,7 @@ var Comb = function() { 'strip-spaces', 'stick-brace', 'colon-space', + 'combinator-space', 'rule-indent', 'block-indent', 'unitless-zero', diff --git a/lib/options/combinator-space.js b/lib/options/combinator-space.js new file mode 100644 index 00000000..3a51f2d3 --- /dev/null +++ b/lib/options/combinator-space.js @@ -0,0 +1,50 @@ +module.exports = { + + /** + * Sets handler value. + * + * @param {String|Boolean|Array} value Option value + * @returns {Object} + */ + setValue: function(value) { + this._value = false; + if (value === true) value = ' '; + if (value === false) value = ''; + if (typeof value === 'string' && value.match(/^[ \t\n]*$/)) { + this._value = [value, value]; + } + if (value.constructor === Array) this._value = value; + if (!this._value) return; + return this; + }, + + /** + * Processes tree node. + * @param {String} nodeType + * @param {node} node + */ + process: function(nodeType, node) { + if (nodeType === 'selector') { + for (var i = node.length; i--;) { + var subSelector = node[i]; + for (var j = subSelector.length; j--;) { + if (subSelector[j][0] === 'combinator') { + // Working with the whitespace after the combinator + if (subSelector[j + 1][0] === 's') { + subSelector[j + 1][1] = this._value[1]; + } else { + subSelector.splice(j + 1, 0, ['s', this._value[1]]); + } + // Working with the whitespace before the combinator + if (subSelector[j - 1][0] === 's') { + subSelector[j - 1][1] = this._value[0]; + } else { + subSelector.splice(j, 0, ['s', this._value[0]]); + } + } + } + } + } + } + +}; diff --git a/test/combinator-space.js b/test/combinator-space.js new file mode 100644 index 00000000..7c269413 --- /dev/null +++ b/test/combinator-space.js @@ -0,0 +1,157 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); + +describe('options/combinator-space', function() { + var comb; + beforeEach(function() { + comb = new Comb(); + }); + it('Invalid String should not change space around combinator', function() { + comb.configure({ 'combinator-space': 'foobar' }); + assert.equal( + comb.processString( + 'a >b { color: red }' + + 'a ~b { color: red }' + + 'a +b { color: red }' + ), + 'a >b { color: red }' + + 'a ~b { color: red }' + + 'a +b { color: red }' + ); + }); + it('True Boolean value should set space around combinator to one space', function() { + comb.configure({ 'combinator-space': true }); + assert.equal( + comb.processString( + 'a>b { color: red }' + + 'a> b { color: red }' + + 'a >b { color: red }' + + 'a+b { color: red }' + + 'a+ b { color: red }' + + 'a +b { color: red }' + + 'a~b { color: red }' + + 'a~ b { color: red }' + + 'a ~b { color: red }' + + 'a ~b+ c>d { color: red }' + ), + 'a > b { color: red }' + + 'a > b { color: red }' + + 'a > b { color: red }' + + 'a + b { color: red }' + + 'a + b { color: red }' + + 'a + b { color: red }' + + 'a ~ b { color: red }' + + 'a ~ b { color: red }' + + 'a ~ b { color: red }' + + 'a ~ b + c > d { color: red }' + ); + }); + it('False Boolean value should remove spaces around combinator', function() { + comb.configure({ 'combinator-space': false }); + assert.equal( + comb.processString( + 'a>b { color: red }' + + 'a> b { color: red }' + + 'a >b { color: red }' + + 'a+b { color: red }' + + 'a+ b { color: red }' + + 'a +b { color: red }' + + 'a~b { color: red }' + + 'a~ b { color: red }' + + 'a ~b { color: red }' + + 'a ~b+ c>d { color: red }' + ), + 'a>b { color: red }' + + 'a>b { color: red }' + + 'a>b { color: red }' + + 'a+b { color: red }' + + 'a+b { color: red }' + + 'a+b { color: red }' + + 'a~b { color: red }' + + 'a~b { color: red }' + + 'a~b { color: red }' + + 'a~b+c>d { color: red }' + ); + }); + it('String `` value should remove spaces around combinator', function() { + comb.configure({ 'combinator-space': '' }); + assert.equal( + comb.processString( + 'a>b { color: red }' + + 'a> b { color: red }' + + 'a >b { color: red }' + + 'a+b { color: red }' + + 'a+ b { color: red }' + + 'a +b { color: red }' + + 'a~b { color: red }' + + 'a~ b { color: red }' + + 'a ~b { color: red }' + + 'a ~b+ c>d { color: red }' + ), + 'a>b { color: red }' + + 'a>b { color: red }' + + 'a>b { color: red }' + + 'a+b { color: red }' + + 'a+b { color: red }' + + 'a+b { color: red }' + + 'a~b { color: red }' + + 'a~b { color: red }' + + 'a~b { color: red }' + + 'a~b+c>d { color: red }' + ); + }); + it('String ` ` value should set two spaces around combinator', function() { + comb.configure({ 'combinator-space': ' ' }); + assert.equal( + comb.processString( + 'a>b { color: red }' + + 'a> b { color: red }' + + 'a >b { color: red }' + + 'a+b { color: red }' + + 'a+ b { color: red }' + + 'a +b { color: red }' + + 'a~b { color: red }' + + 'a~ b { color: red }' + + 'a ~b { color: red }' + + 'a ~b+ c>d { color: red }' + ), + 'a > b { color: red }' + + 'a > b { color: red }' + + 'a > b { color: red }' + + 'a + b { color: red }' + + 'a + b { color: red }' + + 'a + b { color: red }' + + 'a ~ b { color: red }' + + 'a ~ b { color: red }' + + 'a ~ b { color: red }' + + 'a ~ b + c > d { color: red }' + ); + }); + it('Array value should set different spaces around combinator', function() { + comb.configure({ 'combinator-space': [' ', '\n'] }); + assert.equal( + comb.processString( + 'a>b { color: red }' + + 'a> b { color: red }' + + 'a >b { color: red }' + + 'a+b { color: red }' + + 'a+ b { color: red }' + + 'a +b { color: red }' + + 'a~b { color: red }' + + 'a~ b { color: red }' + + 'a ~b { color: red }' + + 'a ~b+ c>d { color: red }' + ), + 'a >\nb { color: red }' + + 'a >\nb { color: red }' + + 'a >\nb { color: red }' + + 'a +\nb { color: red }' + + 'a +\nb { color: red }' + + 'a +\nb { color: red }' + + 'a ~\nb { color: red }' + + 'a ~\nb { color: red }' + + 'a ~\nb { color: red }' + + 'a ~\nb +\nc >\nd { color: red }' + ); + }); +}); diff --git a/test/integral.expect.css b/test/integral.expect.css index d8cee83d..c3982c54 100644 --- a/test/integral.expect.css +++ b/test/integral.expect.css @@ -120,13 +120,13 @@ div padding: 0; } /* foo */ -div p +div ~ p { font-size: 1px; top: 0; } -div p em +div > p + em { /* upline comment*/ font-style: italic; diff --git a/test/integral.origin.css b/test/integral.origin.css index a8f1814b..ff6f1249 100644 --- a/test/integral.origin.css +++ b/test/integral.origin.css @@ -104,11 +104,11 @@ div { padding:0; margin:0; } -/* foo */ div p { +/* foo */ div~p { font-size:1px; top:0 } - div P EM { + div> P +EM { /* upline comment*/ font-style:italic;