From bb61aa3f1ca000fa82ffa57d82a42d43807cdccc Mon Sep 17 00:00:00 2001 From: Anton Rudeshko Date: Tue, 17 Sep 2013 08:23:20 +0400 Subject: [PATCH 1/4] Ignoring MAC crap --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index f96dad5e..763529fd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ node_modules .idea *.iml + +.DS_Store From c4ed68cac58ebefea277306802fe43ff3ba16c8e Mon Sep 17 00:00:00 2001 From: Anton Rudeshko Date: Sun, 15 Sep 2013 13:31:01 +0400 Subject: [PATCH 2/4] Removing extra _this, adding clarifying whitespace. --- lib/csscomb.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/csscomb.js b/lib/csscomb.js index 25d054f8..cb7ad712 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -76,23 +76,25 @@ Comb.prototype = { * @param {Number} level Indent level */ processNode: function(node, level) { - var _this = this; node.forEach(function(node) { if (!Array.isArray(node)) return; + var nodeType = node.shift(); - _this._handlers.forEach(function(handler) { + this._handlers.forEach(function(handler) { handler.process(nodeType, node, level); }); node.unshift(nodeType); + if (nodeType === 'atrulers') level++; - _this.processNode(node, level); - }); + + this.processNode(node, level); + }, this); }, /** * Process file provided with a string. * @param {String} text - * @param {String} filename + * @param {String} [filename] */ processString: function(text, filename) { if (!text) return text; From f258e6c7e8947bfea1532897ce0f029bbc497acf Mon Sep 17 00:00:00 2001 From: Anton Rudeshko Date: Sun, 15 Sep 2013 13:43:51 +0400 Subject: [PATCH 3/4] Added exclude option description --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 37a2b89b..05937bb9 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,14 @@ Example configuration: ## Options +### exclude + +Available values: `{String[]}` + +Array of [Ant path patterns](http://ant.apache.org/manual/dirtasks.html#patterns) to exclude. + +Example: `{ "exclude": ["node_modules/**"] }` - exclude all files and directories under `node_modules` dir. + ### verbose Available value: `{Boolean}` `true` From 26067af4b745e2411a4bddd33192adb54f6c06df Mon Sep 17 00:00:00 2001 From: Anton Rudeshko Date: Sun, 15 Sep 2013 13:31:46 +0400 Subject: [PATCH 4/4] Option to remove empty rulesets. Closes #67. --- .csscomb.json | 1 + README.md | 9 +++ lib/csscomb.js | 1 + lib/options/remove-empty-rulesets.js | 85 ++++++++++++++++++++++++ test/integral.origin.css | 4 +- test/remove-empty-rulesets.js | 99 ++++++++++++++++++++++++++++ 6 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 lib/options/remove-empty-rulesets.js create mode 100644 test/remove-empty-rulesets.js diff --git a/.csscomb.json b/.csscomb.json index 40a77813..c7f26465 100644 --- a/.csscomb.json +++ b/.csscomb.json @@ -12,6 +12,7 @@ "element-case": "lower", "eof-newline": true, "leading-zero": false, + "remove-empty-rulesets": true, "rule-indent": true, "stick-brace": "\n", "strip-spaces": true, diff --git a/README.md b/README.md index 05937bb9..0df0d51a 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Example configuration: "exclude": ["node_modules/**"], "verbose": true, + "remove-empty-rulesets": true, "always-semicolon": true, "block-indent": true, "colon-space": true, @@ -88,6 +89,14 @@ $ ./bin/csscomb ./test --verbose $ ./bin/csscomb ./test -v ``` +### remove-empty-rulesets + +Available values: `{Boolean}` `true` + +Example: `{ "remove-empty-rulesets": true }` - remove rulesets that have no declarations or comments. + +`a { color: red; } p { /* hey */ } b { }` → `a { color: red; } p { /* hey */ } ` + ### always-semicolon Available value: `{Boolean}` `true` diff --git a/lib/csscomb.js b/lib/csscomb.js index cb7ad712..ee8b3b4c 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -11,6 +11,7 @@ var vfs = require('vow-fs'); */ var Comb = function() { this._options = [ + 'remove-empty-rulesets', 'always-semicolon', 'color-case', 'color-shorthand', diff --git a/lib/options/remove-empty-rulesets.js b/lib/options/remove-empty-rulesets.js new file mode 100644 index 00000000..10c818b5 --- /dev/null +++ b/lib/options/remove-empty-rulesets.js @@ -0,0 +1,85 @@ +module.exports = { + + /** + * Sets handler value. + * + * @param {String} value Option value + * @returns {Object|undefined} + */ + setValue: function(value) { + if (value === true) { + this._value = value; + return this; + } + }, + + /** + * Remove rulesets with no declarations. + * + * @param {String} nodeType + * @param {Array} nodeContent + */ + process: function(nodeType, nodeContent) { + if (nodeType === 'stylesheet') { + this._processStylesheetContent(nodeContent); + } + }, + + _processStylesheetContent: function(nodeContent) { + this._removeEmptyRulesets(nodeContent); + this._mergeAdjacentWhitespace(nodeContent); + }, + + _removeEmptyRulesets: function(nodeContent) { + var i = nodeContent.length; + while (i--) { + if (this._isRuleset(nodeContent[i]) && this._isEmptyRuleset(nodeContent[i])) { + nodeContent.splice(i, 1); + } + } + }, + + /** + * Removing ruleset nodes from tree may result in two adjacent whitespace nodes which is not correct AST: + * [space, ruleset, space] => [space, space] + * To ensure correctness of further processing we should merge such nodes into one. + * [space, space] => [space] + */ + _mergeAdjacentWhitespace: function(nodeContent) { + var i = nodeContent.length - 1; + while (i-- > 0) { + if (this._isWhitespace(nodeContent[i]) && this._isWhitespace(nodeContent[i + 1])) { + nodeContent[i][1] += nodeContent[i + 1][1]; + nodeContent.splice(i + 1, 1); + } + } + }, + + _isEmptyRuleset: function(ruleset) { + return ruleset.filter(this._isBlock).every(this._isEmptyBlock, this); + }, + + /** + * Block considered empty when it has no declarations or comments. + */ + _isEmptyBlock: function(node) { + return !node.some(this._isDeclarationOrComment); + }, + + _isDeclarationOrComment: function(node) { + return node[0] === 'declaration' || node[0] === 'comment'; + }, + + _isRuleset: function(node) { + return node[0] === 'ruleset'; + }, + + _isBlock: function(node) { + return node[0] === 'block'; + }, + + _isWhitespace: function(node) { + return node[0] === 's'; + } + +}; diff --git a/test/integral.origin.css b/test/integral.origin.css index ff6f1249..5c126da3 100644 --- a/test/integral.origin.css +++ b/test/integral.origin.css @@ -80,7 +80,7 @@ a, b, i /* foobar */ { outline: 0; padding: 0.4em 0; } -} +}.empty-rule{} /* Фигурные скобки. Вариант 2 */ div @@ -120,7 +120,7 @@ top: 0;/* ololo */margin :0;} b { top :0/* trololo */;margin : 0} - +.empty-rule{} diff --git a/test/remove-empty-rulesets.js b/test/remove-empty-rulesets.js new file mode 100644 index 00000000..ec9ecead --- /dev/null +++ b/test/remove-empty-rulesets.js @@ -0,0 +1,99 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); + +describe('options/remove-empty-rulesets', function() { + var comb; + + beforeEach(function() { + comb = new Comb(); + }); + + describe('configured with invalid value', function() { + beforeEach(function() { + comb.configure({ 'remove-empty-rulesets': 'foobar' }); + }); + + it('should not remove empty ruleset', function() { + assert.equal(comb.processString('a { width: 10px; } b {}'), 'a { width: 10px; } b {}'); + }); + }); + + describe('configured with Boolean "true" value', function() { + beforeEach(function() { + comb.configure({ 'remove-empty-rulesets': true }); + }); + + it('should remove empty ruleset', function() { + assert.equal(comb.processString(' b {} '), ' '); + }); + + it('should leave ruleset with declarations', function() { + assert.equal(comb.processString('a { width: 10px; }\nb {} '), 'a { width: 10px; }\n '); + }); + + it('should leave ruleset with comments', function() { + assert.equal(comb.processString('a { /* comment */ }\nb {} '), 'a { /* comment */ }\n '); + }); + }); +}); + +describe('options/remove-empty-rulesets AST manipulation', function() { + var rule; + var nodeContent; + + beforeEach(function() { + rule = require('../lib/options/remove-empty-rulesets.js'); + }); + + describe('merge adjacent whitespace', function() { + it('should do nothing with empty content', function() { + nodeContent = []; + rule._mergeAdjacentWhitespace(nodeContent); + assert.deepEqual(nodeContent, []); + }); + + it('should do nothing with only one whitespace', function() { + nodeContent = [['s', ' ']]; + rule._mergeAdjacentWhitespace(nodeContent); + assert.deepEqual(nodeContent, [['s', ' ']]); + }); + + it('should merge two adjacent whitespaces', function() { + nodeContent = [['s', ' '], ['s', ' \n']]; + rule._mergeAdjacentWhitespace(nodeContent); + assert.deepEqual(nodeContent, [['s', ' \n']]); + }); + + it('should merge three adjacent whitespaces', function() { + nodeContent = [['s', ' '], ['s', ' \n'], ['s', ' \n']]; + rule._mergeAdjacentWhitespace(nodeContent); + assert.deepEqual(nodeContent, [['s', ' \n \n']]); + }); + }); + + describe('remove empty rulesets', function() { + it('should do nothing with empty content', function() { + nodeContent = []; + rule._removeEmptyRulesets(nodeContent); + assert.deepEqual(nodeContent, []); + }); + + it('should do nothing with no rulesets', function() { + nodeContent = [['s', ' ']]; + rule._removeEmptyRulesets(nodeContent); + assert.deepEqual(nodeContent, [['s', ' ']]); + }); + + it('should remove empty ruleset', function() { + nodeContent = [['ruleset', []]]; + rule._removeEmptyRulesets(nodeContent); + assert.deepEqual(nodeContent, []); + }); + + it('should remove two empty rulesets', function() { + nodeContent = [['s', ' '], ['ruleset', []], ['s', ' \n'], ['ruleset', []]]; + rule._removeEmptyRulesets(nodeContent); + assert.deepEqual(nodeContent, [['s', ' '], ['s', ' \n']]); + }); + }); +});