diff --git a/config/csscomb.json b/config/csscomb.json index ecf675df..e19a8826 100644 --- a/config/csscomb.json +++ b/config/csscomb.json @@ -4,6 +4,7 @@ "node_modules/**" ], "always-semicolon": true, + "block-indent": " ", "color-case": "lower", "color-shorthand": true, "element-case": "lower", diff --git a/doc/options.md b/doc/options.md index 044c99e5..8ee60260 100644 --- a/doc/options.md +++ b/doc/options.md @@ -46,6 +46,60 @@ div { } ``` +## block-indent + +Set indent for code inside blocks, including media queries and nested rules. + +Acceptable values: + +* `{Number}` — number os whitespaces; +* `{String}` — string with whitespaces and tabs. Note that line breaks are not + allowed here. + +Example: `{ 'block-indent': 4 }` + +```scss +// Before: +a { +top: 0; + p { + color: tomato; +position: happy; + } +} + +// After: +a { + top: 0; + p { + color: tomato; + position: happy; + } + } +``` + +Example: `{ 'block-indent': '' }` + +```scss +// Before: +a { +top: 0; + p { + color: tomato; +position: happy; + } +} + +// After: +a { +top: 0; +p { +color: tomato; +position: happy; +} +} +``` + ## color-case Unify case of hexadecimal colors. diff --git a/lib/csscomb.js b/lib/csscomb.js index d9b6b778..60065b3e 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -16,6 +16,7 @@ var OPTIONS = [ 'strip-spaces', 'eof-newline', 'sort-order', + 'block-indent', 'unitless-zero', 'vendor-prefix-align' ]; diff --git a/lib/options/block-indent.js b/lib/options/block-indent.js new file mode 100644 index 00000000..2e8ffe12 --- /dev/null +++ b/lib/options/block-indent.js @@ -0,0 +1,77 @@ +module.exports = { + name: 'block-indent', + + accepts: { + number: true, + string: /^[ \t]*$/ + }, + + /** + * Processes tree node. + * + * @param {String} nodeType + * @param {node} node + * @param {Number} level + */ + process: function process(nodeType, node, level) { + var spaces; + + if (nodeType === 'stylesheet') { + for (var i = node.length; i--;) { + var whitespaceNode = node[i]; + + if (whitespaceNode[0] !== 's') continue; + + spaces = whitespaceNode[1].replace(/\n[ \t]+/gm, '\n'); + if (spaces === '') { + node.splice(i, 1); + } else { + whitespaceNode[1] = spaces; + } + } + return; + } + + // Continue only with space nodes inside {...}: + if (level === 0 || nodeType !== 's') return; + + // Remove all whitespaces and tabs, leave only new lines: + spaces = node[0].replace(/[ \t]/gm, ''); + + if (!spaces) return; + + spaces += new Array(level + 1).join(this.getValue('block-indent')); + node[0] = spaces; + }, + + /** + * Detects the value of an option at the tree node. + * + * @param {String} nodeType + * @param {node} node + * @param {Number} level + */ + detect: function(nodeType, node, level) { + var result = []; + + // Continue only with non-empty {...} blocks: + if (nodeType !== 'atrulers' && nodeType !== 'block' || !node.length) return; + + for (var i = node.length; i--;) { + var whitespaceNode = node[i]; + if (whitespaceNode[0] !== 's') continue; + + var spaces = whitespaceNode[1]; + var lastIndex = spaces.lastIndexOf('\n'); + + // Do not continue if there is no line break: + if (lastIndex < 0) continue; + + // Number of spaces from beginning of line: + var spacesLength = spaces.slice(lastIndex + 1).length; + result.push(new Array(spacesLength / (level + 1) + 1).join(' ')); + } + + return result; + } +}; diff --git a/test/options/block-indent.js b/test/options/block-indent.js new file mode 100644 index 00000000..bf6f478d --- /dev/null +++ b/test/options/block-indent.js @@ -0,0 +1,62 @@ +describe('options/block-indent:', function() { + beforeEach(function() { + this.filename = __filename; + }); + + it('Array value => should not change anything', function() { + this.comb.configure({ 'block-indent': ['', ' '] }); + this.shouldBeEqual('test.css'); + }); + + it('Invalid string value => should not change anything', function() { + this.comb.configure({ 'block-indent': ' nani ' }); + this.shouldBeEqual('test.css'); + }); + + it('Float number value => should not change anything', function() { + this.comb.configure({ 'block-indent': 3.5 }); + this.shouldBeEqual('test.css'); + }); + + it('Integer value => should set proper number of spaces', function() { + this.comb.configure({ 'block-indent': 0 }); + this.shouldBeEqual('test.css', 'test.expected.css'); + }); + + it('Valid string value => should set proper number of spaces', function() { + this.comb.configure({ 'block-indent': ' ' }); + this.shouldBeEqual('test.css', 'test-2.expected.css'); + }); + + it('Should detect nothing with an empty block, test 1', function() { + this.shouldDetect( + ['block-indent'], + 'a{ }', + {} + ); + }); + + it('Should detect nothing with an empty block, test 2', function() { + this.shouldDetect( + ['block-indent'], + 'a{}', + {} + ); + }); + + it('Should detect correct number of spaces', function() { + this.shouldDetect( + ['block-indent'], + 'a{\n top: 0;\n color: tomato;\n}', + { 'block-indent': ' ' } + ); + }); + + it('Should detect no indent for one-line code', function() { + this.shouldDetect( + ['block-indent'], + 'a{ top: 0; color: tomato; }', + {} + ); + }); +}); diff --git a/test/options/block-indent/test-2.expected.css b/test/options/block-indent/test-2.expected.css new file mode 100644 index 00000000..467a9326 --- /dev/null +++ b/test/options/block-indent/test-2.expected.css @@ -0,0 +1,30 @@ +a {color: tomato; top: 0;} + +a { color: tomato; + top: 0; } + +a { color: tomato; + top: 0; } + +a { + color: tomato; + top: 0; } + +a { + color: tomato; + top: 0; + } + +a { + color: tomato; + top: 0; + } + +@media print { a {color: tomato; top: 0; } } + +@media print { + a { + color: tomato; + top: 0; + } + } diff --git a/test/options/block-indent/test-3.expected.css b/test/options/block-indent/test-3.expected.css new file mode 100644 index 00000000..684ff987 --- /dev/null +++ b/test/options/block-indent/test-3.expected.css @@ -0,0 +1,36 @@ +a {color: tomato; top: 0; +} + +a { color: tomato; + top: 0; +} + +a { color: tomato; + top: 0; +} + +a { + color: tomato; + top: 0; +} + +a { + color: tomato; + top: 0; +} + +a { + color: tomato; + top: 0; +} + +@media print { a {color: tomato; top: 0; + } +} + +@media print { + a { + color: tomato; + top: 0; + } +} diff --git a/test/options/block-indent/test.css b/test/options/block-indent/test.css new file mode 100644 index 00000000..b1b52eb3 --- /dev/null +++ b/test/options/block-indent/test.css @@ -0,0 +1,30 @@ +a {color: tomato; top: 0;} + +a { color: tomato; +top: 0; } + +a { color: tomato; + top: 0; } + +a { +color: tomato; +top: 0; } + +a { +color: tomato; +top: 0; +} + + a { + color: tomato; + top: 0; + } + +@media print { a {color: tomato; top: 0; } } + + @media print { +a { + color: tomato; + top: 0; + } + } diff --git a/test/options/block-indent/test.expected.css b/test/options/block-indent/test.expected.css new file mode 100644 index 00000000..0c684ec8 --- /dev/null +++ b/test/options/block-indent/test.expected.css @@ -0,0 +1,30 @@ +a {color: tomato; top: 0;} + +a { color: tomato; +top: 0; } + +a { color: tomato; +top: 0; } + +a { +color: tomato; +top: 0; } + +a { +color: tomato; +top: 0; +} + +a { +color: tomato; +top: 0; +} + +@media print { a {color: tomato; top: 0; } } + +@media print { +a { +color: tomato; +top: 0; +} +}