From b9add65d72e688b4244cab159842e838b7b2c139 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Sat, 5 Oct 2013 21:02:13 +0400 Subject: [PATCH 01/75] Use Gonzales PE to parse *.scss files --- CHANGELOG.md | 4 ++++ README.md | 3 ++- lib/csscomb.js | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c8cb58..e967cb93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.X.X - 2013-XX-XX +- Use Gonzales PE to parse *.scss and *.less files +- Support sorting properties in *.scss and *.less files + ## 1.0.0 - 2013-11-06 - Option: vendor-prefix-align - Dependencies updated diff --git a/README.md b/README.md index 9adcfc26..2edcb2bc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ You can easily write your own [configuration](#configuration) to make your style The main feature is the [sorting properties](#sort-order) in specific order. It was inspired by the same-named [@miripiruni](https://github.com/miripiruni)'s [PHP-based tool](https://github.com/csscomb/csscomb). -This is the new JavaScript version, based on powerful CSS parser [Gonzales](https://github.com/css/gonzales). +This is the new JavaScript version, based on powerful CSS parser [Gonzales PE](https://github.com/tonyganch/gonzales-pe). ## Installation @@ -465,5 +465,6 @@ This software is released under the terms of the [MIT license](https://github.co ## Other projects * https://github.com/senchalabs/cssbeautify * https://github.com/css/gonzales +* https://github.com/tonyganch/gonzales-pe * https://github.com/css/csso * https://github.com/nzakas/parser-lib diff --git a/lib/csscomb.js b/lib/csscomb.js index 937b9663..f417f011 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -1,4 +1,4 @@ -var gonzales = require('gonzales'); +var gonzales = require('gonzales-pe'); var minimatch = require('minimatch'); var vow = require('vow'); var vfs = require('vow-fs'); From 94c7abae73cf83ff89f277c53821beb648865f7f Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 7 Oct 2013 01:45:33 +0400 Subject: [PATCH 02/75] Sass: Add tests for scss syntax --- test/scss.js | 455 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 455 insertions(+) create mode 100644 test/scss.js diff --git a/test/scss.js b/test/scss.js new file mode 100644 index 00000000..679937eb --- /dev/null +++ b/test/scss.js @@ -0,0 +1,455 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); + +describe('SCSS', function() { + var comb; + + beforeEach(function() { + comb = new Comb(); + }); + + describe('Parsing', function() { + it('Should parse nested rules', function() { + comb.configure({'sort-order': [ + ['top', 'color'] + ]}); + assert.equal( + comb.processString( + 'div { color: tomato; a { top: 0; } }' + ), + 'div { color: tomato; a { top: 0; } }' + ); + }); + + it('Should parse parent selector &', function() { + comb.configure({'sort-order': [ + ['top', 'left', 'color'] + ]}); + assert.equal( + comb.processString( + 'div { color: tomato; &.top { color: nani; top: 0; } left: 0; }' + ), + 'div { left: 0; color: tomato; &.top {top: 0; color: nani; }}' + ); + }); + + it('Should parse nested properties', function() { + comb.configure({'sort-order': [ + ['left', 'color', 'font'] + ]}); + assert.equal( + comb.processString( + 'div { color: tomato; font: 2px/3px { family: fantasy; size: 30em; } left: 0; }' + ), + 'div {left: 0; color: tomato; font: 2px/3px { family: fantasy; size: 30em; } }' + ); + }); + + it('Should parse variables', function() { + comb.configure({}); + assert.equal( + comb.processString( + '$red: tomato; div { color: $tomato; }' + ), + '$red: tomato; div { color: $tomato; }' + ); + }); + + it('Should parse interpolated variables inside selectors', function() { + comb.configure({'sort-order': [ + ['top', 'left', 'color'] + ]}); + assert.equal( + comb.processString( + 'div.#{$nani} {color:tomato;top:0;}' + ), + 'div.#{$nani} {top:0;color:tomato;}' + ); + }); + + it('Should parse interpolated variables inside values', function() { + comb.configure({'sort-order': [ + ['top', 'left', 'color'] + ]}); + assert.equal( + comb.processString( + 'div {color:#{$tomato};top:0;}' + ), + 'div {top:0;color:#{$tomato};}' + ); + }); + + it('Should parse defaults', function() { + comb.configure({'sort-order': [ + ['top', 'left', 'color'] + ]}); + assert.equal( + comb.processString( + 'div { color: tomato !default; top: 0; }' + ), + 'div {top: 0; color: tomato !default; }' + ); + }); + + it('Should parse @import', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div { @import "foo.css"; top: 0; }' + ), + 'div { @import "foo.css"; top: 0; }' + ); + }); + + it('Should parse @include', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div { @include nani($panda); top: 0; }' + ), + 'div { @include nani($panda); top: 0; }' + ); + }); + + it('Should parse nested @media', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div {\n' + + ' @media screen and (orientation: landscape) {\n' + + ' color: tomato;\n' + + ' }\n' + + ' top: 0;\n' + + '}' + ), + 'div {\n' + + ' @media screen and (orientation: landscape) {\n' + + ' color: tomato;\n' + + ' }\n' + + ' top: 0;\n' + + '}' + ); + }); + + it('Should parse @extend with classes', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div { @extend .nani; top: 0; }' + ), + 'div { @extend .nani; top: 0; }' + ); + }); + + it('Should parse @extend with placeholders', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div { @extend %nani; top: 0; }' + ), + 'div { @extend %nani; top: 0; }' + ); + }); + + it('Should parse @warn', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div { @warn "nani"; top: 0; }' + ), + 'div { @warn "nani"; top: 0; }' + ); + }); + + it('Should parse @if', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div { @if $type == ocean { top: 0; } }' + ), + 'div { @if $type == ocean { top: 0; } }' + ); + }); + + it('Should parse @if and @else', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div { @if $type == ocean { top: 0; } @else { left: 0; } }' + ), + 'div { @if $type == ocean { top: 0; } @else { left: 0; } }' + ); + }); + + it('Should parse @if and @else if', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }' + ), + 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }' + ); + }); + + it('Should parse @for', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div {\n' + + ' @for $i from 1 through 3 {\n' + + ' .item-#{$i} { width: 2em * 1; }\n' + + ' }\n' + + '}' + ), + 'div {\n' + + ' @for $i from 1 through 3 {\n' + + ' .item-#{$i} { width: 2em * 1; }\n' + + ' }\n' + + '}' + ); + }); + + it('Should parse @each', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div {\n' + + ' @each $animal in puma, sea-slug, erget {\n' + + ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + + ' }\n' + + '}' + ), + 'div {\n' + + ' @each $animal in puma, sea-slug, erget {\n' + + ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + + ' }\n' + + '}' + ); + }); + + it('Should parse @while', function() { + comb.configure({}); + assert.equal( + comb.processString( + 'div {\n' + + ' @while $i > 6 {\n' + + ' .item { width: 2em * $i; }\n' + + ' $i: $i - 2;\n' + + ' }\n' + + '}' + ), + 'div {\n' + + ' @while $i > 6 {\n' + + ' .item { width: 2em * $i; }\n' + + ' $i: $i - 2;\n' + + ' }\n' + + '}' + ); + }); + + it('Should parse mixins', function() { + comb.configure({}); + assert.equal( + comb.processString( + '@mixin nani { color: tomato; } .foo { @include nani; }' + ), + '@mixin nani { color: tomato; } .foo { @include nani; }' + ); + }); + + it('Should parse passing several variables to a mixin', function() { + comb.configure({}); + assert.equal( + comb.processString( + '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }' + ), + '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }' + ); + }); + + it('Should parse passing a list of variables to a mixin', function() { + comb.configure({}); + assert.equal( + comb.processString( + '@mixin nani($shadows...) { box-shadow: $shadows; } .foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }' + ), + '@mixin nani($shadows...) { box-shadow: $shadows; } .foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }' + ); + }); + + it('Should parse passing a content block to a mixin', function() { + comb.configure({}); + assert.equal( + comb.processString( + '.foo { @include nani { color: tomato; top: 0 } }' + ), + '.foo { @include nani { color: tomato; top: 0 } }' + ); + }); + + it('Should parse @content', function() { + comb.configure({}); + assert.equal( + comb.processString( + '@mixin nani { a { @content; } }' + ), + '@mixin nani { a { @content; } }' + ); + }); + + it('Should parse functions', function() { + comb.configure({}); + assert.equal( + comb.processString( + '@function nani($n) { @return $n * 2; }' + ), + '@function nani($n) { @return $n * 2; }' + ); + }); + }); + + describe('Sorting', function() { + it('Should sort properties inside rules', function() { + comb.configure({'sort-order': [ + ['top', 'color'] + ]}); + assert.equal( + comb.processString( + 'div { color: tomato; top: 0; }' + ), + 'div {top: 0; color: tomato; }' + ); + }); + + it('Should sort properties inside nested rules', function() { + comb.configure({'sort-order': [ + ['top', 'color'] + ]}); + assert.equal( + comb.processString( + 'div { color: tomato; a { color: nani; top: 0; } }' + ), + 'div { color: tomato; a {top: 0; color: nani; } }' + ); + }); + + it('Should sort properties divided by nested rules', function() { + comb.configure({'sort-order': [ + ['top', 'left', 'color'] + ]}); + assert.equal( + comb.processString( + 'div { color: tomato; a { color: nani; top: 0; } left: 0; }' + ), + 'div { left: 0; color: tomato; a {top: 0; color: nani; }}' + ); + }); + + it('Should group declarations with proper comments and spaces (multiple lines)', function() { + comb.configure({'sort-order': [ + ['top', 'color'] + ]}); + assert.equal( + comb.processString( + 'div {\n' + + ' color: tomato; /* 1 */\n' + + ' /* 2 */\n' + + ' /* 3 */\n' + + ' top: 0; /* 4 */\n' + + ' /* 5 */\n' + + '}' + ), + 'div {\n' + + ' /* 2 */\n' + + ' /* 3 */\n' + + ' top: 0; /* 4 */\n' + + ' color: tomato; /* 1 */\n' + + ' /* 5 */\n' + + '}' + ); + }); + + it('Should group declarations with proper comments and spaces (single line)', function() { + comb.configure({'sort-order': [ + ['top', 'color'] + ]}); + assert.equal( + comb.processString( + 'div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}' + ), + 'div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }' + ); + }); + + it('Should divide properties from different groups with an empty line', function() { + comb.configure({'sort-order': [ + ['top'], ['color'] + ]}); + assert.equal( + comb.processString( + 'div {\n' + + ' color: tomato;\n' + + ' top: 0;\n' + + '}' + ), + 'div {\n' + + ' top: 0;\n' + + '\n' + + ' color: tomato;\n' + + '}' + ); + }); + + it('Should sort variables', function() { + comb.configure({'sort-order': [ + ['$variable', 'color'] + ]}); + assert.equal( + comb.processString( + 'div { color: $tomato; $red: tomato; }' + ), + 'div {$red: tomato; color: $tomato; }' + ); + }); + + it('Should sort imports', function() { + comb.configure({'sort-order': [ + ['$import', 'color'] + ]}); + assert.equal(comb.processString( + 'div { color: tomato; @import "foo.css"; }' + ), + 'div {@import "foo.css"; color: tomato; }'); + }); + + it('Should sort @include-s', function() { + comb.configure({'sort-order': [ + ['$include', 'color'] + ]}); + assert.equal(comb.processString( + 'div { color: tomato; @include .nani; }' + ), + 'div {@include .nani; color: tomato; }'); + }); + + it('Should sort @extend-s', function() { + comb.configure({'sort-order': [ + ['$extend', 'color'] + ]}); + assert.equal(comb.processString( + 'div { color: tomato; @extend %nani; }' + ), + 'div {@extend %nani; color: tomato; }'); + }); + + it('Should sort properties inside blocks passed to mixins', function() { + comb.configure({'sort-order': [ + ['top', 'color'] + ]}); + assert.equal(comb.processString( + '.foo { @include nani { color: tomato; top: 0; } }' + ), + '.foo { @include nani {top: 0; color: tomato; } }'); + }); + }); +}); From fdaa624305d5dc4bd3998420dc654014945a525b Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 7 Oct 2013 01:47:15 +0400 Subject: [PATCH 03/75] Sass: Support sorting in *.scss files --- lib/options/sort-order.js | 276 ++++++++++++++++++++++++-------------- 1 file changed, 178 insertions(+), 98 deletions(-) diff --git a/lib/options/sort-order.js b/lib/options/sort-order.js index 45270142..231dc668 100644 --- a/lib/options/sort-order.js +++ b/lib/options/sort-order.js @@ -25,126 +25,206 @@ module.exports = { * @param {node} node */ process: function(nodeType, node) { + var order = this._order, // sort order of properties + sorted = [], // list of declarations that should be sorted + deleted = [], // list of nodes that should be removed from parent node + p, // property's name + NODES = ['declaration', 's', 'comment', 'atruleb', 'atrules', 'atruler'], // allowed nodes + SC = ['s', 'comment'], // spaces and comments + i, l, j, nl; // counters for loops + + // TODO: Think it through! if (nodeType !== 'block') return; - var order = this._order; - var nodeExt = this._createNodeExt(node); - var firstSymbols = nodeExt[0].decl || nodeExt.shift(); - var lastSymbols = nodeExt[nodeExt.length - 1].decl || nodeExt.pop(); - - nodeExt.sort(function(a, b) { - var indexA = order[a.decl] || { prop: -1 }; - var indexB = order[b.decl] || { prop: -1 }; - var groupDelta = indexA.group - indexB.group; - var propDelta = indexA.prop - indexB.prop; - - return groupDelta !== 0 ? groupDelta : propDelta; - }); - - firstSymbols && nodeExt.unshift(firstSymbols); - lastSymbols && nodeExt.push(lastSymbols); - - this._applyNodeExt(node, nodeExt); - }, + // Check every child node. + // If it is declaration (property-value pair, e.g. `color: tomato`), + // combine it with spaces, semicolon and comments and move them from + // current node to a separate list for further sorting: + for (i = 0, l = node.length; i < l; i++) { + if (NODES.indexOf(node[i][0]) === -1) continue; + + // Save preceding spaces and comments, if there are any, and mark + // them for removing from parent node: + var sc0 = _checkSC0(i); + if (!sc0) continue; + + if (!node[i]) { + deleted.splice(deleted.length - sc0.length, deleted.length + 1); + break; + } - /** - * Internal. Smart split bunch on '\n' symbols; - * @param {Array} bunch - * @param {Boolean} isPrevDeclExists - */ - _splitBunch: function(bunch, isPrevDeclExists) { - var nextBunch = []; - var declAlready = false; - var flag = false; - var item; - var indexOf; + // Check if there is a property (e.g. `color`) inside + // the declaration. If not, proceed with the next node: + p = null; + for (j = 1, nl = node[i].length; j < nl; j++) { + if (node[i][j][0] === 'property') { + if (node[i][j][1][0] === 'variable') { + p = '$variable'; + } else { + p = node[i][j][1][1]; + } + break; + } else if (node[i][j][0] === 'atkeyword') { + if (['import', 'include', 'extend'].indexOf(node[i][j][1][1]) > -1) { + p = '$' + node[i][j][1][1]; + } + break; + } + } - for (var i = 0; i < bunch.length; ++i) { - if (flag) { nextBunch.push(bunch[i]); continue; } - if (bunch[i][0] === 'declaration') { declAlready = true; } + if (!p) { + deleted = []; + continue; + } - if (isPrevDeclExists && !declAlready) continue; - if (bunch[i][0] !== 's') continue; + // Combine declaration node with other relevant information + // (property index, semicolon, spaces and comments): + var n = { + node: node[i], + sc0: sc0, + delim: [], + // If the declaration's property is in order's list, save its + // group and property indices. Otherwise set them to 10000, so + // declaration appears at the bottom of a sorted list: + gi: (order[p] && order[p].group > -1 ? order[p].group + 2 : 10000), // group index + pi: (order[p] && order[p].prop > -1 ? order[p].prop + 2 : 10000) // property index + }; + + // Mark the declaration node to remove it later from parent node: + deleted.push(i); + + // If there is `;` right after the declaration, save it with the + // declaration and mark it for removing from parent node: + if (node[i + 1] && node[i + 1][0] === 'decldelim') { + n.delim.push(node[i + 1]); + deleted.push(i + 1); + i++; + } - var indexOfNewLine = bunch[i][1].indexOf('\n'); - if (indexOfNewLine === -1) continue; + // Save spaces and comments which follow right after the declaration + // and mark them for removing from parent node: + n.sc1 = _checkSC1(i); - nextBunch.push(['s', bunch[i][1].substr(indexOfNewLine + 1)]); - bunch[i][1] = bunch[i][1].substr(0, indexOfNewLine + 1); + // Move the declaration node to a separate list for further sorting: + sorted.push(n); - flag = i + 1; } - if (nextBunch.length === 1 && nextBunch[0][0] === 's') { - item = nextBunch[0]; - indexOf = item[1].lastIndexOf('\n'); - indexOf !== -1 && (item[1] = item[1].substr(indexOf + 1)); + + // Remove all nodes, that were moved to a `sorted` list, from parent node: + for (i = 0, j = 0, l = deleted.length; i < l; i++, j++) { + // Since every time we remove an element from an array, array's + // length reduces by 1, save number of already removed elements (`j`) + // and use it to find the index of a next element to remove: + node.splice(deleted[i] - j, 1); } - flag && bunch.splice(flag); + // Sort declarations saved for sorting: + sorted.sort(function(a, b) { + // If a's group index is higher than b's group index, in a sorted + // list a appears after b: + if (a.gi !== b.gi) return a.gi - b.gi; - return nextBunch; - }, + // If a and b have the same group index, and a's property index is + // higher than b's property index, in a sorted list a appears after + // b: + return a.pi - b.pi; + }); - /** - * Internal. Create extended node in format of list - * { - * data:[,,declaration,] - * decl: declarationPropertyName - * }; - * @param {node} node - */ - _createNodeExt: function(node) { - var extNode = []; - var bunch = []; - var nextBunch; - var prevDeclPropertyName; - - node.forEach(function(node) { - if (node[0] === 'declaration') { - nextBunch = this._splitBunch(bunch, prevDeclPropertyName); - extNode.push({ data: bunch, decl: prevDeclPropertyName }); - bunch = nextBunch; - prevDeclPropertyName = node[1][1][1]; + // Build all nodes back together. First go sorted declarations, then + // everything else: + if (sorted.length > 0) { + for (i = sorted.length - 1, l = -1; i > l; i--) { + + // Divide declarations from different groups with an empty line: + if (sorted[i - 1] && sorted[i].gi > sorted[i - 1].gi) { + if (sorted[i].sc0[0] && sorted[i].sc0[0][0] === 's' && + sorted[i].sc0[0][1].match(/\n/g) && + sorted[i].sc0[0][1].match(/\n/g).length < 2) { + sorted[i].sc0.unshift(['s', '\n']); + } + } + + sorted[i].sc0.reverse(); + sorted[i].sc1.reverse(); + + for (j = 0, nl = sorted[i].sc1.length; j < nl; j++) { + node.unshift(sorted[i].sc1[j]); + } + if (sorted[i].delim.length > 0) node.unshift(['decldelim']); + node.unshift(sorted[i].node); + for (j = 0, nl = sorted[i].sc0.length; j < nl; j++) { + node.unshift(sorted[i].sc0[j]); + } } - bunch.push(node); - }, this); + } - nextBunch = this._splitBunch(bunch, prevDeclPropertyName); - extNode.push({ data: bunch, decl: prevDeclPropertyName }); - nextBunch.length && extNode.push({ data: nextBunch }); + /** + * Check if there are any comments or spaces before the declaration + * @returns {Array} List of nodes with spaces and comments + * @private + */ + function _checkSC0 () { + var sc = [], + d = []; + + for (; i < l; i++) { + // If there is no node, or it is nor spaces neither comment, stop: + if (!node[i] || + NODES.indexOf(node[i][0]) === -1) { + return false; + } + + // If node is declaration or @-rule: + if (SC.indexOf(node[i][0]) === -1) break; + + sc.push(node[i]); + d.push(i); + } - return extNode; - }, + deleted = deleted.concat(d); - /** - * Internal. Add delimiter at the end of groups of properties - * @param {extNode} extNodePrev - * @param {extNode} extNode - */ - _addGroupDelimiter: function(extNodePrev, extNode) { - if (!extNodePrev.decl || !extNode.decl) return; + return sc; + } - var indexA = this._order[extNodePrev.decl]; - var indexB = this._order[extNode.decl]; - var isGroupBorder = indexA && indexB && indexA.group !== indexB.group; + /** + * Check if there are any comments or spaces after the declaration + * @returns {Array} List of nodes with spaces and comments + * @private + */ + function _checkSC1 () { + var sc = [], // nodes with spaces and comments + d = []; + + // Check every next node: + for (; i < l; i++) { + // If there is no node, or it is nor spaces neither comment, stop: + if (!node[i + 1] || SC.indexOf(node[i + 1][0]) === -1) break; + + if (node[i + 1][0] === 'comment') { + sc.push(node[i + 1]); + d.push(i + 1); + continue; + } + + var lbIndex = node[i + 1][1].indexOf('\n'); + + if (lbIndex > -1) { + // TODO: Don't push an empty array + sc.push(['s', node[i + 1][1].substring(0, lbIndex)]); + node[i + 1][1] = node[i + 1][1].substring(lbIndex); + break; + } + + sc.push(node[i + 1]); + d.push(i + 1); + } - if (isGroupBorder) extNode.data.unshift(['s', '\n']); - }, + deleted = deleted.concat(d); - /** - * Internal. apply extNode back at common format node. - * @param {node} node - * @param {extNode} extNode - */ - _applyNodeExt: function(node, extNode) { - node.splice(0, node.length); - extNode.forEach(function(item, i) { - i > 0 && this._addGroupDelimiter(extNode[i - 1], item); - node.push.apply(node, item.data); - }, this); + return sc; + } } - }; From 808c508b65891ee13716d2371fcac19e14bae529 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Tue, 8 Oct 2013 16:47:10 +0400 Subject: [PATCH 04/75] Update tests for sort-order --- test/integral.expect.css | 1 + test/sort-order.js | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test/integral.expect.css b/test/integral.expect.css index 9d161b4d..c65c07ab 100644 --- a/test/integral.expect.css +++ b/test/integral.expect.css @@ -8,6 +8,7 @@ background: -moz-linear-gradient(top, rgba(0,0,0,.2) 0, rgba(0,0,0,.4) 100%); background: -o-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%); background: linear-gradient(to bottom, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%); + -moz-box-shadow: 0 1px 0 rgba(0,0,0,.07); box-shadow: 0 1px 0 rgba(0,0,0,.07); } diff --git a/test/sort-order.js b/test/sort-order.js index d1e1bbd6..e7ac6a54 100644 --- a/test/sort-order.js +++ b/test/sort-order.js @@ -76,11 +76,11 @@ describe('options/sort-order', function() { var input = 'div p em {\n' + '\t/* upline comment */\n' + '\tfont-style:italic;\n' + - '\tborder-bottom:1px solid red /* trololo */ /* trololo */\n' + + '\tborder-bottom:1px solid red; /* trololo */ /* trololo */\n' + '}'; var expected = 'div p em {\n' + - '\tborder-bottom:1px solid red /* trololo */ /* trololo */\n' + + '\tborder-bottom:1px solid red; /* trololo */ /* trololo */\n' + '\t/* upline comment */\n' + '\tfont-style:italic;\n' + '}'; @@ -117,7 +117,8 @@ describe('options/sort-order', function() { }); - it('Should replace custom delimiters by ours', function() { + /* TODO: We should not change code while sorting. If we want to replace delimeters, it should be done with another module, but NOT sort-order. + /*it('Should replace custom delimiters by ours', function() { var config = { 'sort-order': [ @@ -141,6 +142,6 @@ describe('options/sort-order', function() { comb.configure(config); assert.equal(comb.processString(input), expected); - }); + });*/ }); From 00442637a52356f8feb32c67e672537db4f5798e Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Thu, 10 Oct 2013 01:19:26 +0400 Subject: [PATCH 05/75] Clean up --- lib/options/sort-order.js | 335 ++++++++++++++++++++++---------------- test/scss.js | 74 +++++---- test/sort-order.js | 5 +- 3 files changed, 236 insertions(+), 178 deletions(-) diff --git a/lib/options/sort-order.js b/lib/options/sort-order.js index 231dc668..f9ec47c3 100644 --- a/lib/options/sort-order.js +++ b/lib/options/sort-order.js @@ -25,19 +25,167 @@ module.exports = { * @param {node} node */ process: function(nodeType, node) { - var order = this._order, // sort order of properties - sorted = [], // list of declarations that should be sorted - deleted = [], // list of nodes that should be removed from parent node - p, // property's name - NODES = ['declaration', 's', 'comment', 'atruleb', 'atrules', 'atruler'], // allowed nodes - SC = ['s', 'comment'], // spaces and comments - i, l, j, nl; // counters for loops + // Types of nodes that can be sorted: + var NODES = ['atruleb', 'atruler', 'atrules', 'comment', 'declaration', 's']; + // Special @-rules that can be sorted: + var ATRULES = ['extend', 'import', 'include']; + // Spaces and comments: + var SC = ['comment', 's']; + + var currentNode; + // Sort order of properties: + var order = this._order; + // List of declarations that should be sorted: + var sorted = []; + // list of nodes that should be removed from parent node: + var deleted = []; + // List of spaces and comments that go before declaration/@-rule: + var sc0 = []; + // Value to search in sort order: either a declaration's property name + // (e.g. `color`), or @-rule's special keyword (e.g. `$import`): + var propertyName; + + // Counters for loops: + var i; + var l; + var j; + var nl; + + /** + * Check if there are any comments or spaces before + * the declaration/@-rule. + * @returns {Array} List of nodes with spaces and comments + */ + var checkSC0 = function() { + // List of nodes with spaces and comments: + var sc = []; + // List of nodes that can be later deleted from parent node: + var d = []; + + for (; i < l; i++) { + currentNode = node[i]; + // If there is no node, or it is nor spaces neither comment, + // stop and do nothing with previously found spaces/comments: + if (!currentNode || + NODES.indexOf(currentNode[0]) === -1) { + return false; + } + + // If the node is declaration or @-rule, stop and return all + // found nodes with spaces and comments (if there are any): + if (SC.indexOf(currentNode[0]) === -1) break; + + sc.push(currentNode); + d.push(i); + } + + deleted = deleted.concat(d); + + return sc; + }; + + /** + * Check if there are any comments or spaces after + * the declaration/@-rule. + * @returns {Array} List of nodes with spaces and comments + * @private + */ + var checkSC1 = function() { + // List of nodes with spaces and comments: + var sc = []; + // List of nodes that can be later deleted from parent node: + var d = []; + // Position of `\n` symbol inside a node with spaces: + var lbIndex; + + // Check every next node: + for (; i < l; i++) { + currentNode = node[i + 1]; + // If there is no node, or it is nor spaces neither comment, stop: + if (!currentNode || SC.indexOf(currentNode[0]) === -1) break; + + if (currentNode[0] === 'comment') { + sc.push(currentNode); + d.push(i + 1); + continue; + } + + lbIndex = currentNode[1].indexOf('\n'); + + // If there are any line breaks in a node with spaces, stop and + // split the node into two: one with spaces before line break + // and one with `\n` symbol and everything that goes after. + // Combine the first one with declaration/@-rule's node: + if (lbIndex > -1) { + // TODO: Don't push an empty array + sc.push(['s', currentNode[1].substring(0, lbIndex)]); + currentNode[1] = currentNode[1].substring(lbIndex); + break; + } + + sc.push(currentNode); + d.push(i + 1); + } + + deleted = deleted.concat(d); + + return sc; + }; + + /** + * Combine declaration/@-rule's node with other relevant information: + * property index, semicolon, spaces and comments. + * @returns {Object} Extended node + */ + var extendNode = function() { + currentNode = node[i]; + var nextNode = node[i + 1]; + // Object containing current node, all corresponding spaces, + // comments and other information: + var extendedNode; + // Check if current node's property name is in sort order. + // If it is, save information about its indices: + var orderProperty = order[propertyName]; + + extendedNode = { + node: currentNode, + sc0: sc0, + delim: [] + }; + + // If the declaration's property is in order's list, save its + // group and property indices. Otherwise set them to 10000, so + // declaration appears at the bottom of a sorted list: + extendedNode.groupIndex = orderProperty && orderProperty.group > -1 ? + orderProperty.group : 10000; + extendedNode.propertyIndex = orderProperty && orderProperty.prop > -1 ? + orderProperty.prop : 10000; + + // Mark current node to remove it later from parent node: + deleted.push(i); + + // If there is `;` right after the declaration, save it with the + // declaration and mark it for removing from parent node: + if (currentNode && nextNode[0] === 'decldelim') { + extendedNode.delim.push(nextNode); + deleted.push(i + 1); + i++; + } + + // Save spaces and comments which follow right after the declaration + // and mark them for removing from parent node: + extendedNode.sc1 = checkSC1(i); + + return extendedNode; + }; // TODO: Think it through! + // Sort properties only inside blocks: if (nodeType !== 'block') return; // Check every child node. // If it is declaration (property-value pair, e.g. `color: tomato`), + // or @-rule (e.g. `@include nani`), // combine it with spaces, semicolon and comments and move them from // current node to a separate list for further sorting: for (i = 0, l = node.length; i < l; i++) { @@ -45,186 +193,91 @@ module.exports = { // Save preceding spaces and comments, if there are any, and mark // them for removing from parent node: - var sc0 = _checkSC0(i); + sc0 = checkSC0(); if (!sc0) continue; + // If spaces/comments are the last nodes, stop and go to sorting: if (!node[i]) { deleted.splice(deleted.length - sc0.length, deleted.length + 1); break; } - // Check if there is a property (e.g. `color`) inside - // the declaration. If not, proceed with the next node: - p = null; + // Check if the node needs to be sorted: + // it should be a special @-rule (e.g. `@include`) or a declaration + // with a valid property (e.g. `color` or `$width`). + // If not, proceed with the next node: + propertyName = null; for (j = 1, nl = node[i].length; j < nl; j++) { - if (node[i][j][0] === 'property') { - if (node[i][j][1][0] === 'variable') { - p = '$variable'; - } else { - p = node[i][j][1][1]; - } + currentNode = node[i][j]; + if (currentNode[0] === 'property') { + propertyName = currentNode[1][0] === 'variable' ? + '$variable' : currentNode[1][1]; break; - } else if (node[i][j][0] === 'atkeyword') { - if (['import', 'include', 'extend'].indexOf(node[i][j][1][1]) > -1) { - p = '$' + node[i][j][1][1]; + } else if (currentNode[0] === 'atkeyword') { + if (ATRULES.indexOf(currentNode[1][1]) > -1) { + propertyName = '$' + currentNode[1][1]; } break; } } - if (!p) { + if (!propertyName) { deleted = []; continue; } - // Combine declaration node with other relevant information - // (property index, semicolon, spaces and comments): - var n = { - node: node[i], - sc0: sc0, - delim: [], - // If the declaration's property is in order's list, save its - // group and property indices. Otherwise set them to 10000, so - // declaration appears at the bottom of a sorted list: - gi: (order[p] && order[p].group > -1 ? order[p].group + 2 : 10000), // group index - pi: (order[p] && order[p].prop > -1 ? order[p].prop + 2 : 10000) // property index - }; - - // Mark the declaration node to remove it later from parent node: - deleted.push(i); - - // If there is `;` right after the declaration, save it with the - // declaration and mark it for removing from parent node: - if (node[i + 1] && node[i + 1][0] === 'decldelim') { - n.delim.push(node[i + 1]); - deleted.push(i + 1); - i++; - } - - - // Save spaces and comments which follow right after the declaration - // and mark them for removing from parent node: - n.sc1 = _checkSC1(i); - - // Move the declaration node to a separate list for further sorting: - sorted.push(n); - + // Make an extended node and move it to a separate list for further + // sorting: + sorted.push(extendNode()); } - - // Remove all nodes, that were moved to a `sorted` list, from parent node: - for (i = 0, j = 0, l = deleted.length; i < l; i++, j++) { - // Since every time we remove an element from an array, array's - // length reduces by 1, save number of already removed elements (`j`) - // and use it to find the index of a next element to remove: - node.splice(deleted[i] - j, 1); + for (i = deleted.length - 1; i > -1; i--) { + node.splice(deleted[i], 1); } // Sort declarations saved for sorting: sorted.sort(function(a, b) { // If a's group index is higher than b's group index, in a sorted // list a appears after b: - if (a.gi !== b.gi) return a.gi - b.gi; + if (a.groupIndex !== b.groupIndex) return a.groupIndex - b.groupIndex; // If a and b have the same group index, and a's property index is // higher than b's property index, in a sorted list a appears after // b: - return a.pi - b.pi; + return a.propertyIndex - b.propertyIndex; }); // Build all nodes back together. First go sorted declarations, then // everything else: if (sorted.length > 0) { for (i = sorted.length - 1, l = -1; i > l; i--) { + currentNode = sorted[i]; + var prevNode = sorted[i - 1]; + sc0 = currentNode.sc0; + var sc1 = currentNode.sc1; // Divide declarations from different groups with an empty line: - if (sorted[i - 1] && sorted[i].gi > sorted[i - 1].gi) { - if (sorted[i].sc0[0] && sorted[i].sc0[0][0] === 's' && - sorted[i].sc0[0][1].match(/\n/g) && - sorted[i].sc0[0][1].match(/\n/g).length < 2) { - sorted[i].sc0.unshift(['s', '\n']); + if (prevNode && currentNode.groupIndex > prevNode.groupIndex) { + if (sc0[0] && sc0[0][0] === 's' && + sc0[0][1].match(/\n/g) && + sc0[0][1].match(/\n/g).length < 2) { + sc0.unshift(['s', '\n']); } } - sorted[i].sc0.reverse(); - sorted[i].sc1.reverse(); + sc0.reverse(); + sc1.reverse(); - for (j = 0, nl = sorted[i].sc1.length; j < nl; j++) { - node.unshift(sorted[i].sc1[j]); + for (j = 0, nl = sc1.length; j < nl; j++) { + node.unshift(sc1[j]); } - if (sorted[i].delim.length > 0) node.unshift(['decldelim']); - node.unshift(sorted[i].node); - for (j = 0, nl = sorted[i].sc0.length; j < nl; j++) { - node.unshift(sorted[i].sc0[j]); + if (currentNode.delim.length > 0) node.unshift(['decldelim']); + node.unshift(currentNode.node); + for (j = 0, nl = sc0.length; j < nl; j++) { + node.unshift(sc0[j]); } } } - - /** - * Check if there are any comments or spaces before the declaration - * @returns {Array} List of nodes with spaces and comments - * @private - */ - function _checkSC0 () { - var sc = [], - d = []; - - for (; i < l; i++) { - // If there is no node, or it is nor spaces neither comment, stop: - if (!node[i] || - NODES.indexOf(node[i][0]) === -1) { - return false; - } - - // If node is declaration or @-rule: - if (SC.indexOf(node[i][0]) === -1) break; - - sc.push(node[i]); - d.push(i); - } - - deleted = deleted.concat(d); - - return sc; - } - - /** - * Check if there are any comments or spaces after the declaration - * @returns {Array} List of nodes with spaces and comments - * @private - */ - function _checkSC1 () { - var sc = [], // nodes with spaces and comments - d = []; - - // Check every next node: - for (; i < l; i++) { - // If there is no node, or it is nor spaces neither comment, stop: - if (!node[i + 1] || SC.indexOf(node[i + 1][0]) === -1) break; - - if (node[i + 1][0] === 'comment') { - sc.push(node[i + 1]); - d.push(i + 1); - continue; - } - - var lbIndex = node[i + 1][1].indexOf('\n'); - - if (lbIndex > -1) { - // TODO: Don't push an empty array - sc.push(['s', node[i + 1][1].substring(0, lbIndex)]); - node[i + 1][1] = node[i + 1][1].substring(lbIndex); - break; - } - - sc.push(node[i + 1]); - d.push(i + 1); - } - - deleted = deleted.concat(d); - - return sc; - } } }; diff --git a/test/scss.js b/test/scss.js index 679937eb..5a6b8c22 100644 --- a/test/scss.js +++ b/test/scss.js @@ -10,9 +10,9 @@ describe('SCSS', function() { describe('Parsing', function() { it('Should parse nested rules', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top', 'color'] - ]}); + ] }); assert.equal( comb.processString( 'div { color: tomato; a { top: 0; } }' @@ -22,9 +22,9 @@ describe('SCSS', function() { }); it('Should parse parent selector &', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top', 'left', 'color'] - ]}); + ] }); assert.equal( comb.processString( 'div { color: tomato; &.top { color: nani; top: 0; } left: 0; }' @@ -34,9 +34,9 @@ describe('SCSS', function() { }); it('Should parse nested properties', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['left', 'color', 'font'] - ]}); + ] }); assert.equal( comb.processString( 'div { color: tomato; font: 2px/3px { family: fantasy; size: 30em; } left: 0; }' @@ -56,9 +56,9 @@ describe('SCSS', function() { }); it('Should parse interpolated variables inside selectors', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top', 'left', 'color'] - ]}); + ] }); assert.equal( comb.processString( 'div.#{$nani} {color:tomato;top:0;}' @@ -68,9 +68,9 @@ describe('SCSS', function() { }); it('Should parse interpolated variables inside values', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top', 'left', 'color'] - ]}); + ] }); assert.equal( comb.processString( 'div {color:#{$tomato};top:0;}' @@ -80,9 +80,9 @@ describe('SCSS', function() { }); it('Should parse defaults', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top', 'left', 'color'] - ]}); + ] }); assert.equal( comb.processString( 'div { color: tomato !default; top: 0; }' @@ -271,9 +271,11 @@ describe('SCSS', function() { comb.configure({}); assert.equal( comb.processString( - '@mixin nani($shadows...) { box-shadow: $shadows; } .foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }' + '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + + '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }' ), - '@mixin nani($shadows...) { box-shadow: $shadows; } .foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }' + '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + + '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }' ); }); @@ -310,9 +312,9 @@ describe('SCSS', function() { describe('Sorting', function() { it('Should sort properties inside rules', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top', 'color'] - ]}); + ] }); assert.equal( comb.processString( 'div { color: tomato; top: 0; }' @@ -322,9 +324,9 @@ describe('SCSS', function() { }); it('Should sort properties inside nested rules', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top', 'color'] - ]}); + ] }); assert.equal( comb.processString( 'div { color: tomato; a { color: nani; top: 0; } }' @@ -334,9 +336,9 @@ describe('SCSS', function() { }); it('Should sort properties divided by nested rules', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top', 'left', 'color'] - ]}); + ] }); assert.equal( comb.processString( 'div { color: tomato; a { color: nani; top: 0; } left: 0; }' @@ -346,9 +348,9 @@ describe('SCSS', function() { }); it('Should group declarations with proper comments and spaces (multiple lines)', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top', 'color'] - ]}); + ] }); assert.equal( comb.processString( 'div {\n' + @@ -370,9 +372,9 @@ describe('SCSS', function() { }); it('Should group declarations with proper comments and spaces (single line)', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top', 'color'] - ]}); + ] }); assert.equal( comb.processString( 'div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}' @@ -382,9 +384,9 @@ describe('SCSS', function() { }); it('Should divide properties from different groups with an empty line', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top'], ['color'] - ]}); + ] }); assert.equal( comb.processString( 'div {\n' + @@ -401,9 +403,9 @@ describe('SCSS', function() { }); it('Should sort variables', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['$variable', 'color'] - ]}); + ] }); assert.equal( comb.processString( 'div { color: $tomato; $red: tomato; }' @@ -413,9 +415,9 @@ describe('SCSS', function() { }); it('Should sort imports', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['$import', 'color'] - ]}); + ] }); assert.equal(comb.processString( 'div { color: tomato; @import "foo.css"; }' ), @@ -423,9 +425,9 @@ describe('SCSS', function() { }); it('Should sort @include-s', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['$include', 'color'] - ]}); + ] }); assert.equal(comb.processString( 'div { color: tomato; @include .nani; }' ), @@ -433,9 +435,9 @@ describe('SCSS', function() { }); it('Should sort @extend-s', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['$extend', 'color'] - ]}); + ] }); assert.equal(comb.processString( 'div { color: tomato; @extend %nani; }' ), @@ -443,9 +445,9 @@ describe('SCSS', function() { }); it('Should sort properties inside blocks passed to mixins', function() { - comb.configure({'sort-order': [ + comb.configure({ 'sort-order': [ ['top', 'color'] - ]}); + ] }); assert.equal(comb.processString( '.foo { @include nani { color: tomato; top: 0; } }' ), diff --git a/test/sort-order.js b/test/sort-order.js index e7ac6a54..ef71325e 100644 --- a/test/sort-order.js +++ b/test/sort-order.js @@ -117,7 +117,10 @@ describe('options/sort-order', function() { }); - /* TODO: We should not change code while sorting. If we want to replace delimeters, it should be done with another module, but NOT sort-order. + /* TODO: We should not change code while sorting. + * If we want to replace delimeters, it should be done with another module, + * but NOT sort-order. + */ /*it('Should replace custom delimiters by ours', function() { var config = { From 39cd1d82366c922769d1a5d21c04573568b6eb1a Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Sun, 13 Oct 2013 17:32:16 +0200 Subject: [PATCH 06/75] Update gonzales to v2.0.0-rc0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit – Use new method names – Allow processing of *.scss files – Check syntax name (css or scss) - Use one `include` node for both `@include` and `@extend` – Update tests --- .jshint-groups.js | 2 +- lib/csscomb.js | 13 +- lib/options/always-semicolon.js | 2 +- lib/options/remove-empty-rulesets.js | 2 +- lib/options/sort-order.js | 42 +- package.json | 2 +- test/scss.js | 582 ++++++++++++--------------- 7 files changed, 297 insertions(+), 348 deletions(-) diff --git a/.jshint-groups.js b/.jshint-groups.js index 70b48684..95aa4d7b 100644 --- a/.jshint-groups.js +++ b/.jshint-groups.js @@ -27,7 +27,7 @@ module.exports = { test: { options: { node: true, - predef: ['describe', 'beforeEach', 'it'] + predef: ['describe', 'beforeEach', 'afterEach', 'it'] }, includes: ['test/*.js'] } diff --git a/lib/csscomb.js b/lib/csscomb.js index f417f011..a4ad8d63 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -103,14 +103,15 @@ Comb.prototype = { /** * Process file provided with a string. * @param {String} text + * @param {String} [syntax] Syntax name (e.g. `scss`) * @param {String} [filename] */ - processString: function(text, filename) { + processString: function(text, syntax, filename) { if (!text) return text; var tree; var string = JSON.stringify; try { - tree = gonzales.srcToCSSP(text); + tree = gonzales.cssToAST({ syntax: syntax, css: text }); } catch (e) { throw new Error('Parsing error at ' + filename + ': ' + e.message); } @@ -118,7 +119,7 @@ Comb.prototype = { throw new Error('Undefined tree at ' + filename + ': ' + string(text) + ' => ' + string(tree)); } tree = this.processTree(tree); - return gonzales.csspToSrc(tree); + return gonzales.astToCSS({ syntax: syntax, ast: tree }); }, /** @@ -129,9 +130,11 @@ Comb.prototype = { */ processFile: function(path) { var _this = this; - if (this._shouldProcess(path) && path.match(/\.css$/)) { + // TODO: Move extension check into `_shouldProcess` method + if (this._shouldProcess(path) && path.match(/\.[css, scss]$/)) { return vfs.read(path, 'utf8').then(function(data) { - var processedData = _this.processString(data, path); + var syntax = path.split('.').pop(); + var processedData = _this.processString(data, syntax, path); var changed = data !== processedData; var lint = _this._lint; diff --git a/lib/options/always-semicolon.js b/lib/options/always-semicolon.js index c0a32091..07e951ad 100644 --- a/lib/options/always-semicolon.js +++ b/lib/options/always-semicolon.js @@ -29,7 +29,7 @@ module.exports = { if (type === 'declaration') { var space = []; for (var j = value.length; j--;) { - if (value[j][0] !== 's' && value[j][0] !== 'comment') break; + if (['s', 'commentML', 'commentSL'].indexOf(value[j][0]) === -1) break; space.unshift(value.splice(j)[0]); } node.splice.apply(node, [i + 1, 0, ['decldelim']].concat(space)); diff --git a/lib/options/remove-empty-rulesets.js b/lib/options/remove-empty-rulesets.js index 10c818b5..51acdca9 100644 --- a/lib/options/remove-empty-rulesets.js +++ b/lib/options/remove-empty-rulesets.js @@ -67,7 +67,7 @@ module.exports = { }, _isDeclarationOrComment: function(node) { - return node[0] === 'declaration' || node[0] === 'comment'; + return ['declaration', 'commentML', 'commentSL'].indexOf(node[0]) > -1; }, _isRuleset: function(node) { diff --git a/lib/options/sort-order.js b/lib/options/sort-order.js index f9ec47c3..beb620b9 100644 --- a/lib/options/sort-order.js +++ b/lib/options/sort-order.js @@ -26,11 +26,10 @@ module.exports = { */ process: function(nodeType, node) { // Types of nodes that can be sorted: - var NODES = ['atruleb', 'atruler', 'atrules', 'comment', 'declaration', 's']; - // Special @-rules that can be sorted: - var ATRULES = ['extend', 'import', 'include']; + var NODES = ['atruleb', 'atruler', 'atrules', 'commentML', 'commentSL', + 'declaration', 's', 'include']; // Spaces and comments: - var SC = ['comment', 's']; + var SC = ['commentML', 'commentSL', 's']; var currentNode; // Sort order of properties: @@ -64,10 +63,9 @@ module.exports = { for (; i < l; i++) { currentNode = node[i]; - // If there is no node, or it is nor spaces neither comment, + // If there is no node left, // stop and do nothing with previously found spaces/comments: - if (!currentNode || - NODES.indexOf(currentNode[0]) === -1) { + if (!currentNode) { return false; } @@ -104,7 +102,7 @@ module.exports = { // If there is no node, or it is nor spaces neither comment, stop: if (!currentNode || SC.indexOf(currentNode[0]) === -1) break; - if (currentNode[0] === 'comment') { + if (['commentML', 'commentSL'].indexOf(currentNode[0]) > -1) { sc.push(currentNode); d.push(i + 1); continue; @@ -166,7 +164,7 @@ module.exports = { // If there is `;` right after the declaration, save it with the // declaration and mark it for removing from parent node: - if (currentNode && nextNode[0] === 'decldelim') { + if (currentNode && nextNode && nextNode[0] === 'decldelim') { extendedNode.delim.push(nextNode); deleted.push(i + 1); i++; @@ -174,7 +172,7 @@ module.exports = { // Save spaces and comments which follow right after the declaration // and mark them for removing from parent node: - extendedNode.sc1 = checkSC1(i); + extendedNode.sc1 = checkSC1(); return extendedNode; }; @@ -207,17 +205,21 @@ module.exports = { // with a valid property (e.g. `color` or `$width`). // If not, proceed with the next node: propertyName = null; - for (j = 1, nl = node[i].length; j < nl; j++) { - currentNode = node[i][j]; - if (currentNode[0] === 'property') { - propertyName = currentNode[1][0] === 'variable' ? - '$variable' : currentNode[1][1]; - break; - } else if (currentNode[0] === 'atkeyword') { - if (ATRULES.indexOf(currentNode[1][1]) > -1) { - propertyName = '$' + currentNode[1][1]; + // Look for includes: + if (node[i][0] === 'include') { + propertyName = '$include'; + } else { + for (j = 1, nl = node[i].length; j < nl; j++) { + currentNode = node[i][j]; + if (currentNode[0] === 'property') { + propertyName = currentNode[1][0] === 'variable' ? + '$variable' : currentNode[1][1]; + break; + } else if (currentNode[0] === 'atkeyword' && + currentNode[1][1] === 'import') { // Look for imports + propertyName = '$import'; + break; } - break; } } diff --git a/package.json b/package.json index 75d8ab3a..2f37b89f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "commander": "2.0.0", - "gonzales": "1.0.7", + "gonzales-pe": "2.0.0-rc0", "minimatch": "0.2.12", "vow": "0.3.11", "vow-fs": "0.2.3" diff --git a/test/scss.js b/test/scss.js index 5a6b8c22..0659215a 100644 --- a/test/scss.js +++ b/test/scss.js @@ -3,455 +3,399 @@ var assert = require('assert'); describe('SCSS', function() { var comb; + var config; + var input; + var expected; beforeEach(function() { comb = new Comb(); }); + afterEach(function() { + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); + }); + describe('Parsing', function() { it('Should parse nested rules', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top', 'color'] - ] }); - assert.equal( - comb.processString( - 'div { color: tomato; a { top: 0; } }' - ), - 'div { color: tomato; a { top: 0; } }' - ); + ] }; + + input = 'div { color: tomato; a { top: 0; } }'; + + expected = 'div { color: tomato; a { top: 0; } }'; }); it('Should parse parent selector &', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top', 'left', 'color'] - ] }); - assert.equal( - comb.processString( - 'div { color: tomato; &.top { color: nani; top: 0; } left: 0; }' - ), - 'div { left: 0; color: tomato; &.top {top: 0; color: nani; }}' - ); + ] }; + + input = 'div { color: tomato; &.top { color: nani; top: 0; } left: 0; }'; + + expected = 'div { left: 0; color: tomato; &.top {top: 0; color: nani; }}'; }); it('Should parse nested properties', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['left', 'color', 'font'] - ] }); - assert.equal( - comb.processString( - 'div { color: tomato; font: 2px/3px { family: fantasy; size: 30em; } left: 0; }' - ), - 'div {left: 0; color: tomato; font: 2px/3px { family: fantasy; size: 30em; } }' - ); + ] }; + + input = 'div { color: tomato; font: 2px/3px { family: fantasy; size: 30em; } left: 0; }'; + + expected = 'div {left: 0; color: tomato; font: 2px/3px { family: fantasy; size: 30em; } }'; }); it('Should parse variables', function() { - comb.configure({}); - assert.equal( - comb.processString( - '$red: tomato; div { color: $tomato; }' - ), - '$red: tomato; div { color: $tomato; }' - ); + config = {}; + + input = '$red: tomato; div { color: $tomato; }'; + + expected = '$red: tomato; div { color: $tomato; }'; }); it('Should parse interpolated variables inside selectors', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top', 'left', 'color'] - ] }); - assert.equal( - comb.processString( - 'div.#{$nani} {color:tomato;top:0;}' - ), - 'div.#{$nani} {top:0;color:tomato;}' - ); + ] }; + + input = 'div.#{$nani} {color:tomato;top:0;}'; + + expected = 'div.#{$nani} {top:0;color:tomato;}'; }); it('Should parse interpolated variables inside values', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top', 'left', 'color'] - ] }); - assert.equal( - comb.processString( - 'div {color:#{$tomato};top:0;}' - ), - 'div {top:0;color:#{$tomato};}' - ); + ] }; + + input = 'div {color:#{$tomato};top:0;}'; + + expected = 'div {top:0;color:#{$tomato};}'; }); it('Should parse defaults', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top', 'left', 'color'] - ] }); - assert.equal( - comb.processString( - 'div { color: tomato !default; top: 0; }' - ), - 'div {top: 0; color: tomato !default; }' - ); + ] }; + + input = 'div { color: tomato !default; top: 0; }'; + + expected = 'div {top: 0; color: tomato !default; }'; }); it('Should parse @import', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div { @import "foo.css"; top: 0; }' - ), - 'div { @import "foo.css"; top: 0; }' - ); + config = {}; + + input = 'div { @import "foo.css"; top: 0; }'; + + expected = 'div { @import "foo.css"; top: 0; }'; }); it('Should parse @include', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div { @include nani($panda); top: 0; }' - ), - 'div { @include nani($panda); top: 0; }' - ); + config = {}; + + input = 'div { @include nani($panda); top: 0; }'; + + expected = 'div { @include nani($panda); top: 0; }'; }); it('Should parse nested @media', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div {\n' + - ' @media screen and (orientation: landscape) {\n' + - ' color: tomato;\n' + - ' }\n' + - ' top: 0;\n' + - '}' - ), - 'div {\n' + - ' @media screen and (orientation: landscape) {\n' + - ' color: tomato;\n' + - ' }\n' + - ' top: 0;\n' + - '}' - ); + config = {}; + + input = 'div {\n' + + ' @media screen and (orientation: landscape) {\n' + + ' color: tomato;\n' + + ' }\n' + + ' top: 0;\n' + + '}'; + + expected = 'div {\n' + + ' @media screen and (orientation: landscape) {\n' + + ' color: tomato;\n' + + ' }\n' + + ' top: 0;\n' + + '}'; }); it('Should parse @extend with classes', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div { @extend .nani; top: 0; }' - ), - 'div { @extend .nani; top: 0; }' - ); + config = {}; + + input = 'div { @extend .nani; top: 0; }'; + + expected = 'div { @extend .nani; top: 0; }'; }); it('Should parse @extend with placeholders', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div { @extend %nani; top: 0; }' - ), - 'div { @extend %nani; top: 0; }' - ); + config = {}; + + input = 'div { @extend %nani; top: 0; }'; + + expected = 'div { @extend %nani; top: 0; }'; }); it('Should parse @warn', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div { @warn "nani"; top: 0; }' - ), - 'div { @warn "nani"; top: 0; }' - ); + config = {}; + + input = 'div { @warn "nani"; top: 0; }'; + + expected = 'div { @warn "nani"; top: 0; }'; }); it('Should parse @if', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div { @if $type == ocean { top: 0; } }' - ), - 'div { @if $type == ocean { top: 0; } }' - ); + config = {}; + + input = 'div { @if $type == ocean { top: 0; } }'; + + expected = 'div { @if $type == ocean { top: 0; } }'; }); it('Should parse @if and @else', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div { @if $type == ocean { top: 0; } @else { left: 0; } }' - ), - 'div { @if $type == ocean { top: 0; } @else { left: 0; } }' - ); + config = {}; + + input = 'div { @if $type == ocean { top: 0; } @else { left: 0; } }'; + + expected = 'div { @if $type == ocean { top: 0; } @else { left: 0; } }'; }); it('Should parse @if and @else if', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }' - ), - 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }' - ); + config = {}; + + input = 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }'; + + expected = 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }'; }); it('Should parse @for', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div {\n' + - ' @for $i from 1 through 3 {\n' + - ' .item-#{$i} { width: 2em * 1; }\n' + - ' }\n' + - '}' - ), - 'div {\n' + - ' @for $i from 1 through 3 {\n' + - ' .item-#{$i} { width: 2em * 1; }\n' + - ' }\n' + - '}' - ); + config = {}; + + input = 'div {\n' + + ' @for $i from 1 through 3 {\n' + + ' .item-#{$i} { width: 2em * 1; }\n' + + ' }\n' + + '}'; + + expected = 'div {\n' + + ' @for $i from 1 through 3 {\n' + + ' .item-#{$i} { width: 2em * 1; }\n' + + ' }\n' + + '}'; }); it('Should parse @each', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div {\n' + - ' @each $animal in puma, sea-slug, erget {\n' + - ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + - ' }\n' + - '}' - ), - 'div {\n' + - ' @each $animal in puma, sea-slug, erget {\n' + - ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + - ' }\n' + - '}' - ); + config = {}; + + input = 'div {\n' + + ' @each $animal in puma, sea-slug, erget {\n' + + ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + + ' }\n' + + '}'; + + expected = 'div {\n' + + ' @each $animal in puma, sea-slug, erget {\n' + + ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + + ' }\n' + + '}'; }); it('Should parse @while', function() { - comb.configure({}); - assert.equal( - comb.processString( - 'div {\n' + - ' @while $i > 6 {\n' + - ' .item { width: 2em * $i; }\n' + - ' $i: $i - 2;\n' + - ' }\n' + - '}' - ), - 'div {\n' + - ' @while $i > 6 {\n' + - ' .item { width: 2em * $i; }\n' + - ' $i: $i - 2;\n' + - ' }\n' + - '}' - ); + config = {}; + + input = 'div {\n' + + ' @while $i > 6 {\n' + + ' .item { width: 2em * $i; }\n' + + ' $i: $i - 2;\n' + + ' }\n' + + '}'; + + expected = 'div {\n' + + ' @while $i > 6 {\n' + + ' .item { width: 2em * $i; }\n' + + ' $i: $i - 2;\n' + + ' }\n' + + '}'; }); it('Should parse mixins', function() { - comb.configure({}); - assert.equal( - comb.processString( - '@mixin nani { color: tomato; } .foo { @include nani; }' - ), - '@mixin nani { color: tomato; } .foo { @include nani; }' - ); + config = {}; + + input = '@mixin nani { color: tomato; } .foo { @include nani; }'; + + expected = '@mixin nani { color: tomato; } .foo { @include nani; }'; }); it('Should parse passing several variables to a mixin', function() { - comb.configure({}); - assert.equal( - comb.processString( - '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }' - ), - '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }' - ); + config = {}; + + input = '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }'; + + expected = '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }'; }); it('Should parse passing a list of variables to a mixin', function() { - comb.configure({}); - assert.equal( - comb.processString( - '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + - '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }' - ), - '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + - '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }' - ); + config = {}; + + input = '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + + '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }'; + + expected = '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + + '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }'; }); it('Should parse passing a content block to a mixin', function() { - comb.configure({}); - assert.equal( - comb.processString( - '.foo { @include nani { color: tomato; top: 0 } }' - ), - '.foo { @include nani { color: tomato; top: 0 } }' - ); + config = {}; + + input = '.foo { @include nani { color: tomato; top: 0 } }'; + + expected = '.foo { @include nani { color: tomato; top: 0 } }'; }); it('Should parse @content', function() { - comb.configure({}); - assert.equal( - comb.processString( - '@mixin nani { a { @content; } }' - ), - '@mixin nani { a { @content; } }' - ); + config = {}; + + input = '@mixin nani { a { @content; } }'; + + expected = '@mixin nani { a { @content; } }'; }); it('Should parse functions', function() { - comb.configure({}); - assert.equal( - comb.processString( - '@function nani($n) { @return $n * 2; }' - ), - '@function nani($n) { @return $n * 2; }' - ); + config = {}; + + input = '@function nani($n) { @return $n * 2; }'; + + expected = '@function nani($n) { @return $n * 2; }'; }); }); describe('Sorting', function() { it('Should sort properties inside rules', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top', 'color'] - ] }); - assert.equal( - comb.processString( - 'div { color: tomato; top: 0; }' - ), - 'div {top: 0; color: tomato; }' - ); + ] }; + + input = 'div { color: tomato; top: 0; }'; + + expected = 'div {top: 0; color: tomato; }'; }); it('Should sort properties inside nested rules', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top', 'color'] - ] }); - assert.equal( - comb.processString( - 'div { color: tomato; a { color: nani; top: 0; } }' - ), - 'div { color: tomato; a {top: 0; color: nani; } }' - ); + ] }; + + input = 'div { color: tomato; a { color: nani; top: 0; } }'; + + expected = 'div { color: tomato; a {top: 0; color: nani; } }'; }); it('Should sort properties divided by nested rules', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top', 'left', 'color'] - ] }); - assert.equal( - comb.processString( - 'div { color: tomato; a { color: nani; top: 0; } left: 0; }' - ), - 'div { left: 0; color: tomato; a {top: 0; color: nani; }}' - ); + ] }; + + input = 'div { color: tomato; a { color: nani; top: 0; } left: 0; }'; + + expected = 'div { left: 0; color: tomato; a {top: 0; color: nani; }}'; }); it('Should group declarations with proper comments and spaces (multiple lines)', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top', 'color'] - ] }); - assert.equal( - comb.processString( - 'div {\n' + - ' color: tomato; /* 1 */\n' + - ' /* 2 */\n' + - ' /* 3 */\n' + - ' top: 0; /* 4 */\n' + - ' /* 5 */\n' + - '}' - ), - 'div {\n' + - ' /* 2 */\n' + - ' /* 3 */\n' + - ' top: 0; /* 4 */\n' + - ' color: tomato; /* 1 */\n' + - ' /* 5 */\n' + - '}' - ); + ] }; + + input = 'div {\n' + + ' color: tomato; /* 1 */\n' + + ' /* 2 */\n' + + ' /* 3 */\n' + + ' top: 0; /* 4 */\n' + + ' /* 5 */\n' + + '}'; + + expected = 'div {\n' + + ' /* 2 */\n' + + ' /* 3 */\n' + + ' top: 0; /* 4 */\n' + + ' color: tomato; /* 1 */\n' + + ' /* 5 */\n' + + '}'; }); it('Should group declarations with proper comments and spaces (single line)', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top', 'color'] - ] }); - assert.equal( - comb.processString( - 'div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}' - ), - 'div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }' - ); + ] }; + + input = 'div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}'; + + expected = 'div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }'; }); it('Should divide properties from different groups with an empty line', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top'], ['color'] - ] }); - assert.equal( - comb.processString( - 'div {\n' + - ' color: tomato;\n' + - ' top: 0;\n' + - '}' - ), - 'div {\n' + - ' top: 0;\n' + - '\n' + - ' color: tomato;\n' + - '}' - ); + ] }; + + input = 'div {\n' + + ' color: tomato;\n' + + ' top: 0;\n' + + '}'; + + expected = 'div {\n' + + ' top: 0;\n' + + '\n' + + ' color: tomato;\n' + + '}'; }); it('Should sort variables', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['$variable', 'color'] - ] }); - assert.equal( - comb.processString( - 'div { color: $tomato; $red: tomato; }' - ), - 'div {$red: tomato; color: $tomato; }' - ); + ] }; + + input = 'div { color: $tomato; $red: tomato; }'; + + expected = 'div {$red: tomato; color: $tomato; }'; }); it('Should sort imports', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['$import', 'color'] - ] }); - assert.equal(comb.processString( - 'div { color: tomato; @import "foo.css"; }' - ), - 'div {@import "foo.css"; color: tomato; }'); + ] }; + + input = 'div { color: tomato; @import "foo.css"; }'; + + expected = 'div {@import "foo.css"; color: tomato; }'; }); it('Should sort @include-s', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['$include', 'color'] - ] }); - assert.equal(comb.processString( - 'div { color: tomato; @include .nani; }' - ), - 'div {@include .nani; color: tomato; }'); + ] }; + + input = 'div { color: tomato; @include .nani; }'; + + expected = 'div {@include .nani; color: tomato; }'; }); it('Should sort @extend-s', function() { - comb.configure({ 'sort-order': [ - ['$extend', 'color'] - ] }); - assert.equal(comb.processString( - 'div { color: tomato; @extend %nani; }' - ), - 'div {@extend %nani; color: tomato; }'); + config = { 'sort-order': [ + ['$include', 'color'] + ] }; + + input = 'div { color: tomato; @extend %nani; }'; + + expected = 'div {@extend %nani; color: tomato; }'; }); it('Should sort properties inside blocks passed to mixins', function() { - comb.configure({ 'sort-order': [ + config = { 'sort-order': [ ['top', 'color'] - ] }); - assert.equal(comb.processString( - '.foo { @include nani { color: tomato; top: 0; } }' - ), - '.foo { @include nani {top: 0; color: tomato; } }'); + ] }; + + input = '.foo { @include nani { color: tomato; top: 0; } }'; + + expected = '.foo { @include nani {top: 0; color: tomato; } }'; }); }); }); From 2f37068b34cabc4bd73d8dd273f36203859ef4b7 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Sat, 26 Oct 2013 14:56:01 +0400 Subject: [PATCH 07/75] Less: Add tests for sort-order option --- test/less.js | 313 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 test/less.js diff --git a/test/less.js b/test/less.js new file mode 100644 index 00000000..8d8107e7 --- /dev/null +++ b/test/less.js @@ -0,0 +1,313 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); + +describe('LESS', function() { + var comb; + var config; + var input; + var expected; + + beforeEach(function() { + comb = new Comb(); + }); + + afterEach(function() { + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); + }); + + describe('Parsing', function() { + it('Should parse nested rules', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div { color: tomato; a { top: 0; } }'; + + expected = 'div { color: tomato; a { top: 0; } }'; + }); + + it('Should parse operations', function() { + config = {}; + + input = 'div {\n' + + ' @base: 5%;\n' + + ' @filler: @base * 2;\n' + + ' @other: @base + @filler;\n' + + ' color: #888 / 4;\n' + + ' background-color: @base-color + #111;\n' + + ' height: 100% / 2 + @filler;\n' + + ' }'; + + expected = 'div {\n' + + ' @base: 5%;\n' + + ' @filler: @base * 2;\n' + + ' @other: @base + @filler;\n' + + ' color: #888 / 4;\n' + + ' background-color: @base-color + #111;\n' + + ' height: 100% / 2 + @filler;\n' + + ' }'; + }); + + it('Should parse parent selector &', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; + + input = 'div { color: tomato; &.top { color: nani; top: 0; } left: 0; }'; + + expected = 'div { left: 0; color: tomato; &.top {top: 0; color: nani; }}'; + }); + + it('Should parse variables', function() { + config = {}; + + input = '@red: tomato; div { color: @tomato; top: @@foo; }'; + + expected = '@red: tomato; div { color: @tomato; top: @@foo; }'; + }); + + it('Should parse interpolated variables inside selectors', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; + + input = 'div.@{nani} {color:tomato;top:0;}'; + + expected = 'div.@{nani} {top:0;color:tomato;}'; + }); + + it('Should parse interpolated variables inside values', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; + + input = 'div {color:@{tomato};top:0;}'; + + expected = 'div {top:0;color:@{tomato};}'; + }); + + it('Should parse @import', function() { + config = {}; + + input = 'div { @import "foo.css"; top: 0; }'; + + expected = 'div { @import "foo.css"; top: 0; }'; + }); + + it('Should parse included mixins', function() { + config = {}; + + input = 'div { .mixin; top: 0; }'; + + expected = 'div { .mixin; top: 0; }'; + }); + + it('Should parse nested @media', function() { + config = {}; + + input = 'div {\n' + + ' @media screen and (orientation: landscape) {\n' + + ' color: tomato;\n' + + ' }\n' + + ' top: 0;\n' + + '}'; + + expected = 'div {\n' + + ' @media screen and (orientation: landscape) {\n' + + ' color: tomato;\n' + + ' }\n' + + ' top: 0;\n' + + '}'; + }); + }); + + describe('Sorting', function() { + it('Should sort properties inside rules', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div { color: tomato; top: 0; }'; + + expected = 'div {top: 0; color: tomato; }'; + }); + + it('Should sort properties inside nested rules', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div { color: tomato; a { color: nani; top: 0; } }'; + + expected = 'div { color: tomato; a {top: 0; color: nani; } }'; + }); + + it('Should sort properties divided by nested rules', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; + + input = 'div { color: tomato; a { color: nani; top: 0; } left: 0; }'; + + expected = 'div { left: 0; color: tomato; a {top: 0; color: nani; }}'; + }); + + it('Should group declarations with proper comments and spaces (single line)', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}'; + + expected = 'div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }'; + }); + + it('Should group declarations with proper comments and spaces (multiple lines). Test 1', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div {\n' + + ' color: tomato; /* 1 */\n' + + ' /* 2 */\n' + + ' /* 3 */\n' + + ' top: 0; /* 4 */\n' + + ' /* 5 */\n' + + '}'; + + expected = 'div {\n' + + ' /* 2 */\n' + + ' /* 3 */\n' + + ' top: 0; /* 4 */\n' + + ' color: tomato; /* 1 */\n' + + ' /* 5 */\n' + + '}'; + }); + + it('Should group declarations with proper comments and spaces (multiple lines). Test 2', function() { + config = { 'sort-order': [ + ['$variable', 'color'] + ] }; + + input = 'p {\n' + + ' /* One hell of a comment */\n' + + ' color: tomato;\n' + + ' // Get in line!\n' + + ' @var: white;\n' + + ' }'; + + expected = 'p {\n' + + ' // Get in line!\n' + + ' @var: white;\n' + + ' /* One hell of a comment */\n' + + ' color: tomato;\n' + + ' }'; + }); + + it('Should group declarations with proper comments and spaces (multiple lines). Test 3', function() { + config = { 'sort-order': [ + ['$variable', 'color'] + ] }; + + input = 'p {\n' + + ' color: tomato; /* One hell of a comment */\n' + + ' @var: white; // Get in line!\n' + + ' }'; + + expected = 'p {\n' + + ' @var: white; // Get in line!\n' + + ' color: tomato; /* One hell of a comment */\n' + + ' }'; + }); + + it('Should divide properties from different groups with an empty line', function() { + config = { 'sort-order': [ + ['top'], ['color'] + ] }; + + input = 'div {\n' + + ' color: tomato;\n' + + ' top: 0;\n' + + '}'; + + expected = 'div {\n' + + ' top: 0;\n' + + '\n' + + ' color: tomato;\n' + + '}'; + }); + + it('Should sort variables', function() { + config = { 'sort-order': [ + ['$variable', 'color'] + ] }; + + input = 'div { color: @red; @red: tomato; }'; + + expected = 'div {@red: tomato; color: @red; }'; + }); + + it('Should sort imports', function() { + config = { 'sort-order': [ + ['$import', 'color'] + ] }; + + input = 'div { color: tomato; @import "foo.css"; }'; + + expected = 'div {@import "foo.css"; color: tomato; }'; + }); + + it('Should sort included mixins. Test 1', function() { + config = { 'sort-order': [ + ['$include', 'color', 'border-top', 'border-bottom'] + ] }; + + input = '.bordered {\n' + + ' border-bottom: solid 2px black;\n' + + ' border-top: dotted 1px black;\n' + + ' }\n' + + '#menu a {\n' + + ' color: #111;\n' + + ' .bordered;\n' + + ' }\n' + + '.post a {\n' + + ' color: red;\n' + + ' .bordered;\n' + + ' }'; + + expected = '.bordered {\n' + + ' border-top: dotted 1px black;\n' + + ' border-bottom: solid 2px black;\n' + + ' }\n' + + '#menu a {\n' + + ' .bordered;\n' + + ' color: #111;\n' + + ' }\n' + + '.post a {\n' + + ' .bordered;\n' + + ' color: red;\n' + + ' }'; + }); + + it('Should sort included mixins. Test 2', function() { + config = { 'sort-order': [ + ['$include', 'top', 'color'] + ] }; + + input = '.test {\n' + + ' .test1();\n' + + ' color: tomato;\n' + + ' .test2();\n' + + ' top: 0;\n' + + ' }'; + + expected = '.test {\n' + + ' .test1();\n' + + ' .test2();\n' + + ' top: 0;\n' + + ' color: tomato;\n' + + ' }'; + }); + }); +}); From 97196c73ac0d627da0ebf0e50e408efa7dd0159e Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Sat, 26 Oct 2013 15:46:28 +0400 Subject: [PATCH 08/75] Update README Add examples of: - sorting properties in *.scss files - using keywords (`$variable`, `$include` and `$import`) in sort-order config. --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 2edcb2bc..f473a41a 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,35 @@ p { } ``` +If you sort properties in `*.scss` or `*.less` files, you can use one of 3 +keywords in your config: + * `$variable` for variable declarations (e.g. `$var` in Sass or `@var` in LESS); + * `$include` for included mixins (e.g. `@include ...` and `@extend ...` in Sass + or `.mixin()` in LESS); + * `$import` for `@import` rules. + +Example: `{ "sort-order": [ [ "$variable" ], [ "$include" ], [ "top", "padding" ] ] }` + +```scss +/* before */ +p { + padding: 0; + @include mixin($color); + $color: tomato; + top: 0; +} + +/* after */ +p { + $color: tomato; + + @include mixin($color); + + top: 0; + padding: 0; +} +``` + ### stick-brace Available values: From a074488abfd75f5335e1f0af3ff02867741fa358 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Thu, 31 Oct 2013 23:26:44 +0200 Subject: [PATCH 09/75] Sort order: Remove unnecessary test We should not change code while sorting. If we want to replace delimeters, it should be done with another module, but not sort-order. --- test/sort-order.js | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/test/sort-order.js b/test/sort-order.js index ef71325e..21e310ad 100644 --- a/test/sort-order.js +++ b/test/sort-order.js @@ -116,35 +116,4 @@ describe('options/sort-order', function() { assert.equal(comb.processString(input), expected); }); - - /* TODO: We should not change code while sorting. - * If we want to replace delimeters, it should be done with another module, - * but NOT sort-order. - */ - /*it('Should replace custom delimiters by ours', function() { - - var config = { - 'sort-order': [ - ['margin'], - ['padding'] - ] - }; - - var input = 'div p em {\n' + - '\tpadding: 1px;\n' + - ' \n' + - '\tmargin: 1px;\n' + - '}'; - - var expected = 'div p em {\n' + - '\tmargin: 1px;\n' + - '\n' + - '\tpadding: 1px;\n' + - '}'; - - comb.configure(config); - assert.equal(comb.processString(input), expected); - - });*/ - }); From fa747e6ece88fade18dbe52ecd64ad2735eb72fd Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Sat, 2 Nov 2013 09:42:28 +0400 Subject: [PATCH 10/75] Less: Add test for csscomb/csscomb#225 --- test/less.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/less.js b/test/less.js index 8d8107e7..0c35daee 100644 --- a/test/less.js +++ b/test/less.js @@ -309,5 +309,23 @@ describe('LESS', function() { ' color: tomato;\n' + ' }'; }); + + it('Should sort included mixins. Test 3', function() { + config = { 'sort-order': [ + ['$include', 'border', 'color'] + ] }; + + input = '.foo {\n' + + ' color: #0f0;\n' + + ' border: 1px solid #f00;\n' + + ' .linear-gradient(#fff; #000);\n' + + '}'; + + expected = '.foo {\n' + + ' .linear-gradient(#fff; #000);\n' + + ' border: 1px solid #f00;\n' + + ' color: #0f0;\n' + + '}'; + }); }); }); From d3ed28f9006d68c815c378ce77e18adf7d6889e0 Mon Sep 17 00:00:00 2001 From: Denis Payase Date: Thu, 7 Nov 2013 20:22:34 +0400 Subject: [PATCH 11/75] Fix issue #98 --- lib/options/vendor-prefix-align.js | 4 ++-- test/vendor-prefix-align.js | 8 +++++++- test/vendor-prefix-align/both.css | 5 +++++ test/vendor-prefix-align/both.expected.css | 5 +++++ 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 test/vendor-prefix-align/both.css create mode 100644 test/vendor-prefix-align/both.expected.css diff --git a/lib/options/vendor-prefix-align.js b/lib/options/vendor-prefix-align.js index eb087b82..b31beb45 100644 --- a/lib/options/vendor-prefix-align.js +++ b/lib/options/vendor-prefix-align.js @@ -73,8 +73,8 @@ module.exports = { */ _valName: function(item) { return item[0] === 'declaration' && item[2] && item[2][2] && - item[2][2][0] === 'funktion' && item[2][2][1][0] === 'ident' && - item[2][2][1][1]; + ((item[2][2][0] === 'funktion' && item[2][2][1][0] === 'ident' && item[2][2][1][1]) || + (item[2][2][0] === 'ident' && item[2][2][1])); }, /** diff --git a/test/vendor-prefix-align.js b/test/vendor-prefix-align.js index d5cf97b9..d58be6df 100644 --- a/test/vendor-prefix-align.js +++ b/test/vendor-prefix-align.js @@ -35,11 +35,17 @@ describe('options/vendor-prefix-align', function() { assert.equal(comb.processString(input), expected); }); + it('Should correct align prefixes in preoperties and values at the same time', function() { + var input = fs.readFileSync('./test/vendor-prefix-align/both.css', 'utf8'); + var expected = fs.readFileSync('./test/vendor-prefix-align/both.expected.css', 'utf8'); + + assert.equal(comb.processString(input), expected); + }); + it('Should always correctly align prefixes', function() { var input = fs.readFileSync('./test/vendor-prefix-align/complex.css', 'utf8'); var expected = fs.readFileSync('./test/vendor-prefix-align/complex.expected.css', 'utf8'); assert.equal(comb.processString(input), expected); }); - }); diff --git a/test/vendor-prefix-align/both.css b/test/vendor-prefix-align/both.css new file mode 100644 index 00000000..3791d296 --- /dev/null +++ b/test/vendor-prefix-align/both.css @@ -0,0 +1,5 @@ +.serp-block__head_animation_yes +{ + -webkit-transition: -webkit-transform 150ms linear; + transition: transform 150ms linear; +} diff --git a/test/vendor-prefix-align/both.expected.css b/test/vendor-prefix-align/both.expected.css new file mode 100644 index 00000000..f58aa02a --- /dev/null +++ b/test/vendor-prefix-align/both.expected.css @@ -0,0 +1,5 @@ +.serp-block__head_animation_yes +{ + -webkit-transition: -webkit-transform 150ms linear; + transition: transform 150ms linear; +} From 6575105d36fa5f06a0b529d98db0e6885adaa05a Mon Sep 17 00:00:00 2001 From: Denis Payase Date: Mon, 11 Nov 2013 21:33:43 +0400 Subject: [PATCH 12/75] Update getter functions --- lib/options/vendor-prefix-align.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/options/vendor-prefix-align.js b/lib/options/vendor-prefix-align.js index b31beb45..bd6c1c16 100644 --- a/lib/options/vendor-prefix-align.js +++ b/lib/options/vendor-prefix-align.js @@ -60,8 +60,9 @@ module.exports = { * @param {node} item * @returns {String|false|undefined} */ - _declName: function(item) { - return item[0] === 'declaration' && item[1][1][1]; + _getDeclName: function(node) { + if (node[0] !== 'declaration') return; + return node[1][1][1]; }, /** @@ -71,10 +72,13 @@ module.exports = { * @param {node} item * @returns {String|false|undefined} */ - _valName: function(item) { - return item[0] === 'declaration' && item[2] && item[2][2] && - ((item[2][2][0] === 'funktion' && item[2][2][1][0] === 'ident' && item[2][2][1][1]) || - (item[2][2][0] === 'ident' && item[2][2][1])); + _getValName: function(node) { + if (node[0] !== 'declaration' || !node[2] || !node[2][2]) + return; + if (node[2][2][0] === 'ident') + return node[2][2][1]; + if (node[2][2][0] === 'funktion') + return node[2][2][1][1]; }, /** @@ -140,18 +144,18 @@ module.exports = { var _this = this; // Gathering Info - this._walk(node, this._declName, function(info, i) { + this._walk(node, this._getDeclName, function(info, i) { _this._updateDict(info, dict, node[i - 1][1]); }); - this._walk(node, this._valName, function(info, i) { + this._walk(node, this._getValName, function(info, i) { _this._updateDict(info, dict, node[i][2][1][1]); }); // Update nodes - this._walk(node, this._declName, function(info, i) { + this._walk(node, this._getDeclName, function(info, i) { node[i - 1][1] = _this._updateIndent(info, dict, node[i - 1][1]); }); - this._walk(node, this._valName, function(info, i) { + this._walk(node, this._getValName, function(info, i) { node[i][2][1][1] = _this._updateIndent(info, dict, node[i][2][1][1]); }); } From 8abf1950233d090e12abe6f942cdcdbfb623beb8 Mon Sep 17 00:00:00 2001 From: Denis Payase Date: Mon, 11 Nov 2013 21:35:51 +0400 Subject: [PATCH 13/75] Added examples in JSDocs --- lib/options/vendor-prefix-align.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/options/vendor-prefix-align.js b/lib/options/vendor-prefix-align.js index bd6c1c16..3ed8a920 100644 --- a/lib/options/vendor-prefix-align.js +++ b/lib/options/vendor-prefix-align.js @@ -56,9 +56,12 @@ module.exports = { /** * Internal * - * Selector for property name. - * @param {node} item - * @returns {String|false|undefined} + * Return property name. + * e.g. + * for:'color: #fff' + * returns string: 'color' + * @param {node} node + * @returns {String|undefined} */ _getDeclName: function(node) { if (node[0] !== 'declaration') return; @@ -68,9 +71,14 @@ module.exports = { /** * Internal * - * Selector for value name. - * @param {node} item - * @returns {String|false|undefined} + * Return for value name. + * e.g. + * for: '-webkit-transition: -webkit-transform 150ms linear' + * returns string: '-webkit-transform', and + * for: 'background: -webkit-linear-gradient(...)' + * returns string: '-webkit-linear-gradient' + * @param {node}node + * @returns {String|undefined} */ _getValName: function(node) { if (node[0] !== 'declaration' || !node[2] || !node[2][2]) From e4fc38e4441ab59aefde1e17a286cb94c2a52290 Mon Sep 17 00:00:00 2001 From: Denis Payase Date: Mon, 11 Nov 2013 22:30:34 +0400 Subject: [PATCH 14/75] Fix typo in JSDocs --- lib/options/vendor-prefix-align.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/options/vendor-prefix-align.js b/lib/options/vendor-prefix-align.js index 3ed8a920..4b9a5840 100644 --- a/lib/options/vendor-prefix-align.js +++ b/lib/options/vendor-prefix-align.js @@ -58,7 +58,7 @@ module.exports = { * * Return property name. * e.g. - * for:'color: #fff' + * for: 'color: #fff' * returns string: 'color' * @param {node} node * @returns {String|undefined} @@ -71,13 +71,13 @@ module.exports = { /** * Internal * - * Return for value name. + * Return property value name. * e.g. * for: '-webkit-transition: -webkit-transform 150ms linear' * returns string: '-webkit-transform', and * for: 'background: -webkit-linear-gradient(...)' * returns string: '-webkit-linear-gradient' - * @param {node}node + * @param {node} node * @returns {String|undefined} */ _getValName: function(node) { From de940abf29c3ee6fba17213a20fc609948386cde Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Wed, 13 Nov 2013 18:14:01 +0400 Subject: [PATCH 15/75] Tests: Change mocha reporter `spec` -> `dot` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f37b89f..e2eb406c 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,6 @@ "csscomb": "./bin/csscomb" }, "scripts": { - "test": "./node_modules/.bin/jshint-groups && ./node_modules/.bin/jscs . && ./node_modules/.bin/mocha -u bdd -R spec" + "test": "./node_modules/.bin/jshint-groups && ./node_modules/.bin/jscs . && ./node_modules/.bin/mocha -u bdd -R dot" } } From b1da2a6f3487d8eb94b2277c8cf8cb66cbbfaf9d Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Wed, 13 Nov 2013 23:03:15 +0400 Subject: [PATCH 16/75] Fix #93: Check if node exists --- lib/options/rule-indent.js | 2 +- test/rule-indent.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/options/rule-indent.js b/lib/options/rule-indent.js index 5fb76171..470058d0 100644 --- a/lib/options/rule-indent.js +++ b/lib/options/rule-indent.js @@ -22,7 +22,7 @@ module.exports = { */ process: function(nodeType, node, level) { if (nodeType === 'block') { - if (node[0][0] !== 's') { + if (node[0] && node[0][0] !== 's') { node.unshift(['s', '']); } for (var i = 0; i < node.length; i++) { diff --git a/test/rule-indent.js b/test/rule-indent.js index 8c44f94a..3826a82d 100644 --- a/test/rule-indent.js +++ b/test/rule-indent.js @@ -49,4 +49,11 @@ describe('options/rule-indent', function() { 'a { /* foo */\n\tcolor:red; /* bar */\n\n\tbackground: #fff\n}\n' ); }); + it('Valid value should ignore empty blocks', function() { + comb.configure({ 'rule-indent': true }); + assert.equal( + comb.processString('a {}'), + 'a {}' + ); + }); }); From 660afdd86bd6b0dfe2670e5118b1bb3e24d11106 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Wed, 13 Nov 2013 19:24:16 +0400 Subject: [PATCH 17/75] Update Readme (#83) - Update "Installation" section - Add information about using as a Node.js module - Show example of using as a global and a local module --- README.md | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f473a41a..ec7e7064 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,48 @@ This is the new JavaScript version, based on powerful CSS parser [Gonzales PE](h ## Installation +Global installation (use as a command-line tool): + +```bash +npm install csscomb -g +``` + +Local installation (use as a command-line tool within current directory): + ```bash npm install csscomb ``` -To run `csscomb`, you can use the following command from the project root: +To install as a project dependency (package will appear in your dependencies): + +```bash +npm install csscomb --save +``` + +To install as a dev dependency (package will appear in your devDependencies): + +```bash +npm install csscomb --save-dev +``` + +## CLI usage + +To run `csscomb`: + +```bash +csscomb path[ path[...]] +``` + +If you installed the package locally, in the project's directory run: ```bash ./node_modules/.bin/csscomb path[ path[...]] ``` +If you run `csscomb -h`, it will show some helpful information: + ```bash -./node_modules/.bin/csscomb --help +csscomb -h Usage: csscomb [options] @@ -29,10 +59,82 @@ To run `csscomb`, you can use the following command from the project root: -h, --help output usage information -V, --version output the version number + -v, --verbose verbose mode -c, --config [path] configuration file path -l, --lint in case some fixes needed returns an error ``` +## Node.js module usage + +Besides being a great CLI, `csscomb` can be used in Node.js projects (inside +a plugin or as a dev tool): + +```js +// Require: +var Comb = require('csscomb'); + +// Configure: +var comb = new Comb(); +comb.configure(config); + +// Use: +comb.processPath('style.css'); +``` + +### configure(config) + +You must configure csscomb before using and config must be a valid JSON. +See [configuration section](#configuration) for more information. + +### processPath(path) + +Comb a file or a directory. +Warning: This method rewrites the file. + +```js +// One file: +comb.processPath('main.scss'); + +// Whole directory: +comb.processPath('assets/styles'); +``` + +### processDirectory(path) + +Comb all supported files in a directory. +Warning: This method rewrites the files. + +```js +comb.processDirectory('public/css'); +``` + +### processFile(path) + +Comb one file. +If file's syntax is not supported, the file will be ignored. +Warning: This method rewrites the file. + +```js +comb.processFile('print.less'); +``` + +### processString(text, syntax, filename) + +Comb a stylesheet. +If style's syntax is different from `css`, you should pass `syntax` parameter, +too. +`filename` is optional, it's used to print possible errors. + +```js +// Comb a css string: +var css = 'a {top: 0; left: 0}'; +var combedCSS = comb.processString(css); + +// Comb a less string: +var less = '@color: tomato; a {color: @color}'; +var combedLESS = comb.processString(less, 'less'); +``` + ## Configuration `csscomb` is configured using [.csscomb.json](https://github.com/csscomb/csscomb.js/blob/master/.csscomb.json) file, located in the project root. @@ -76,7 +178,8 @@ Available value: `{Boolean}` `true` Config mode: `{ "verbose": true }` ```bash -$ ./bin/csscomb ./test +./bin/csscomb ./test + ✓ test/integral.origin.css test/integral.expect.css @@ -87,8 +190,8 @@ $ ./bin/csscomb ./test CLI mode: ```bash -$ ./bin/csscomb ./test --verbose -$ ./bin/csscomb ./test -v +./bin/csscomb ./test --verbose +./bin/csscomb ./test -v ``` ### always-semicolon From 1ead4c597ce283164c7935151900a336e52a3a70 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Thu, 7 Nov 2013 17:09:46 +0400 Subject: [PATCH 18/75] Fix syntax detecting (close #99) Get file's extension and check if it's present in a list of supported syntaxes. If not, ignore the file. --- lib/csscomb.js | 42 ++++++++++++++++++++++++++++-------------- test/csscomb.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 test/csscomb.js diff --git a/lib/csscomb.js b/lib/csscomb.js index a4ad8d63..ae4b2370 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -11,6 +11,7 @@ var doNothing = function() {}; * @name Comb */ var Comb = function() { + this.SUPPORTED_SYNTAXES = ['css', 'scss', 'less']; this._options = [ 'remove-empty-rulesets', 'always-semicolon', @@ -130,8 +131,7 @@ Comb.prototype = { */ processFile: function(path) { var _this = this; - // TODO: Move extension check into `_shouldProcess` method - if (this._shouldProcess(path) && path.match(/\.[css, scss]$/)) { + if (this._shouldProcessFile(path)) { return vfs.read(path, 'utf8').then(function(data) { var syntax = path.split('.').pop(); var processedData = _this.processString(data, syntax, path); @@ -168,17 +168,15 @@ Comb.prototype = { return vfs.listDir(path).then(function(filenames) { return vow.all(filenames.map(function(filename) { var fullname = path + '/' + filename; - if (_this._shouldProcess(fullname)) { - return vfs.stat(fullname).then(function(stat) { - if (stat.isDirectory()) { - return _this.processDirectory(fullname); - } else if (fullname.match(/\.css$/)) { - return vow.when(_this.processFile(fullname)).then(function(errors) { - if (errors) return errors; - }); - } - }); - } + return vfs.stat(fullname).then(function(stat) { + if (stat.isDirectory()) { + return _this._shouldProcess(fullname) && _this.processDirectory(fullname); + } else { + return vow.when(_this.processFile(fullname)).then(function(errors) { + if (errors) return errors; + }); + } + }); })).then(function(results) { return [].concat.apply([], results); }); @@ -223,8 +221,24 @@ Comb.prototype = { if (exclude[i].match(path)) return false; } return true; - } + }, + + /** + * Returns true if specified path is not in excluded list and has one of + * acceptable extensions. + * + * @returns {Boolean} + */ + _shouldProcessFile: function(path) { + // Get file's extension: + var syntax = path.split('.').pop(); + // Check if syntax is supported. If not, ignore the file: + if (this.SUPPORTED_SYNTAXES.indexOf(syntax) < 0) { + return false; + } + return this._shouldProcess(path); + } }; module.exports = Comb; diff --git a/test/csscomb.js b/test/csscomb.js new file mode 100644 index 00000000..368a46c7 --- /dev/null +++ b/test/csscomb.js @@ -0,0 +1,43 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); + +describe('csscomb methods', function() { + var comb; + + beforeEach(function() { + comb = new Comb(); + comb.configure({ exclude: ['nani/*', 'foo', 'b.css'] }); + }); + + it('shouldProcess(path)', function() { + assert.equal(true, comb._shouldProcess('styles')); + }); + + it('shouldProcess(excluded path)', function() { + assert.equal(false, comb._shouldProcess('foo')); + }); + + it('shouldProcessFile(css)', function() { + assert.equal(true, comb._shouldProcessFile('a.css')); + }); + + it('shouldProcessFile(scss)', function() { + assert.equal(true, comb._shouldProcessFile('a.scss')); + }); + + it('shouldProcessFile(less)', function() { + assert.equal(true, comb._shouldProcessFile('a.less')); + }); + + it('shouldProcessFile(txt)', function() { + assert.equal(false, comb._shouldProcessFile('a.txt')); + }); + + it('shouldProcessFile(css, excluded directory)', function() { + assert.equal(false, comb._shouldProcessFile('nani/a.css')); + }); + + it('shouldProcessFile(css, excluded file)', function() { + assert.equal(false, comb._shouldProcessFile('b.css')); + }); +}); From cd53062ad05b9ab182bc6e58ca10f4938a4f5cdc Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 18 Nov 2013 21:09:13 +0400 Subject: [PATCH 19/75] Update Gonzales to v2.0.1 - `propertyDelim` node was added (that is `:` between properties and values) - `decldelim` node was renamed to `declDelim` - `funktion` node was renamed to `function` --- lib/options/always-semicolon.js | 14 +++++++++++--- lib/options/sort-order.js | 6 +++--- lib/options/vendor-prefix-align.js | 18 ++++++++++-------- package.json | 2 +- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/lib/options/always-semicolon.js b/lib/options/always-semicolon.js index 07e951ad..702ae548 100644 --- a/lib/options/always-semicolon.js +++ b/lib/options/always-semicolon.js @@ -22,17 +22,25 @@ module.exports = { for (var i = node.length; i--;) { var nodeItem = node[i]; var type = nodeItem[0]; - var value = nodeItem[2]; - if (type === 'decldelim') break; + if (type === 'declDelim') break; if (type === 'declaration') { + // Look for value node: + var value; + for (var k = nodeItem.length; k--;) { + if (nodeItem[k][0] === 'value') { + value = nodeItem[k]; + break; + } + } + var space = []; for (var j = value.length; j--;) { if (['s', 'commentML', 'commentSL'].indexOf(value[j][0]) === -1) break; space.unshift(value.splice(j)[0]); } - node.splice.apply(node, [i + 1, 0, ['decldelim']].concat(space)); + node.splice.apply(node, [i + 1, 0, ['declDelim']].concat(space)); break; } } diff --git a/lib/options/sort-order.js b/lib/options/sort-order.js index beb620b9..ce2be533 100644 --- a/lib/options/sort-order.js +++ b/lib/options/sort-order.js @@ -164,7 +164,7 @@ module.exports = { // If there is `;` right after the declaration, save it with the // declaration and mark it for removing from parent node: - if (currentNode && nextNode && nextNode[0] === 'decldelim') { + if (currentNode && nextNode && nextNode[0] === 'declDelim') { extendedNode.delim.push(nextNode); deleted.push(i + 1); i++; @@ -224,7 +224,7 @@ module.exports = { } if (!propertyName) { - deleted = []; + deleted.splice(deleted.length - sc0.length, deleted.length + 1); continue; } @@ -274,7 +274,7 @@ module.exports = { for (j = 0, nl = sc1.length; j < nl; j++) { node.unshift(sc1[j]); } - if (currentNode.delim.length > 0) node.unshift(['decldelim']); + if (currentNode.delim.length > 0) node.unshift(['declDelim']); node.unshift(currentNode.node); for (j = 0, nl = sc0.length; j < nl; j++) { node.unshift(sc0[j]); diff --git a/lib/options/vendor-prefix-align.js b/lib/options/vendor-prefix-align.js index 4b9a5840..5359812f 100644 --- a/lib/options/vendor-prefix-align.js +++ b/lib/options/vendor-prefix-align.js @@ -25,6 +25,7 @@ module.exports = { var result = { baseName: propertyName, prefixLength: 0 }; this._prefixesList.some(function(prefix) { + // TODO: Why don't we store prefixes with `-`? prefix = '-' + prefix + '-'; if (propertyName.indexOf(prefix) !== 0) return; result = { @@ -65,6 +66,7 @@ module.exports = { */ _getDeclName: function(node) { if (node[0] !== 'declaration') return; + // TODO: Check that it's not a variable return node[1][1][1]; }, @@ -81,12 +83,13 @@ module.exports = { * @returns {String|undefined} */ _getValName: function(node) { - if (node[0] !== 'declaration' || !node[2] || !node[2][2]) + // TODO: Check that `node[3]` is the node we need + if (node[0] !== 'declaration' || !node[3] || !node[3]) return; - if (node[2][2][0] === 'ident') - return node[2][2][1]; - if (node[2][2][0] === 'funktion') - return node[2][2][1][1]; + if (node[3][2][0] === 'ident') + return node[3][2][1]; + if (node[3][2][0] === 'function') + return node[3][2][1][1]; }, /** @@ -156,7 +159,7 @@ module.exports = { _this._updateDict(info, dict, node[i - 1][1]); }); this._walk(node, this._getValName, function(info, i) { - _this._updateDict(info, dict, node[i][2][1][1]); + _this._updateDict(info, dict, node[i][3][1][1]); }); // Update nodes @@ -164,8 +167,7 @@ module.exports = { node[i - 1][1] = _this._updateIndent(info, dict, node[i - 1][1]); }); this._walk(node, this._getValName, function(info, i) { - node[i][2][1][1] = _this._updateIndent(info, dict, node[i][2][1][1]); + node[i][3][1][1] = _this._updateIndent(info, dict, node[i][3][1][1]); }); } - }; diff --git a/package.json b/package.json index e2eb406c..9365c1da 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "commander": "2.0.0", - "gonzales-pe": "2.0.0-rc0", + "gonzales-pe": "2.0.1", "minimatch": "0.2.12", "vow": "0.3.11", "vow-fs": "0.2.3" From 2a6c900cdd608fd41626a58a5a0c4e5375370583 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 18 Nov 2013 14:58:37 +0400 Subject: [PATCH 20/75] Always semicolon: support scss and less --- README.md | 41 +++++++ lib/options/always-semicolon.js | 57 ++++++--- test/always-semicolon-less.js | 100 ++++++++++++++++ .../condition-multiline.less | 6 + test/always-semicolon-less/condition.less | 1 + .../include-1-multiline.expected.less | 4 + .../include-1-multiline.less | 4 + .../include-1.expected.less | 1 + test/always-semicolon-less/include-1.less | 1 + .../include-2-multiline.expected.less | 4 + .../include-2-multiline.less | 4 + .../include-2.expected.less | 1 + test/always-semicolon-less/include-2.less | 1 + .../include-3-multiline.expected.less | 4 + .../include-3-multiline.less | 4 + .../include-3.expected.less | 1 + test/always-semicolon-less/include-3.less | 1 + .../include-4-multiline.expected.less | 4 + .../include-4-multiline.less | 4 + .../include-4.expected.less | 1 + test/always-semicolon-less/include-4.less | 1 + .../include-5-multiline.expected.less | 4 + .../include-5-multiline.less | 4 + .../include-5.expected.less | 1 + test/always-semicolon-less/include-5.less | 1 + test/always-semicolon-scss.js | 108 ++++++++++++++++++ .../block-include-multiline.scss | 7 ++ test/always-semicolon-scss/block-include.scss | 1 + .../block-value-multiline.scss | 7 ++ test/always-semicolon-scss/block-value.scss | 1 + .../condition-multiline.scss | 6 + test/always-semicolon-scss/condition.scss | 1 + .../extend-multiline.expected.scss | 4 + .../extend-multiline.scss | 4 + .../extend.expected.scss | 1 + test/always-semicolon-scss/extend.scss | 1 + .../include-1-multiline.expected.scss | 4 + .../include-1-multiline.scss | 4 + .../include-1.expected.scss | 1 + test/always-semicolon-scss/include-1.scss | 1 + .../include-2-multiline.expected.scss | 4 + .../include-2-multiline.scss | 4 + .../include-2.expected.scss | 1 + test/always-semicolon-scss/include-2.scss | 1 + .../always-semicolon-scss/loop-multiline.scss | 6 + test/always-semicolon-scss/loop.scss | 1 + 46 files changed, 406 insertions(+), 17 deletions(-) create mode 100644 test/always-semicolon-less.js create mode 100644 test/always-semicolon-less/condition-multiline.less create mode 100644 test/always-semicolon-less/condition.less create mode 100644 test/always-semicolon-less/include-1-multiline.expected.less create mode 100644 test/always-semicolon-less/include-1-multiline.less create mode 100644 test/always-semicolon-less/include-1.expected.less create mode 100644 test/always-semicolon-less/include-1.less create mode 100644 test/always-semicolon-less/include-2-multiline.expected.less create mode 100644 test/always-semicolon-less/include-2-multiline.less create mode 100644 test/always-semicolon-less/include-2.expected.less create mode 100644 test/always-semicolon-less/include-2.less create mode 100644 test/always-semicolon-less/include-3-multiline.expected.less create mode 100644 test/always-semicolon-less/include-3-multiline.less create mode 100644 test/always-semicolon-less/include-3.expected.less create mode 100644 test/always-semicolon-less/include-3.less create mode 100644 test/always-semicolon-less/include-4-multiline.expected.less create mode 100644 test/always-semicolon-less/include-4-multiline.less create mode 100644 test/always-semicolon-less/include-4.expected.less create mode 100644 test/always-semicolon-less/include-4.less create mode 100644 test/always-semicolon-less/include-5-multiline.expected.less create mode 100644 test/always-semicolon-less/include-5-multiline.less create mode 100644 test/always-semicolon-less/include-5.expected.less create mode 100644 test/always-semicolon-less/include-5.less create mode 100644 test/always-semicolon-scss.js create mode 100644 test/always-semicolon-scss/block-include-multiline.scss create mode 100644 test/always-semicolon-scss/block-include.scss create mode 100644 test/always-semicolon-scss/block-value-multiline.scss create mode 100644 test/always-semicolon-scss/block-value.scss create mode 100644 test/always-semicolon-scss/condition-multiline.scss create mode 100644 test/always-semicolon-scss/condition.scss create mode 100644 test/always-semicolon-scss/extend-multiline.expected.scss create mode 100644 test/always-semicolon-scss/extend-multiline.scss create mode 100644 test/always-semicolon-scss/extend.expected.scss create mode 100644 test/always-semicolon-scss/extend.scss create mode 100644 test/always-semicolon-scss/include-1-multiline.expected.scss create mode 100644 test/always-semicolon-scss/include-1-multiline.scss create mode 100644 test/always-semicolon-scss/include-1.expected.scss create mode 100644 test/always-semicolon-scss/include-1.scss create mode 100644 test/always-semicolon-scss/include-2-multiline.expected.scss create mode 100644 test/always-semicolon-scss/include-2-multiline.scss create mode 100644 test/always-semicolon-scss/include-2.expected.scss create mode 100644 test/always-semicolon-scss/include-2.scss create mode 100644 test/always-semicolon-scss/loop-multiline.scss create mode 100644 test/always-semicolon-scss/loop.scss diff --git a/README.md b/README.md index ec7e7064..3021603e 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,8 @@ CLI mode: ### always-semicolon +Whether to add a semicolon after the last value/mixin. + Available value: `{Boolean}` `true` Example: `{ "always-semicolon": true }` @@ -208,6 +210,45 @@ a { color: red } a { color: red; } ``` +Example: `{ "always-semicolon": true }` (scss file): + +```scss +// before +div { + color: tomato; + @include nani + } + +// after +div { + color: tomato; + @include nani; + } +``` + +Note that in `*.scss` and `*.less` files semicolons are not added after `}` +even if it's part of a value: + +```scss +// before +div { + color: tomato; + font: { + family: fantasy; + size: 16px + } + } + +// after +div { + color: tomato; + font: { + family: fantasy; + size: 16px; + } + } +``` + ### block-indent **Note**: better to use with [rule-indent](#rule-indent) diff --git a/lib/options/always-semicolon.js b/lib/options/always-semicolon.js index 702ae548..2fa7cca8 100644 --- a/lib/options/always-semicolon.js +++ b/lib/options/always-semicolon.js @@ -5,6 +5,7 @@ module.exports = { * * @param {String|Boolean} value Option value * @returns {Object|undefined} + * TODO: This option accepts only boolean */ setValue: function(value) { this._value = value === true; @@ -20,31 +21,53 @@ module.exports = { process: function(nodeType, node) { if (nodeType === 'block') { for (var i = node.length; i--;) { - var nodeItem = node[i]; - var type = nodeItem[0]; + var currentNode = node[i]; + var currentNodeType = currentNode[0]; + var nodeWithoutSemicolon; - if (type === 'declDelim') break; + // Skip nodes that already have `;` at the end: + if (currentNodeType === 'declDelim') break; - if (type === 'declaration') { - // Look for value node: - var value; - for (var k = nodeItem.length; k--;) { - if (nodeItem[k][0] === 'value') { - value = nodeItem[k]; + // Add semicolon only after declarations and includes. + // If current node is include, insert semicolon right into it. + // If it's declaration, look for value node: + if (currentNodeType === 'include') { + nodeWithoutSemicolon = currentNode; + } else if (currentNodeType === 'declaration') { + for (var k = currentNode.length; k--;) { + if (currentNode[k][0] === 'value') { + nodeWithoutSemicolon = currentNode[k]; break; } } + } else { + continue; + } - var space = []; - for (var j = value.length; j--;) { - if (['s', 'commentML', 'commentSL'].indexOf(value[j][0]) === -1) break; - space.unshift(value.splice(j)[0]); - } - node.splice.apply(node, [i + 1, 0, ['declDelim']].concat(space)); - break; + var space = []; + var isBlock = false; + + // Check if there are spaces and comments at the end of the node: + for (var j = nodeWithoutSemicolon.length; j--;) { + var lastNode = nodeWithoutSemicolon[j][0]; + // If the node's last child is block, do not add semicolon: + // TODO: Add syntax check and run the code only for scss + if (lastNode === 'block') { + isBlock = true; + break; + } else if (['s', 'commentML', 'commentSL'].indexOf(lastNode) === -1) break; + + space.unshift(nodeWithoutSemicolon[j]); } + + if (isBlock) break; + + // Temporarily remove last spaces and comments and insert `;` + // before them: + nodeWithoutSemicolon.splice(nodeWithoutSemicolon.length - space.length); + node.splice.apply(node, [i + 1, 0, ['declDelim']].concat(space)); + break; } } } - }; diff --git a/test/always-semicolon-less.js b/test/always-semicolon-less.js new file mode 100644 index 00000000..df436239 --- /dev/null +++ b/test/always-semicolon-less.js @@ -0,0 +1,100 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); +var fs = require('fs'); + +describe('options/always-semicolon (scss)', function() { + var comb; + var input; + var expected; + + function readFile(path) { + return fs.readFileSync('test/always-semicolon-less/' + path, 'utf8'); + } + + beforeEach(function() { + comb = new Comb(); + comb.configure({ 'always-semicolon': true }); + }); + + it('Should not add semicolon to condition (single-line style)', function() { + input = readFile('condition.less'); + + assert.equal(comb.processString(input, 'less'), input); + }); + + it('Should not add semicolon to condition (multi-line style)', function() { + input = readFile('condition-multiline.less'); + + assert.equal(comb.processString(input, 'less'), input); + }); + + it('Should add semicolon to last included mixin if missing. Test 1 (single-line style)', function() { + input = readFile('include-1.less'); + expected = readFile('include-1.expected.less'); + + assert.equal(comb.processString(input, 'less'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 1 (multi-line style)', function() { + input = readFile('include-1-multiline.less'); + expected = readFile('include-1-multiline.expected.less'); + + assert.equal(comb.processString(input, 'less'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 2 (single-line style)', function() { + input = readFile('include-2.less'); + expected = readFile('include-2.expected.less'); + + assert.equal(comb.processString(input, 'less'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 2 (multi-line style)', function() { + input = readFile('include-2-multiline.less'); + expected = readFile('include-2-multiline.expected.less'); + + assert.equal(comb.processString(input, 'less'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 3 (single-line style)', function() { + input = readFile('include-3.less'); + expected = readFile('include-3.expected.less'); + + assert.equal(comb.processString(input, 'less'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 3 (multi-line style)', function() { + input = readFile('include-3-multiline.less'); + expected = readFile('include-3-multiline.expected.less'); + + assert.equal(comb.processString(input, 'less'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 4 (single-line style)', function() { + input = readFile('include-4.less'); + expected = readFile('include-4.expected.less'); + + assert.equal(comb.processString(input, 'less'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 4 (multi-line style)', function() { + input = readFile('include-4-multiline.less'); + expected = readFile('include-4-multiline.expected.less'); + + assert.equal(comb.processString(input, 'less'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 5 (single-line style)', function() { + input = readFile('include-5.less'); + expected = readFile('include-5.expected.less'); + + assert.equal(comb.processString(input, 'less'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 5 (multi-line style)', function() { + input = readFile('include-5-multiline.less'); + expected = readFile('include-5-multiline.expected.less'); + + assert.equal(comb.processString(input, 'less'), expected); + }); +}); diff --git a/test/always-semicolon-less/condition-multiline.less b/test/always-semicolon-less/condition-multiline.less new file mode 100644 index 00000000..b3071171 --- /dev/null +++ b/test/always-semicolon-less/condition-multiline.less @@ -0,0 +1,6 @@ +div { + @color: tomato; + when (@color = tomato) { + top: 0; + } + } diff --git a/test/always-semicolon-less/condition.less b/test/always-semicolon-less/condition.less new file mode 100644 index 00000000..c0c4950f --- /dev/null +++ b/test/always-semicolon-less/condition.less @@ -0,0 +1 @@ +div { @color: tomato; when (@color = tomato) { top: 0; } } diff --git a/test/always-semicolon-less/include-1-multiline.expected.less b/test/always-semicolon-less/include-1-multiline.expected.less new file mode 100644 index 00000000..77cf6660 --- /dev/null +++ b/test/always-semicolon-less/include-1-multiline.expected.less @@ -0,0 +1,4 @@ +div { + color: tomato; + .nani; + } diff --git a/test/always-semicolon-less/include-1-multiline.less b/test/always-semicolon-less/include-1-multiline.less new file mode 100644 index 00000000..9daab170 --- /dev/null +++ b/test/always-semicolon-less/include-1-multiline.less @@ -0,0 +1,4 @@ +div { + color: tomato; + .nani + } diff --git a/test/always-semicolon-less/include-1.expected.less b/test/always-semicolon-less/include-1.expected.less new file mode 100644 index 00000000..9058edc9 --- /dev/null +++ b/test/always-semicolon-less/include-1.expected.less @@ -0,0 +1 @@ +div { color: tomato; .nani; } diff --git a/test/always-semicolon-less/include-1.less b/test/always-semicolon-less/include-1.less new file mode 100644 index 00000000..4e470177 --- /dev/null +++ b/test/always-semicolon-less/include-1.less @@ -0,0 +1 @@ +div { color: tomato; .nani } diff --git a/test/always-semicolon-less/include-2-multiline.expected.less b/test/always-semicolon-less/include-2-multiline.expected.less new file mode 100644 index 00000000..6877e14b --- /dev/null +++ b/test/always-semicolon-less/include-2-multiline.expected.less @@ -0,0 +1,4 @@ +div { + color: tomato; + .nani(2px); + } diff --git a/test/always-semicolon-less/include-2-multiline.less b/test/always-semicolon-less/include-2-multiline.less new file mode 100644 index 00000000..c419ae5f --- /dev/null +++ b/test/always-semicolon-less/include-2-multiline.less @@ -0,0 +1,4 @@ +div { + color: tomato; + .nani(2px) + } diff --git a/test/always-semicolon-less/include-2.expected.less b/test/always-semicolon-less/include-2.expected.less new file mode 100644 index 00000000..3fb43359 --- /dev/null +++ b/test/always-semicolon-less/include-2.expected.less @@ -0,0 +1 @@ +div { color: tomato; .nani(2px); } diff --git a/test/always-semicolon-less/include-2.less b/test/always-semicolon-less/include-2.less new file mode 100644 index 00000000..1ed55fed --- /dev/null +++ b/test/always-semicolon-less/include-2.less @@ -0,0 +1 @@ +div { color: tomato; .nani(2px) } diff --git a/test/always-semicolon-less/include-3-multiline.expected.less b/test/always-semicolon-less/include-3-multiline.expected.less new file mode 100644 index 00000000..d51df782 --- /dev/null +++ b/test/always-semicolon-less/include-3-multiline.expected.less @@ -0,0 +1,4 @@ +div { + color: tomato; + .nani(2px) !important; + } diff --git a/test/always-semicolon-less/include-3-multiline.less b/test/always-semicolon-less/include-3-multiline.less new file mode 100644 index 00000000..4d628b86 --- /dev/null +++ b/test/always-semicolon-less/include-3-multiline.less @@ -0,0 +1,4 @@ +div { + color: tomato; + .nani(2px) !important + } diff --git a/test/always-semicolon-less/include-3.expected.less b/test/always-semicolon-less/include-3.expected.less new file mode 100644 index 00000000..117678f1 --- /dev/null +++ b/test/always-semicolon-less/include-3.expected.less @@ -0,0 +1 @@ +div { color: tomato; .nani(2px) !important; } diff --git a/test/always-semicolon-less/include-3.less b/test/always-semicolon-less/include-3.less new file mode 100644 index 00000000..30988983 --- /dev/null +++ b/test/always-semicolon-less/include-3.less @@ -0,0 +1 @@ +div { color: tomato; .nani(2px) !important } diff --git a/test/always-semicolon-less/include-4-multiline.expected.less b/test/always-semicolon-less/include-4-multiline.expected.less new file mode 100644 index 00000000..1efba9ef --- /dev/null +++ b/test/always-semicolon-less/include-4-multiline.expected.less @@ -0,0 +1,4 @@ +div { + color: tomato; + #bundle > .button; + } diff --git a/test/always-semicolon-less/include-4-multiline.less b/test/always-semicolon-less/include-4-multiline.less new file mode 100644 index 00000000..f16812dc --- /dev/null +++ b/test/always-semicolon-less/include-4-multiline.less @@ -0,0 +1,4 @@ +div { + color: tomato; + #bundle > .button + } diff --git a/test/always-semicolon-less/include-4.expected.less b/test/always-semicolon-less/include-4.expected.less new file mode 100644 index 00000000..1b3c6d88 --- /dev/null +++ b/test/always-semicolon-less/include-4.expected.less @@ -0,0 +1 @@ +div { color: tomato; #bundle > .button; } diff --git a/test/always-semicolon-less/include-4.less b/test/always-semicolon-less/include-4.less new file mode 100644 index 00000000..9221814e --- /dev/null +++ b/test/always-semicolon-less/include-4.less @@ -0,0 +1 @@ +div { color: tomato; #bundle > .button } diff --git a/test/always-semicolon-less/include-5-multiline.expected.less b/test/always-semicolon-less/include-5-multiline.expected.less new file mode 100644 index 00000000..4c15e42d --- /dev/null +++ b/test/always-semicolon-less/include-5-multiline.expected.less @@ -0,0 +1,4 @@ +div { + @color: tomato; + #bundle > .button (@color); + } diff --git a/test/always-semicolon-less/include-5-multiline.less b/test/always-semicolon-less/include-5-multiline.less new file mode 100644 index 00000000..15d03fca --- /dev/null +++ b/test/always-semicolon-less/include-5-multiline.less @@ -0,0 +1,4 @@ +div { + @color: tomato; + #bundle > .button (@color) + } diff --git a/test/always-semicolon-less/include-5.expected.less b/test/always-semicolon-less/include-5.expected.less new file mode 100644 index 00000000..718c5c53 --- /dev/null +++ b/test/always-semicolon-less/include-5.expected.less @@ -0,0 +1 @@ +div { @color: tomato; #bundle > .button (@color); } diff --git a/test/always-semicolon-less/include-5.less b/test/always-semicolon-less/include-5.less new file mode 100644 index 00000000..6bf447cd --- /dev/null +++ b/test/always-semicolon-less/include-5.less @@ -0,0 +1 @@ +div { @color: tomato; #bundle > .button (@color) } diff --git a/test/always-semicolon-scss.js b/test/always-semicolon-scss.js new file mode 100644 index 00000000..290382cb --- /dev/null +++ b/test/always-semicolon-scss.js @@ -0,0 +1,108 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); +var fs = require('fs'); + +describe('options/always-semicolon (scss)', function() { + var comb; + var input; + var expected; + + function readFile(path) { + return fs.readFileSync('test/always-semicolon-scss/' + path, 'utf8'); + } + + beforeEach(function() { + comb = new Comb(); + comb.configure({ 'always-semicolon': true }); + }); + + it('Should not add semicolon if last value is block (singl-line style)', function() { + input = readFile('block-value.scss'); + + assert.equal(comb.processString(input, 'scss'), input); + }); + + it('Should not add semicolon if last value is block (multi-line style)', function() { + input = readFile('block-value-multiline.scss'); + + assert.equal(comb.processString(input, 'scss'), input); + }); + + it('Should add semicolon to last included mixin if missing. Test 1 (single-line style)', function() { + input = readFile('include-1.scss'); + expected = readFile('include-1.expected.scss'); + + assert.equal(comb.processString(input, 'scss'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 1 (multi-line style)', function() { + input = readFile('include-1-multiline.scss'); + expected = readFile('include-1-multiline.expected.scss'); + + assert.equal(comb.processString(input, 'scss'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 2 (single-line style)', function() { + input = readFile('include-2.scss'); + expected = readFile('include-2.expected.scss'); + + assert.equal(comb.processString(input, 'scss'), expected); + }); + + it('Should add semicolon to last included mixin if missing. Test 2 (multi-line style)', function() { + input = readFile('include-2-multiline.scss'); + expected = readFile('include-2-multiline.expected.scss'); + + assert.equal(comb.processString(input, 'scss'), expected); + }); + + it('Should not add semicolon to last included mixin if there is a block (single-line style)', function() { + input = readFile('block-include.scss'); + + assert.equal(comb.processString(input, 'scss'), input); + }); + + it('Should not add semicolon to last included mixin if there is a block (multi-line style)', function() { + input = readFile('block-include-multiline.scss'); + + assert.equal(comb.processString(input, 'scss'), input); + }); + + it('Should add semicolon to last extend if missing (single-line style)', function() { + input = readFile('extend.scss'); + expected = readFile('extend.expected.scss'); + + assert.equal(comb.processString(input, 'scss'), expected); + }); + + it('Should add semicolon to last extend if missing (multi-line style)', function() { + input = readFile('extend-multiline.scss'); + expected = readFile('extend-multiline.expected.scss'); + + assert.equal(comb.processString(input, 'scss'), expected); + }); + + it('Should not add semicolon to condition (single-line style)', function() { + input = readFile('condition.scss'); + + assert.equal(comb.processString(input, 'scss'), input); + }); + + it('Should not add semicolon to condition (multi-line style)', function() { + input = readFile('condition-multiline.scss'); + + assert.equal(comb.processString(input, 'scss'), input); + }); + + it('Should not add semicolon to loop (single-line style)', function() { + input = readFile('loop.scss'); + + assert.equal(comb.processString(input, 'scss'), input); + }); + + it('Should not add semicolon to loop (multi-line style)', function() { + input = readFile('loop-multiline.scss'); + + assert.equal(comb.processString(input, 'scss'), input); + }); +}); diff --git a/test/always-semicolon-scss/block-include-multiline.scss b/test/always-semicolon-scss/block-include-multiline.scss new file mode 100644 index 00000000..a9e5aa98 --- /dev/null +++ b/test/always-semicolon-scss/block-include-multiline.scss @@ -0,0 +1,7 @@ +div { + color: tomato; + @include nani { + top: 0; + content: ''; + } + } diff --git a/test/always-semicolon-scss/block-include.scss b/test/always-semicolon-scss/block-include.scss new file mode 100644 index 00000000..251fa784 --- /dev/null +++ b/test/always-semicolon-scss/block-include.scss @@ -0,0 +1 @@ +div { color: tomato; @include nani { top: 0; content: ''; } } diff --git a/test/always-semicolon-scss/block-value-multiline.scss b/test/always-semicolon-scss/block-value-multiline.scss new file mode 100644 index 00000000..f654043d --- /dev/null +++ b/test/always-semicolon-scss/block-value-multiline.scss @@ -0,0 +1,7 @@ +div { + color: tomato; + font: { + family: fantasy; + size: 16px; + } + } diff --git a/test/always-semicolon-scss/block-value.scss b/test/always-semicolon-scss/block-value.scss new file mode 100644 index 00000000..15f19e27 --- /dev/null +++ b/test/always-semicolon-scss/block-value.scss @@ -0,0 +1 @@ +div { color: tomato; font: { family: fantasy; size: 16px; } } diff --git a/test/always-semicolon-scss/condition-multiline.scss b/test/always-semicolon-scss/condition-multiline.scss new file mode 100644 index 00000000..7aeb8afa --- /dev/null +++ b/test/always-semicolon-scss/condition-multiline.scss @@ -0,0 +1,6 @@ +div { + $color: tomato; + @if $color == tomato { + top: 0; + } + } diff --git a/test/always-semicolon-scss/condition.scss b/test/always-semicolon-scss/condition.scss new file mode 100644 index 00000000..692cbcae --- /dev/null +++ b/test/always-semicolon-scss/condition.scss @@ -0,0 +1 @@ +div { $color: tomato; @if $color == tomato { top: 0; } } diff --git a/test/always-semicolon-scss/extend-multiline.expected.scss b/test/always-semicolon-scss/extend-multiline.expected.scss new file mode 100644 index 00000000..eaaa0c75 --- /dev/null +++ b/test/always-semicolon-scss/extend-multiline.expected.scss @@ -0,0 +1,4 @@ +div { + color: tomato; + @extend .nani; + } diff --git a/test/always-semicolon-scss/extend-multiline.scss b/test/always-semicolon-scss/extend-multiline.scss new file mode 100644 index 00000000..6493e728 --- /dev/null +++ b/test/always-semicolon-scss/extend-multiline.scss @@ -0,0 +1,4 @@ +div { + color: tomato; + @extend .nani + } diff --git a/test/always-semicolon-scss/extend.expected.scss b/test/always-semicolon-scss/extend.expected.scss new file mode 100644 index 00000000..ec45b2e0 --- /dev/null +++ b/test/always-semicolon-scss/extend.expected.scss @@ -0,0 +1 @@ +div { color: tomato; @extend .nani; } diff --git a/test/always-semicolon-scss/extend.scss b/test/always-semicolon-scss/extend.scss new file mode 100644 index 00000000..8f545cc6 --- /dev/null +++ b/test/always-semicolon-scss/extend.scss @@ -0,0 +1 @@ +div { color: tomato; @extend .nani } diff --git a/test/always-semicolon-scss/include-1-multiline.expected.scss b/test/always-semicolon-scss/include-1-multiline.expected.scss new file mode 100644 index 00000000..d8db3e32 --- /dev/null +++ b/test/always-semicolon-scss/include-1-multiline.expected.scss @@ -0,0 +1,4 @@ +div { + color: tomato; + @include nani; + } diff --git a/test/always-semicolon-scss/include-1-multiline.scss b/test/always-semicolon-scss/include-1-multiline.scss new file mode 100644 index 00000000..bf95505c --- /dev/null +++ b/test/always-semicolon-scss/include-1-multiline.scss @@ -0,0 +1,4 @@ +div { + color: tomato; + @include nani + } diff --git a/test/always-semicolon-scss/include-1.expected.scss b/test/always-semicolon-scss/include-1.expected.scss new file mode 100644 index 00000000..2feaaec8 --- /dev/null +++ b/test/always-semicolon-scss/include-1.expected.scss @@ -0,0 +1 @@ +div { color: tomato; @include nani; } diff --git a/test/always-semicolon-scss/include-1.scss b/test/always-semicolon-scss/include-1.scss new file mode 100644 index 00000000..204e35d3 --- /dev/null +++ b/test/always-semicolon-scss/include-1.scss @@ -0,0 +1 @@ +div { color: tomato; @include nani } diff --git a/test/always-semicolon-scss/include-2-multiline.expected.scss b/test/always-semicolon-scss/include-2-multiline.expected.scss new file mode 100644 index 00000000..e0c36008 --- /dev/null +++ b/test/always-semicolon-scss/include-2-multiline.expected.scss @@ -0,0 +1,4 @@ +div { + color: tomato; + @include nani(10); + } diff --git a/test/always-semicolon-scss/include-2-multiline.scss b/test/always-semicolon-scss/include-2-multiline.scss new file mode 100644 index 00000000..ad05456d --- /dev/null +++ b/test/always-semicolon-scss/include-2-multiline.scss @@ -0,0 +1,4 @@ +div { + color: tomato; + @include nani(10) + } diff --git a/test/always-semicolon-scss/include-2.expected.scss b/test/always-semicolon-scss/include-2.expected.scss new file mode 100644 index 00000000..87719565 --- /dev/null +++ b/test/always-semicolon-scss/include-2.expected.scss @@ -0,0 +1 @@ +div { color: tomato; @include nani(10); } diff --git a/test/always-semicolon-scss/include-2.scss b/test/always-semicolon-scss/include-2.scss new file mode 100644 index 00000000..f5bccc9f --- /dev/null +++ b/test/always-semicolon-scss/include-2.scss @@ -0,0 +1 @@ +div { color: tomato; @include nani(10) } diff --git a/test/always-semicolon-scss/loop-multiline.scss b/test/always-semicolon-scss/loop-multiline.scss new file mode 100644 index 00000000..5768e29b --- /dev/null +++ b/test/always-semicolon-scss/loop-multiline.scss @@ -0,0 +1,6 @@ +div { + color: tomato; + @while 1 > 2 { + top: 0; + } + } diff --git a/test/always-semicolon-scss/loop.scss b/test/always-semicolon-scss/loop.scss new file mode 100644 index 00000000..afb46f9c --- /dev/null +++ b/test/always-semicolon-scss/loop.scss @@ -0,0 +1 @@ +div { color: tomato; @while 1 > 2 { top: 0; } } From 4b5382df8726511c78b13baf15a21f80762ae9b3 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Thu, 14 Nov 2013 00:17:56 +0400 Subject: [PATCH 21/75] Tests: Divide parsing and sorting tests --- test/less.js | 367 +++++++----------------------- test/scss.js | 487 +++++++++++++++------------------------- test/sort-order-less.js | 223 ++++++++++++++++++ test/sort-order-scss.js | 147 ++++++++++++ 4 files changed, 626 insertions(+), 598 deletions(-) create mode 100644 test/sort-order-less.js create mode 100644 test/sort-order-scss.js diff --git a/test/less.js b/test/less.js index 0c35daee..2163f11b 100644 --- a/test/less.js +++ b/test/less.js @@ -16,316 +16,107 @@ describe('LESS', function() { assert.equal(comb.processString(input, 'less'), expected); }); - describe('Parsing', function() { - it('Should parse nested rules', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; + it('Should parse nested rules', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; - input = 'div { color: tomato; a { top: 0; } }'; + input = 'div { color: tomato; a { top: 0; } }'; - expected = 'div { color: tomato; a { top: 0; } }'; - }); - - it('Should parse operations', function() { - config = {}; - - input = 'div {\n' + - ' @base: 5%;\n' + - ' @filler: @base * 2;\n' + - ' @other: @base + @filler;\n' + - ' color: #888 / 4;\n' + - ' background-color: @base-color + #111;\n' + - ' height: 100% / 2 + @filler;\n' + - ' }'; - - expected = 'div {\n' + - ' @base: 5%;\n' + - ' @filler: @base * 2;\n' + - ' @other: @base + @filler;\n' + - ' color: #888 / 4;\n' + - ' background-color: @base-color + #111;\n' + - ' height: 100% / 2 + @filler;\n' + - ' }'; - }); - - it('Should parse parent selector &', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div { color: tomato; &.top { color: nani; top: 0; } left: 0; }'; - - expected = 'div { left: 0; color: tomato; &.top {top: 0; color: nani; }}'; - }); - - it('Should parse variables', function() { - config = {}; - - input = '@red: tomato; div { color: @tomato; top: @@foo; }'; - - expected = '@red: tomato; div { color: @tomato; top: @@foo; }'; - }); - - it('Should parse interpolated variables inside selectors', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div.@{nani} {color:tomato;top:0;}'; - - expected = 'div.@{nani} {top:0;color:tomato;}'; - }); - - it('Should parse interpolated variables inside values', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div {color:@{tomato};top:0;}'; - - expected = 'div {top:0;color:@{tomato};}'; - }); - - it('Should parse @import', function() { - config = {}; - - input = 'div { @import "foo.css"; top: 0; }'; - - expected = 'div { @import "foo.css"; top: 0; }'; - }); - - it('Should parse included mixins', function() { - config = {}; - - input = 'div { .mixin; top: 0; }'; - - expected = 'div { .mixin; top: 0; }'; - }); - - it('Should parse nested @media', function() { - config = {}; - - input = 'div {\n' + - ' @media screen and (orientation: landscape) {\n' + - ' color: tomato;\n' + - ' }\n' + - ' top: 0;\n' + - '}'; - - expected = 'div {\n' + - ' @media screen and (orientation: landscape) {\n' + - ' color: tomato;\n' + - ' }\n' + - ' top: 0;\n' + - '}'; - }); + expected = 'div { color: tomato; a { top: 0; } }'; }); - describe('Sorting', function() { - it('Should sort properties inside rules', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; - - input = 'div { color: tomato; top: 0; }'; - - expected = 'div {top: 0; color: tomato; }'; - }); - - it('Should sort properties inside nested rules', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; - - input = 'div { color: tomato; a { color: nani; top: 0; } }'; - - expected = 'div { color: tomato; a {top: 0; color: nani; } }'; - }); - - it('Should sort properties divided by nested rules', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div { color: tomato; a { color: nani; top: 0; } left: 0; }'; - - expected = 'div { left: 0; color: tomato; a {top: 0; color: nani; }}'; - }); - - it('Should group declarations with proper comments and spaces (single line)', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; - - input = 'div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}'; - - expected = 'div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }'; - }); - - it('Should group declarations with proper comments and spaces (multiple lines). Test 1', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; - - input = 'div {\n' + - ' color: tomato; /* 1 */\n' + - ' /* 2 */\n' + - ' /* 3 */\n' + - ' top: 0; /* 4 */\n' + - ' /* 5 */\n' + - '}'; - - expected = 'div {\n' + - ' /* 2 */\n' + - ' /* 3 */\n' + - ' top: 0; /* 4 */\n' + - ' color: tomato; /* 1 */\n' + - ' /* 5 */\n' + - '}'; - }); - - it('Should group declarations with proper comments and spaces (multiple lines). Test 2', function() { - config = { 'sort-order': [ - ['$variable', 'color'] - ] }; - - input = 'p {\n' + - ' /* One hell of a comment */\n' + - ' color: tomato;\n' + - ' // Get in line!\n' + - ' @var: white;\n' + - ' }'; - - expected = 'p {\n' + - ' // Get in line!\n' + - ' @var: white;\n' + - ' /* One hell of a comment */\n' + - ' color: tomato;\n' + - ' }'; - }); - - it('Should group declarations with proper comments and spaces (multiple lines). Test 3', function() { - config = { 'sort-order': [ - ['$variable', 'color'] - ] }; - - input = 'p {\n' + - ' color: tomato; /* One hell of a comment */\n' + - ' @var: white; // Get in line!\n' + - ' }'; + it('Should parse operations', function() { + config = {}; + + input = 'div {\n' + + ' @base: 5%;\n' + + ' @filler: @base * 2;\n' + + ' @other: @base + @filler;\n' + + ' color: #888 / 4;\n' + + ' background-color: @base-color + #111;\n' + + ' height: 100% / 2 + @filler;\n' + + ' }'; + + expected = 'div {\n' + + ' @base: 5%;\n' + + ' @filler: @base * 2;\n' + + ' @other: @base + @filler;\n' + + ' color: #888 / 4;\n' + + ' background-color: @base-color + #111;\n' + + ' height: 100% / 2 + @filler;\n' + + ' }'; + }); - expected = 'p {\n' + - ' @var: white; // Get in line!\n' + - ' color: tomato; /* One hell of a comment */\n' + - ' }'; - }); + it('Should parse parent selector &', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; - it('Should divide properties from different groups with an empty line', function() { - config = { 'sort-order': [ - ['top'], ['color'] - ] }; + input = 'div { color: tomato; &.top { color: nani; top: 0; } left: 0; }'; - input = 'div {\n' + - ' color: tomato;\n' + - ' top: 0;\n' + - '}'; + expected = 'div { left: 0; color: tomato; &.top {top: 0; color: nani; }}'; + }); - expected = 'div {\n' + - ' top: 0;\n' + - '\n' + - ' color: tomato;\n' + - '}'; - }); + it('Should parse variables', function() { + config = {}; - it('Should sort variables', function() { - config = { 'sort-order': [ - ['$variable', 'color'] - ] }; + input = '@red: tomato; div { color: @tomato; top: @@foo; }'; - input = 'div { color: @red; @red: tomato; }'; + expected = '@red: tomato; div { color: @tomato; top: @@foo; }'; + }); - expected = 'div {@red: tomato; color: @red; }'; - }); + it('Should parse interpolated variables inside selectors', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; - it('Should sort imports', function() { - config = { 'sort-order': [ - ['$import', 'color'] - ] }; + input = 'div.@{nani} {color:tomato;top:0;}'; - input = 'div { color: tomato; @import "foo.css"; }'; + expected = 'div.@{nani} {top:0;color:tomato;}'; + }); - expected = 'div {@import "foo.css"; color: tomato; }'; - }); + it('Should parse interpolated variables inside values', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; - it('Should sort included mixins. Test 1', function() { - config = { 'sort-order': [ - ['$include', 'color', 'border-top', 'border-bottom'] - ] }; + input = 'div {color:@{tomato};top:0;}'; - input = '.bordered {\n' + - ' border-bottom: solid 2px black;\n' + - ' border-top: dotted 1px black;\n' + - ' }\n' + - '#menu a {\n' + - ' color: #111;\n' + - ' .bordered;\n' + - ' }\n' + - '.post a {\n' + - ' color: red;\n' + - ' .bordered;\n' + - ' }'; + expected = 'div {top:0;color:@{tomato};}'; + }); - expected = '.bordered {\n' + - ' border-top: dotted 1px black;\n' + - ' border-bottom: solid 2px black;\n' + - ' }\n' + - '#menu a {\n' + - ' .bordered;\n' + - ' color: #111;\n' + - ' }\n' + - '.post a {\n' + - ' .bordered;\n' + - ' color: red;\n' + - ' }'; - }); + it('Should parse @import', function() { + config = {}; - it('Should sort included mixins. Test 2', function() { - config = { 'sort-order': [ - ['$include', 'top', 'color'] - ] }; + input = 'div { @import "foo.css"; top: 0; }'; - input = '.test {\n' + - ' .test1();\n' + - ' color: tomato;\n' + - ' .test2();\n' + - ' top: 0;\n' + - ' }'; + expected = 'div { @import "foo.css"; top: 0; }'; + }); - expected = '.test {\n' + - ' .test1();\n' + - ' .test2();\n' + - ' top: 0;\n' + - ' color: tomato;\n' + - ' }'; - }); + it('Should parse included mixins', function() { + config = {}; - it('Should sort included mixins. Test 3', function() { - config = { 'sort-order': [ - ['$include', 'border', 'color'] - ] }; + input = 'div { .mixin; top: 0; }'; - input = '.foo {\n' + - ' color: #0f0;\n' + - ' border: 1px solid #f00;\n' + - ' .linear-gradient(#fff; #000);\n' + - '}'; + expected = 'div { .mixin; top: 0; }'; + }); - expected = '.foo {\n' + - ' .linear-gradient(#fff; #000);\n' + - ' border: 1px solid #f00;\n' + - ' color: #0f0;\n' + - '}'; - }); + it('Should parse nested @media', function() { + config = {}; + + input = 'div {\n' + + ' @media screen and (orientation: landscape) {\n' + + ' color: tomato;\n' + + ' }\n' + + ' top: 0;\n' + + '}'; + + expected = 'div {\n' + + ' @media screen and (orientation: landscape) {\n' + + ' color: tomato;\n' + + ' }\n' + + ' top: 0;\n' + + '}'; }); }); diff --git a/test/scss.js b/test/scss.js index 0659215a..8949479e 100644 --- a/test/scss.js +++ b/test/scss.js @@ -16,386 +16,253 @@ describe('SCSS', function() { assert.equal(comb.processString(input, 'scss'), expected); }); - describe('Parsing', function() { - it('Should parse nested rules', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; + it('Should parse nested rules', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; - input = 'div { color: tomato; a { top: 0; } }'; + input = 'div { color: tomato; a { top: 0; } }'; - expected = 'div { color: tomato; a { top: 0; } }'; - }); - - it('Should parse parent selector &', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div { color: tomato; &.top { color: nani; top: 0; } left: 0; }'; - - expected = 'div { left: 0; color: tomato; &.top {top: 0; color: nani; }}'; - }); - - it('Should parse nested properties', function() { - config = { 'sort-order': [ - ['left', 'color', 'font'] - ] }; - - input = 'div { color: tomato; font: 2px/3px { family: fantasy; size: 30em; } left: 0; }'; - - expected = 'div {left: 0; color: tomato; font: 2px/3px { family: fantasy; size: 30em; } }'; - }); - - it('Should parse variables', function() { - config = {}; - - input = '$red: tomato; div { color: $tomato; }'; - - expected = '$red: tomato; div { color: $tomato; }'; - }); - - it('Should parse interpolated variables inside selectors', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div.#{$nani} {color:tomato;top:0;}'; - - expected = 'div.#{$nani} {top:0;color:tomato;}'; - }); - - it('Should parse interpolated variables inside values', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div {color:#{$tomato};top:0;}'; - - expected = 'div {top:0;color:#{$tomato};}'; - }); - - it('Should parse defaults', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div { color: tomato !default; top: 0; }'; - - expected = 'div {top: 0; color: tomato !default; }'; - }); - - it('Should parse @import', function() { - config = {}; - - input = 'div { @import "foo.css"; top: 0; }'; - - expected = 'div { @import "foo.css"; top: 0; }'; - }); - - it('Should parse @include', function() { - config = {}; - - input = 'div { @include nani($panda); top: 0; }'; - - expected = 'div { @include nani($panda); top: 0; }'; - }); - - it('Should parse nested @media', function() { - config = {}; - - input = 'div {\n' + - ' @media screen and (orientation: landscape) {\n' + - ' color: tomato;\n' + - ' }\n' + - ' top: 0;\n' + - '}'; - - expected = 'div {\n' + - ' @media screen and (orientation: landscape) {\n' + - ' color: tomato;\n' + - ' }\n' + - ' top: 0;\n' + - '}'; - }); - - it('Should parse @extend with classes', function() { - config = {}; - - input = 'div { @extend .nani; top: 0; }'; - - expected = 'div { @extend .nani; top: 0; }'; - }); - - it('Should parse @extend with placeholders', function() { - config = {}; - - input = 'div { @extend %nani; top: 0; }'; - - expected = 'div { @extend %nani; top: 0; }'; - }); - - it('Should parse @warn', function() { - config = {}; - - input = 'div { @warn "nani"; top: 0; }'; + expected = 'div { color: tomato; a { top: 0; } }'; + }); - expected = 'div { @warn "nani"; top: 0; }'; - }); + it('Should parse parent selector &', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; - it('Should parse @if', function() { - config = {}; + input = 'div { color: tomato; &.top { color: nani; top: 0; } left: 0; }'; - input = 'div { @if $type == ocean { top: 0; } }'; + expected = 'div { left: 0; color: tomato; &.top {top: 0; color: nani; }}'; + }); - expected = 'div { @if $type == ocean { top: 0; } }'; - }); + it('Should parse nested properties', function() { + config = { 'sort-order': [ + ['left', 'color', 'font'] + ] }; - it('Should parse @if and @else', function() { - config = {}; + input = 'div { color: tomato; font: 2px/3px { family: fantasy; size: 30em; } left: 0; }'; - input = 'div { @if $type == ocean { top: 0; } @else { left: 0; } }'; + expected = 'div {left: 0; color: tomato; font: 2px/3px { family: fantasy; size: 30em; } }'; + }); - expected = 'div { @if $type == ocean { top: 0; } @else { left: 0; } }'; - }); + it('Should parse variables', function() { + config = {}; - it('Should parse @if and @else if', function() { - config = {}; + input = '$red: tomato; div { color: $tomato; }'; - input = 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }'; + expected = '$red: tomato; div { color: $tomato; }'; + }); - expected = 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }'; - }); + it('Should parse interpolated variables inside selectors', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; - it('Should parse @for', function() { - config = {}; + input = 'div.#{$nani} {color:tomato;top:0;}'; - input = 'div {\n' + - ' @for $i from 1 through 3 {\n' + - ' .item-#{$i} { width: 2em * 1; }\n' + - ' }\n' + - '}'; + expected = 'div.#{$nani} {top:0;color:tomato;}'; + }); - expected = 'div {\n' + - ' @for $i from 1 through 3 {\n' + - ' .item-#{$i} { width: 2em * 1; }\n' + - ' }\n' + - '}'; - }); + it('Should parse interpolated variables inside values', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; - it('Should parse @each', function() { - config = {}; + input = 'div {color:#{$tomato};top:0;}'; - input = 'div {\n' + - ' @each $animal in puma, sea-slug, erget {\n' + - ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + - ' }\n' + - '}'; + expected = 'div {top:0;color:#{$tomato};}'; + }); - expected = 'div {\n' + - ' @each $animal in puma, sea-slug, erget {\n' + - ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + - ' }\n' + - '}'; - }); + it('Should parse defaults', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; - it('Should parse @while', function() { - config = {}; + input = 'div { color: tomato !default; top: 0; }'; - input = 'div {\n' + - ' @while $i > 6 {\n' + - ' .item { width: 2em * $i; }\n' + - ' $i: $i - 2;\n' + - ' }\n' + - '}'; + expected = 'div {top: 0; color: tomato !default; }'; + }); - expected = 'div {\n' + - ' @while $i > 6 {\n' + - ' .item { width: 2em * $i; }\n' + - ' $i: $i - 2;\n' + - ' }\n' + - '}'; - }); + it('Should parse @import', function() { + config = {}; - it('Should parse mixins', function() { - config = {}; + input = 'div { @import "foo.css"; top: 0; }'; - input = '@mixin nani { color: tomato; } .foo { @include nani; }'; + expected = 'div { @import "foo.css"; top: 0; }'; + }); - expected = '@mixin nani { color: tomato; } .foo { @include nani; }'; - }); + it('Should parse @include', function() { + config = {}; - it('Should parse passing several variables to a mixin', function() { - config = {}; + input = 'div { @include nani($panda); top: 0; }'; - input = '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }'; + expected = 'div { @include nani($panda); top: 0; }'; + }); - expected = '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }'; - }); + it('Should parse nested @media', function() { + config = {}; + + input = 'div {\n' + + ' @media screen and (orientation: landscape) {\n' + + ' color: tomato;\n' + + ' }\n' + + ' top: 0;\n' + + '}'; + + expected = 'div {\n' + + ' @media screen and (orientation: landscape) {\n' + + ' color: tomato;\n' + + ' }\n' + + ' top: 0;\n' + + '}'; + }); - it('Should parse passing a list of variables to a mixin', function() { - config = {}; + it('Should parse @extend with classes', function() { + config = {}; - input = '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + - '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }'; + input = 'div { @extend .nani; top: 0; }'; - expected = '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + - '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }'; - }); + expected = 'div { @extend .nani; top: 0; }'; + }); - it('Should parse passing a content block to a mixin', function() { - config = {}; + it('Should parse @extend with placeholders', function() { + config = {}; - input = '.foo { @include nani { color: tomato; top: 0 } }'; + input = 'div { @extend %nani; top: 0; }'; - expected = '.foo { @include nani { color: tomato; top: 0 } }'; - }); + expected = 'div { @extend %nani; top: 0; }'; + }); - it('Should parse @content', function() { - config = {}; + it('Should parse @warn', function() { + config = {}; - input = '@mixin nani { a { @content; } }'; + input = 'div { @warn "nani"; top: 0; }'; - expected = '@mixin nani { a { @content; } }'; - }); + expected = 'div { @warn "nani"; top: 0; }'; + }); - it('Should parse functions', function() { - config = {}; + it('Should parse @if', function() { + config = {}; - input = '@function nani($n) { @return $n * 2; }'; + input = 'div { @if $type == ocean { top: 0; } }'; - expected = '@function nani($n) { @return $n * 2; }'; - }); + expected = 'div { @if $type == ocean { top: 0; } }'; }); - describe('Sorting', function() { - it('Should sort properties inside rules', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; - - input = 'div { color: tomato; top: 0; }'; + it('Should parse @if and @else', function() { + config = {}; - expected = 'div {top: 0; color: tomato; }'; - }); + input = 'div { @if $type == ocean { top: 0; } @else { left: 0; } }'; - it('Should sort properties inside nested rules', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; - - input = 'div { color: tomato; a { color: nani; top: 0; } }'; + expected = 'div { @if $type == ocean { top: 0; } @else { left: 0; } }'; + }); - expected = 'div { color: tomato; a {top: 0; color: nani; } }'; - }); + it('Should parse @if and @else if', function() { + config = {}; - it('Should sort properties divided by nested rules', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; + input = 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }'; - input = 'div { color: tomato; a { color: nani; top: 0; } left: 0; }'; + expected = 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }'; + }); - expected = 'div { left: 0; color: tomato; a {top: 0; color: nani; }}'; - }); + it('Should parse @for', function() { + config = {}; - it('Should group declarations with proper comments and spaces (multiple lines)', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; + input = 'div {\n' + + ' @for $i from 1 through 3 {\n' + + ' .item-#{$i} { width: 2em * 1; }\n' + + ' }\n' + + '}'; - input = 'div {\n' + - ' color: tomato; /* 1 */\n' + - ' /* 2 */\n' + - ' /* 3 */\n' + - ' top: 0; /* 4 */\n' + - ' /* 5 */\n' + - '}'; + expected = 'div {\n' + + ' @for $i from 1 through 3 {\n' + + ' .item-#{$i} { width: 2em * 1; }\n' + + ' }\n' + + '}'; + }); - expected = 'div {\n' + - ' /* 2 */\n' + - ' /* 3 */\n' + - ' top: 0; /* 4 */\n' + - ' color: tomato; /* 1 */\n' + - ' /* 5 */\n' + - '}'; - }); + it('Should parse @each', function() { + config = {}; - it('Should group declarations with proper comments and spaces (single line)', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; + input = 'div {\n' + + ' @each $animal in puma, sea-slug, erget {\n' + + ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + + ' }\n' + + '}'; - input = 'div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}'; + expected = 'div {\n' + + ' @each $animal in puma, sea-slug, erget {\n' + + ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + + ' }\n' + + '}'; + }); - expected = 'div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }'; - }); + it('Should parse @while', function() { + config = {}; + + input = 'div {\n' + + ' @while $i > 6 {\n' + + ' .item { width: 2em * $i; }\n' + + ' $i: $i - 2;\n' + + ' }\n' + + '}'; + + expected = 'div {\n' + + ' @while $i > 6 {\n' + + ' .item { width: 2em * $i; }\n' + + ' $i: $i - 2;\n' + + ' }\n' + + '}'; + }); - it('Should divide properties from different groups with an empty line', function() { - config = { 'sort-order': [ - ['top'], ['color'] - ] }; + it('Should parse mixins', function() { + config = {}; - input = 'div {\n' + - ' color: tomato;\n' + - ' top: 0;\n' + - '}'; + input = '@mixin nani { color: tomato; } .foo { @include nani; }'; - expected = 'div {\n' + - ' top: 0;\n' + - '\n' + - ' color: tomato;\n' + - '}'; - }); + expected = '@mixin nani { color: tomato; } .foo { @include nani; }'; + }); - it('Should sort variables', function() { - config = { 'sort-order': [ - ['$variable', 'color'] - ] }; + it('Should parse passing several variables to a mixin', function() { + config = {}; - input = 'div { color: $tomato; $red: tomato; }'; + input = '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }'; - expected = 'div {$red: tomato; color: $tomato; }'; - }); + expected = '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }'; + }); - it('Should sort imports', function() { - config = { 'sort-order': [ - ['$import', 'color'] - ] }; + it('Should parse passing a list of variables to a mixin', function() { + config = {}; - input = 'div { color: tomato; @import "foo.css"; }'; + input = '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + + '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }'; - expected = 'div {@import "foo.css"; color: tomato; }'; - }); + expected = '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + + '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }'; + }); - it('Should sort @include-s', function() { - config = { 'sort-order': [ - ['$include', 'color'] - ] }; + it('Should parse passing a content block to a mixin', function() { + config = {}; - input = 'div { color: tomato; @include .nani; }'; + input = '.foo { @include nani { color: tomato; top: 0 } }'; - expected = 'div {@include .nani; color: tomato; }'; - }); + expected = '.foo { @include nani { color: tomato; top: 0 } }'; + }); - it('Should sort @extend-s', function() { - config = { 'sort-order': [ - ['$include', 'color'] - ] }; + it('Should parse @content', function() { + config = {}; - input = 'div { color: tomato; @extend %nani; }'; + input = '@mixin nani { a { @content; } }'; - expected = 'div {@extend %nani; color: tomato; }'; - }); + expected = '@mixin nani { a { @content; } }'; + }); - it('Should sort properties inside blocks passed to mixins', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; + it('Should parse functions', function() { + config = {}; - input = '.foo { @include nani { color: tomato; top: 0; } }'; + input = '@function nani($n) { @return $n * 2; }'; - expected = '.foo { @include nani {top: 0; color: tomato; } }'; - }); + expected = '@function nani($n) { @return $n * 2; }'; }); }); diff --git a/test/sort-order-less.js b/test/sort-order-less.js new file mode 100644 index 00000000..8cd123fd --- /dev/null +++ b/test/sort-order-less.js @@ -0,0 +1,223 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); + +describe('options/sort-order (less)', function() { + var comb; + var config; + var input; + var expected; + + beforeEach(function() { + comb = new Comb(); + }); + + afterEach(function() { + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); + }); + + it('Should sort properties inside rules', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div { color: tomato; top: 0; }'; + + expected = 'div {top: 0; color: tomato; }'; + }); + + it('Should sort properties inside nested rules', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div { color: tomato; a { color: nani; top: 0; } }'; + + expected = 'div { color: tomato; a {top: 0; color: nani; } }'; + }); + + it('Should sort properties divided by nested rules', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; + + input = 'div { color: tomato; a { color: nani; top: 0; } left: 0; }'; + + expected = 'div { left: 0; color: tomato; a {top: 0; color: nani; }}'; + }); + + it('Should group declarations with proper comments and spaces (single line)', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}'; + + expected = 'div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }'; + }); + + it('Should group declarations with proper comments and spaces (multiple lines). Test 1', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div {\n' + + ' color: tomato; /* 1 */\n' + + ' /* 2 */\n' + + ' /* 3 */\n' + + ' top: 0; /* 4 */\n' + + ' /* 5 */\n' + + '}'; + + expected = 'div {\n' + + ' /* 2 */\n' + + ' /* 3 */\n' + + ' top: 0; /* 4 */\n' + + ' color: tomato; /* 1 */\n' + + ' /* 5 */\n' + + '}'; + }); + + it('Should group declarations with proper comments and spaces (multiple lines). Test 2', function() { + config = { 'sort-order': [ + ['$variable', 'color'] + ] }; + + input = 'p {\n' + + ' /* One hell of a comment */\n' + + ' color: tomato;\n' + + ' // Get in line!\n' + + ' @var: white;\n' + + ' }'; + + expected = 'p {\n' + + ' // Get in line!\n' + + ' @var: white;\n' + + ' /* One hell of a comment */\n' + + ' color: tomato;\n' + + ' }'; + }); + + it('Should group declarations with proper comments and spaces (multiple lines). Test 3', function() { + config = { 'sort-order': [ + ['$variable', 'color'] + ] }; + + input = 'p {\n' + + ' color: tomato; /* One hell of a comment */\n' + + ' @var: white; // Get in line!\n' + + ' }'; + + expected = 'p {\n' + + ' @var: white; // Get in line!\n' + + ' color: tomato; /* One hell of a comment */\n' + + ' }'; + }); + + it('Should divide properties from different groups with an empty line', function() { + config = { 'sort-order': [ + ['top'], ['color'] + ] }; + + input = 'div {\n' + + ' color: tomato;\n' + + ' top: 0;\n' + + '}'; + + expected = 'div {\n' + + ' top: 0;\n' + + '\n' + + ' color: tomato;\n' + + '}'; + }); + + it('Should sort variables', function() { + config = { 'sort-order': [ + ['$variable', 'color'] + ] }; + + input = 'div { color: @red; @red: tomato; }'; + + expected = 'div {@red: tomato; color: @red; }'; + }); + + it('Should sort imports', function() { + config = { 'sort-order': [ + ['$import', 'color'] + ] }; + + input = 'div { color: tomato; @import "foo.css"; }'; + + expected = 'div {@import "foo.css"; color: tomato; }'; + }); + + it('Should sort included mixins. Test 1', function() { + config = { 'sort-order': [ + ['$include', 'color', 'border-top', 'border-bottom'] + ] }; + + input = '.bordered {\n' + + ' border-bottom: solid 2px black;\n' + + ' border-top: dotted 1px black;\n' + + ' }\n' + + '#menu a {\n' + + ' color: #111;\n' + + ' .bordered;\n' + + ' }\n' + + '.post a {\n' + + ' color: red;\n' + + ' .bordered;\n' + + ' }'; + + expected = '.bordered {\n' + + ' border-top: dotted 1px black;\n' + + ' border-bottom: solid 2px black;\n' + + ' }\n' + + '#menu a {\n' + + ' .bordered;\n' + + ' color: #111;\n' + + ' }\n' + + '.post a {\n' + + ' .bordered;\n' + + ' color: red;\n' + + ' }'; + }); + + it('Should sort included mixins. Test 2', function() { + config = { 'sort-order': [ + ['$include', 'top', 'color'] + ] }; + + input = '.test {\n' + + ' .test1();\n' + + ' color: tomato;\n' + + ' .test2();\n' + + ' top: 0;\n' + + ' }'; + + expected = '.test {\n' + + ' .test1();\n' + + ' .test2();\n' + + ' top: 0;\n' + + ' color: tomato;\n' + + ' }'; + }); + + it('Should sort included mixins. Test 3', function() { + config = { 'sort-order': [ + ['$include', 'border', 'color'] + ] }; + + input = '.foo {\n' + + ' color: #0f0;\n' + + ' border: 1px solid #f00;\n' + + ' .linear-gradient(#fff; #000);\n' + + '}'; + + expected = '.foo {\n' + + ' .linear-gradient(#fff; #000);\n' + + ' border: 1px solid #f00;\n' + + ' color: #0f0;\n' + + '}'; + }); +}); diff --git a/test/sort-order-scss.js b/test/sort-order-scss.js new file mode 100644 index 00000000..84e7c5b9 --- /dev/null +++ b/test/sort-order-scss.js @@ -0,0 +1,147 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); + +describe('options/sort-order (scss)', function() { + var comb; + var config; + var input; + var expected; + + beforeEach(function() { + comb = new Comb(); + }); + + afterEach(function() { + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); + }); + + it('Should sort properties inside rules', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div { color: tomato; top: 0; }'; + + expected = 'div {top: 0; color: tomato; }'; + }); + + it('Should sort properties inside nested rules', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div { color: tomato; a { color: nani; top: 0; } }'; + + expected = 'div { color: tomato; a {top: 0; color: nani; } }'; + }); + + it('Should sort properties divided by nested rules', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; + + input = 'div { color: tomato; a { color: nani; top: 0; } left: 0; }'; + + expected = 'div { left: 0; color: tomato; a {top: 0; color: nani; }}'; + }); + + it('Should group declarations with proper comments and spaces (multiple lines)', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div {\n' + + ' color: tomato; /* 1 */\n' + + ' /* 2 */\n' + + ' /* 3 */\n' + + ' top: 0; /* 4 */\n' + + ' /* 5 */\n' + + '}'; + + expected = 'div {\n' + + ' /* 2 */\n' + + ' /* 3 */\n' + + ' top: 0; /* 4 */\n' + + ' color: tomato; /* 1 */\n' + + ' /* 5 */\n' + + '}'; + }); + + it('Should group declarations with proper comments and spaces (single line)', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = 'div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}'; + + expected = 'div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }'; + }); + + it('Should divide properties from different groups with an empty line', function() { + config = { 'sort-order': [ + ['top'], ['color'] + ] }; + + input = 'div {\n' + + ' color: tomato;\n' + + ' top: 0;\n' + + '}'; + + expected = 'div {\n' + + ' top: 0;\n' + + '\n' + + ' color: tomato;\n' + + '}'; + }); + + it('Should sort variables', function() { + config = { 'sort-order': [ + ['$variable', 'color'] + ] }; + + input = 'div { color: $tomato; $red: tomato; }'; + + expected = 'div {$red: tomato; color: $tomato; }'; + }); + + it('Should sort imports', function() { + config = { 'sort-order': [ + ['$import', 'color'] + ] }; + + input = 'div { color: tomato; @import "foo.css"; }'; + + expected = 'div {@import "foo.css"; color: tomato; }'; + }); + + it('Should sort @include-s', function() { + config = { 'sort-order': [ + ['$include', 'color'] + ] }; + + input = 'div { color: tomato; @include .nani; }'; + + expected = 'div {@include .nani; color: tomato; }'; + }); + + it('Should sort @extend-s', function() { + config = { 'sort-order': [ + ['$include', 'color'] + ] }; + + input = 'div { color: tomato; @extend %nani; }'; + + expected = 'div {@extend %nani; color: tomato; }'; + }); + + it('Should sort properties inside blocks passed to mixins', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = '.foo { @include nani { color: tomato; top: 0; } }'; + + expected = '.foo { @include nani {top: 0; color: tomato; } }'; + }); +}); From 7f4dd15dddb3e8c49c2df4948cc68ccf011e7aaf Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Thu, 14 Nov 2013 00:28:51 +0400 Subject: [PATCH 22/75] Tests: Use external files for parsing/sorting test --- test/less.js | 108 ++------ test/less/import.less | 4 + test/less/interpolated-variable-1.less | 4 + test/less/interpolated-variable-2.less | 4 + test/less/mixin.less | 4 + test/less/nested-media.less | 6 + test/less/nested-rule.less | 6 + test/less/operation.less | 8 + test/less/parent-selector.less | 8 + test/less/variable.less | 5 + test/scss.js | 238 +++++------------- test/scss/content.scss | 5 + test/scss/default.scss | 4 + test/scss/each.scss | 7 + test/scss/extend-1.scss | 4 + test/scss/extend-2.scss | 4 + test/scss/for.scss | 7 + test/scss/function.scss | 3 + test/scss/if-else-if.scss | 7 + test/scss/if-else.scss | 7 + test/scss/if.scss | 5 + test/scss/import.scss | 4 + test/scss/include.scss | 4 + test/scss/interpolated-variable-1.scss | 4 + test/scss/interpolated-variable-2.scss | 4 + test/scss/mixin-1.scss | 6 + test/scss/mixin-2.scss | 6 + test/scss/mixin-3.scss | 6 + test/scss/mixin-4.scss | 6 + test/scss/nested-media.scss | 6 + test/scss/nested-property.scss | 8 + test/scss/nested-rule.scss | 6 + test/scss/parent-selector.scss | 8 + test/scss/variable.scss | 4 + test/scss/warn.scss | 4 + test/scss/while.scss | 8 + test/sort-order-less.js | 173 +++++-------- test/sort-order-less/comments-1.expected.less | 1 + test/sort-order-less/comments-1.less | 1 + test/sort-order-less/comments-2.expected.less | 7 + test/sort-order-less/comments-2.less | 7 + test/sort-order-less/comments-3.expected.less | 6 + test/sort-order-less/comments-3.less | 6 + test/sort-order-less/comments-4.expected.less | 4 + test/sort-order-less/comments-4.less | 4 + .../different-groups.expected.less | 5 + test/sort-order-less/different-groups.less | 4 + test/sort-order-less/import.expected.less | 1 + test/sort-order-less/import.less | 1 + test/sort-order-less/mixin-1.expected.less | 12 + test/sort-order-less/mixin-1.less | 12 + test/sort-order-less/mixin-2.expected.less | 6 + test/sort-order-less/mixin-2.less | 6 + test/sort-order-less/mixin-3.expected.less | 5 + test/sort-order-less/mixin-3.less | 5 + .../nested-rule-1.expected.less | 6 + test/sort-order-less/nested-rule-1.less | 6 + .../nested-rule-2.expected.less | 7 + test/sort-order-less/nested-rule-2.less | 7 + test/sort-order-less/rule.expected.less | 4 + test/sort-order-less/rule.less | 4 + test/sort-order-less/variable.expected.less | 1 + test/sort-order-less/variable.less | 1 + test/sort-order-scss.js | 97 +++---- test/sort-order-scss/comments-1.expected.scss | 7 + test/sort-order-scss/comments-1.scss | 7 + test/sort-order-scss/comments-2.expected.scss | 1 + test/sort-order-scss/comments-2.scss | 1 + .../different-groups.expected.scss | 5 + test/sort-order-scss/different-groups.scss | 4 + test/sort-order-scss/extend.expected.scss | 1 + test/sort-order-scss/extend.scss | 1 + test/sort-order-scss/import.expected.scss | 1 + test/sort-order-scss/import.scss | 1 + test/sort-order-scss/include.expected.scss | 1 + test/sort-order-scss/include.scss | 1 + test/sort-order-scss/mixin.expected.scss | 1 + test/sort-order-scss/mixin.scss | 1 + .../nested-rule-1.expected.scss | 1 + test/sort-order-scss/nested-rule-1.scss | 1 + .../nested-rule-2.expected.scss | 1 + test/sort-order-scss/nested-rule-2.scss | 1 + test/sort-order-scss/rule.expected.scss | 1 + test/sort-order-scss/rule.scss | 1 + test/sort-order-scss/variable.expected.scss | 1 + test/sort-order-scss/variable.scss | 1 + test/sort-order.js | 106 ++------ test/sort-order/multiple-groups-comments.css | 5 + .../multiple-groups-comments.expected.css | 6 + test/sort-order/multiple-groups.css | 7 + test/sort-order/multiple-groups.expected.css | 8 + test/sort-order/single-group-comments.css | 5 + .../single-group-comments.expected.css | 5 + test/sort-order/single-group.css | 5 + test/sort-order/single-group.expected.css | 5 + 95 files changed, 621 insertions(+), 503 deletions(-) create mode 100644 test/less/import.less create mode 100644 test/less/interpolated-variable-1.less create mode 100644 test/less/interpolated-variable-2.less create mode 100644 test/less/mixin.less create mode 100644 test/less/nested-media.less create mode 100644 test/less/nested-rule.less create mode 100644 test/less/operation.less create mode 100644 test/less/parent-selector.less create mode 100644 test/less/variable.less create mode 100644 test/scss/content.scss create mode 100644 test/scss/default.scss create mode 100644 test/scss/each.scss create mode 100644 test/scss/extend-1.scss create mode 100644 test/scss/extend-2.scss create mode 100644 test/scss/for.scss create mode 100644 test/scss/function.scss create mode 100644 test/scss/if-else-if.scss create mode 100644 test/scss/if-else.scss create mode 100644 test/scss/if.scss create mode 100644 test/scss/import.scss create mode 100644 test/scss/include.scss create mode 100644 test/scss/interpolated-variable-1.scss create mode 100644 test/scss/interpolated-variable-2.scss create mode 100644 test/scss/mixin-1.scss create mode 100644 test/scss/mixin-2.scss create mode 100644 test/scss/mixin-3.scss create mode 100644 test/scss/mixin-4.scss create mode 100644 test/scss/nested-media.scss create mode 100644 test/scss/nested-property.scss create mode 100644 test/scss/nested-rule.scss create mode 100644 test/scss/parent-selector.scss create mode 100644 test/scss/variable.scss create mode 100644 test/scss/warn.scss create mode 100644 test/scss/while.scss create mode 100644 test/sort-order-less/comments-1.expected.less create mode 100644 test/sort-order-less/comments-1.less create mode 100644 test/sort-order-less/comments-2.expected.less create mode 100644 test/sort-order-less/comments-2.less create mode 100644 test/sort-order-less/comments-3.expected.less create mode 100644 test/sort-order-less/comments-3.less create mode 100644 test/sort-order-less/comments-4.expected.less create mode 100644 test/sort-order-less/comments-4.less create mode 100644 test/sort-order-less/different-groups.expected.less create mode 100644 test/sort-order-less/different-groups.less create mode 100644 test/sort-order-less/import.expected.less create mode 100644 test/sort-order-less/import.less create mode 100644 test/sort-order-less/mixin-1.expected.less create mode 100644 test/sort-order-less/mixin-1.less create mode 100644 test/sort-order-less/mixin-2.expected.less create mode 100644 test/sort-order-less/mixin-2.less create mode 100644 test/sort-order-less/mixin-3.expected.less create mode 100644 test/sort-order-less/mixin-3.less create mode 100644 test/sort-order-less/nested-rule-1.expected.less create mode 100644 test/sort-order-less/nested-rule-1.less create mode 100644 test/sort-order-less/nested-rule-2.expected.less create mode 100644 test/sort-order-less/nested-rule-2.less create mode 100644 test/sort-order-less/rule.expected.less create mode 100644 test/sort-order-less/rule.less create mode 100644 test/sort-order-less/variable.expected.less create mode 100644 test/sort-order-less/variable.less create mode 100644 test/sort-order-scss/comments-1.expected.scss create mode 100644 test/sort-order-scss/comments-1.scss create mode 100644 test/sort-order-scss/comments-2.expected.scss create mode 100644 test/sort-order-scss/comments-2.scss create mode 100644 test/sort-order-scss/different-groups.expected.scss create mode 100644 test/sort-order-scss/different-groups.scss create mode 100644 test/sort-order-scss/extend.expected.scss create mode 100644 test/sort-order-scss/extend.scss create mode 100644 test/sort-order-scss/import.expected.scss create mode 100644 test/sort-order-scss/import.scss create mode 100644 test/sort-order-scss/include.expected.scss create mode 100644 test/sort-order-scss/include.scss create mode 100644 test/sort-order-scss/mixin.expected.scss create mode 100644 test/sort-order-scss/mixin.scss create mode 100644 test/sort-order-scss/nested-rule-1.expected.scss create mode 100644 test/sort-order-scss/nested-rule-1.scss create mode 100644 test/sort-order-scss/nested-rule-2.expected.scss create mode 100644 test/sort-order-scss/nested-rule-2.scss create mode 100644 test/sort-order-scss/rule.expected.scss create mode 100644 test/sort-order-scss/rule.scss create mode 100644 test/sort-order-scss/variable.expected.scss create mode 100644 test/sort-order-scss/variable.scss create mode 100644 test/sort-order/multiple-groups-comments.css create mode 100644 test/sort-order/multiple-groups-comments.expected.css create mode 100644 test/sort-order/multiple-groups.css create mode 100644 test/sort-order/multiple-groups.expected.css create mode 100644 test/sort-order/single-group-comments.css create mode 100644 test/sort-order/single-group-comments.expected.css create mode 100644 test/sort-order/single-group.css create mode 100644 test/sort-order/single-group.expected.css diff --git a/test/less.js b/test/less.js index 2163f11b..a5a2ff9d 100644 --- a/test/less.js +++ b/test/less.js @@ -1,122 +1,62 @@ var Comb = require('../lib/csscomb'); var assert = require('assert'); +var fs = require('fs'); describe('LESS', function() { var comb; - var config; var input; - var expected; + + function readFile(path) { + return fs.readFileSync('test/less/' + path, 'utf8'); + } beforeEach(function() { comb = new Comb(); - }); - - afterEach(function() { - comb.configure(config); - assert.equal(comb.processString(input, 'less'), expected); + comb.configure({}); }); it('Should parse nested rules', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; - - input = 'div { color: tomato; a { top: 0; } }'; - - expected = 'div { color: tomato; a { top: 0; } }'; + input = readFile('nested-rule.less'); + assert.equal(comb.processString(input, 'less'), input); }); it('Should parse operations', function() { - config = {}; - - input = 'div {\n' + - ' @base: 5%;\n' + - ' @filler: @base * 2;\n' + - ' @other: @base + @filler;\n' + - ' color: #888 / 4;\n' + - ' background-color: @base-color + #111;\n' + - ' height: 100% / 2 + @filler;\n' + - ' }'; - - expected = 'div {\n' + - ' @base: 5%;\n' + - ' @filler: @base * 2;\n' + - ' @other: @base + @filler;\n' + - ' color: #888 / 4;\n' + - ' background-color: @base-color + #111;\n' + - ' height: 100% / 2 + @filler;\n' + - ' }'; + input = readFile('operation.less'); + assert.equal(comb.processString(input, 'less'), input); }); it('Should parse parent selector &', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div { color: tomato; &.top { color: nani; top: 0; } left: 0; }'; - - expected = 'div { left: 0; color: tomato; &.top {top: 0; color: nani; }}'; + input = readFile('parent-selector.less'); + assert.equal(comb.processString(input, 'less'), input); }); it('Should parse variables', function() { - config = {}; - - input = '@red: tomato; div { color: @tomato; top: @@foo; }'; - - expected = '@red: tomato; div { color: @tomato; top: @@foo; }'; + input = readFile('variable.less'); + assert.equal(comb.processString(input, 'less'), input); }); it('Should parse interpolated variables inside selectors', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div.@{nani} {color:tomato;top:0;}'; - - expected = 'div.@{nani} {top:0;color:tomato;}'; + input = readFile('interpolated-variable-1.less'); + assert.equal(comb.processString(input, 'less'), input); }); it('Should parse interpolated variables inside values', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div {color:@{tomato};top:0;}'; - - expected = 'div {top:0;color:@{tomato};}'; + input = readFile('interpolated-variable-2.less'); + assert.equal(comb.processString(input, 'less'), input); }); it('Should parse @import', function() { - config = {}; - - input = 'div { @import "foo.css"; top: 0; }'; - - expected = 'div { @import "foo.css"; top: 0; }'; + input = readFile('import.less'); + assert.equal(comb.processString(input, 'less'), input); }); it('Should parse included mixins', function() { - config = {}; - - input = 'div { .mixin; top: 0; }'; - - expected = 'div { .mixin; top: 0; }'; + input = readFile('mixin.less'); + assert.equal(comb.processString(input, 'less'), input); }); it('Should parse nested @media', function() { - config = {}; - - input = 'div {\n' + - ' @media screen and (orientation: landscape) {\n' + - ' color: tomato;\n' + - ' }\n' + - ' top: 0;\n' + - '}'; - - expected = 'div {\n' + - ' @media screen and (orientation: landscape) {\n' + - ' color: tomato;\n' + - ' }\n' + - ' top: 0;\n' + - '}'; + input = readFile('nested-media.less'); + assert.equal(comb.processString(input, 'less'), input); }); }); diff --git a/test/less/import.less b/test/less/import.less new file mode 100644 index 00000000..8faa1263 --- /dev/null +++ b/test/less/import.less @@ -0,0 +1,4 @@ +div { + @import "foo.css"; + top: 0; +} diff --git a/test/less/interpolated-variable-1.less b/test/less/interpolated-variable-1.less new file mode 100644 index 00000000..b0bc3148 --- /dev/null +++ b/test/less/interpolated-variable-1.less @@ -0,0 +1,4 @@ +div.@{nani} { + color:tomato; + top:0; +} diff --git a/test/less/interpolated-variable-2.less b/test/less/interpolated-variable-2.less new file mode 100644 index 00000000..18a66ed2 --- /dev/null +++ b/test/less/interpolated-variable-2.less @@ -0,0 +1,4 @@ +div { + color: @{tomato}; + top:0; +} diff --git a/test/less/mixin.less b/test/less/mixin.less new file mode 100644 index 00000000..7e95951f --- /dev/null +++ b/test/less/mixin.less @@ -0,0 +1,4 @@ +div { + .mixin; + top: 0; +} diff --git a/test/less/nested-media.less b/test/less/nested-media.less new file mode 100644 index 00000000..346ddbf4 --- /dev/null +++ b/test/less/nested-media.less @@ -0,0 +1,6 @@ +div { + @media screen and (orientation: landscape) { + color: tomato; + } + top: 0; +} diff --git a/test/less/nested-rule.less b/test/less/nested-rule.less new file mode 100644 index 00000000..5f74a2d9 --- /dev/null +++ b/test/less/nested-rule.less @@ -0,0 +1,6 @@ +div { + color: tomato; + a { + top: 0; + } +} diff --git a/test/less/operation.less b/test/less/operation.less new file mode 100644 index 00000000..a3711aa3 --- /dev/null +++ b/test/less/operation.less @@ -0,0 +1,8 @@ +div { + @base: 5%; + @filler: @base * 2; + @other: @base + @filler; + color: #888 / 4; + background-color: @base-color + #111; + height: 100% / 2 + @filler; +} diff --git a/test/less/parent-selector.less b/test/less/parent-selector.less new file mode 100644 index 00000000..61760301 --- /dev/null +++ b/test/less/parent-selector.less @@ -0,0 +1,8 @@ +div { + color: tomato; + &.top { + color: nani; + top: 0; + } + left: 0; +} diff --git a/test/less/variable.less b/test/less/variable.less new file mode 100644 index 00000000..8eba0cc3 --- /dev/null +++ b/test/less/variable.less @@ -0,0 +1,5 @@ +@red: tomato; +div { + color: @tomato; + top: @@foo; +} diff --git a/test/scss.js b/test/scss.js index 8949479e..a0a4ee41 100644 --- a/test/scss.js +++ b/test/scss.js @@ -1,268 +1,142 @@ var Comb = require('../lib/csscomb'); var assert = require('assert'); +var fs = require('fs'); describe('SCSS', function() { var comb; - var config; var input; - var expected; + + function readFile(path) { + return fs.readFileSync('test/scss/' + path, 'utf8'); + } beforeEach(function() { comb = new Comb(); - }); - - afterEach(function() { - comb.configure(config); - assert.equal(comb.processString(input, 'scss'), expected); + comb.configure({}); }); it('Should parse nested rules', function() { - config = { 'sort-order': [ - ['top', 'color'] - ] }; - - input = 'div { color: tomato; a { top: 0; } }'; - - expected = 'div { color: tomato; a { top: 0; } }'; + input = readFile('nested-rule.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse parent selector &', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div { color: tomato; &.top { color: nani; top: 0; } left: 0; }'; - - expected = 'div { left: 0; color: tomato; &.top {top: 0; color: nani; }}'; + input = readFile('parent-selector.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse nested properties', function() { - config = { 'sort-order': [ - ['left', 'color', 'font'] - ] }; - - input = 'div { color: tomato; font: 2px/3px { family: fantasy; size: 30em; } left: 0; }'; - - expected = 'div {left: 0; color: tomato; font: 2px/3px { family: fantasy; size: 30em; } }'; + input = readFile('nested-property.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse variables', function() { - config = {}; - - input = '$red: tomato; div { color: $tomato; }'; - - expected = '$red: tomato; div { color: $tomato; }'; + input = readFile('variable.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse interpolated variables inside selectors', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div.#{$nani} {color:tomato;top:0;}'; - - expected = 'div.#{$nani} {top:0;color:tomato;}'; + input = readFile('interpolated-variable-1.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse interpolated variables inside values', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div {color:#{$tomato};top:0;}'; - - expected = 'div {top:0;color:#{$tomato};}'; + input = readFile('interpolated-variable-2.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse defaults', function() { - config = { 'sort-order': [ - ['top', 'left', 'color'] - ] }; - - input = 'div { color: tomato !default; top: 0; }'; - - expected = 'div {top: 0; color: tomato !default; }'; + input = readFile('default.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @import', function() { - config = {}; - - input = 'div { @import "foo.css"; top: 0; }'; - - expected = 'div { @import "foo.css"; top: 0; }'; + input = readFile('import.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @include', function() { - config = {}; - - input = 'div { @include nani($panda); top: 0; }'; - - expected = 'div { @include nani($panda); top: 0; }'; + input = readFile('include.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse nested @media', function() { - config = {}; - - input = 'div {\n' + - ' @media screen and (orientation: landscape) {\n' + - ' color: tomato;\n' + - ' }\n' + - ' top: 0;\n' + - '}'; - - expected = 'div {\n' + - ' @media screen and (orientation: landscape) {\n' + - ' color: tomato;\n' + - ' }\n' + - ' top: 0;\n' + - '}'; + input = readFile('nested-media.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @extend with classes', function() { - config = {}; - - input = 'div { @extend .nani; top: 0; }'; - - expected = 'div { @extend .nani; top: 0; }'; + input = readFile('extend-1.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @extend with placeholders', function() { - config = {}; - - input = 'div { @extend %nani; top: 0; }'; - - expected = 'div { @extend %nani; top: 0; }'; + input = readFile('extend-2.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @warn', function() { - config = {}; - - input = 'div { @warn "nani"; top: 0; }'; - - expected = 'div { @warn "nani"; top: 0; }'; + input = readFile('warn.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @if', function() { - config = {}; - - input = 'div { @if $type == ocean { top: 0; } }'; - - expected = 'div { @if $type == ocean { top: 0; } }'; + input = readFile('if.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @if and @else', function() { - config = {}; - - input = 'div { @if $type == ocean { top: 0; } @else { left: 0; } }'; - - expected = 'div { @if $type == ocean { top: 0; } @else { left: 0; } }'; + input = readFile('if-else.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @if and @else if', function() { - config = {}; - - input = 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }'; - - expected = 'div { @if $type == ocean { top: 0; } @else if $type == monster { left: 0; } }'; + input = readFile('if-else-if.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @for', function() { - config = {}; - - input = 'div {\n' + - ' @for $i from 1 through 3 {\n' + - ' .item-#{$i} { width: 2em * 1; }\n' + - ' }\n' + - '}'; - - expected = 'div {\n' + - ' @for $i from 1 through 3 {\n' + - ' .item-#{$i} { width: 2em * 1; }\n' + - ' }\n' + - '}'; + input = readFile('for.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @each', function() { - config = {}; - - input = 'div {\n' + - ' @each $animal in puma, sea-slug, erget {\n' + - ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + - ' }\n' + - '}'; - - expected = 'div {\n' + - ' @each $animal in puma, sea-slug, erget {\n' + - ' .#{$animal}-icon { background-image: url("/images/#{$animal}.png"); }\n' + - ' }\n' + - '}'; + input = readFile('each.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @while', function() { - config = {}; - - input = 'div {\n' + - ' @while $i > 6 {\n' + - ' .item { width: 2em * $i; }\n' + - ' $i: $i - 2;\n' + - ' }\n' + - '}'; - - expected = 'div {\n' + - ' @while $i > 6 {\n' + - ' .item { width: 2em * $i; }\n' + - ' $i: $i - 2;\n' + - ' }\n' + - '}'; + input = readFile('while.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse mixins', function() { - config = {}; - - input = '@mixin nani { color: tomato; } .foo { @include nani; }'; - - expected = '@mixin nani { color: tomato; } .foo { @include nani; }'; + input = readFile('mixin-1.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse passing several variables to a mixin', function() { - config = {}; - - input = '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }'; - - expected = '@mixin nani($tomato) { color: $tomato; } .foo { @include nani(red); }'; + input = readFile('mixin-2.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse passing a list of variables to a mixin', function() { - config = {}; - - input = '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + - '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }'; - - expected = '@mixin nani($shadows...) { box-shadow: $shadows; }\n' + - '.foo { @include nani(0px 4px 5px #666, 2px 6px 10px #999); }'; + input = readFile('mixin-3.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse passing a content block to a mixin', function() { - config = {}; - - input = '.foo { @include nani { color: tomato; top: 0 } }'; - - expected = '.foo { @include nani { color: tomato; top: 0 } }'; + input = readFile('mixin-4.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse @content', function() { - config = {}; - - input = '@mixin nani { a { @content; } }'; - - expected = '@mixin nani { a { @content; } }'; + input = readFile('content.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); it('Should parse functions', function() { - config = {}; - - input = '@function nani($n) { @return $n * 2; }'; - - expected = '@function nani($n) { @return $n * 2; }'; + input = readFile('function.scss'); + assert.equal(comb.processString(input, 'scss'), input); }); }); diff --git a/test/scss/content.scss b/test/scss/content.scss new file mode 100644 index 00000000..6852bccf --- /dev/null +++ b/test/scss/content.scss @@ -0,0 +1,5 @@ +@mixin nani { + a { + @content; + } +} diff --git a/test/scss/default.scss b/test/scss/default.scss new file mode 100644 index 00000000..12c10f9f --- /dev/null +++ b/test/scss/default.scss @@ -0,0 +1,4 @@ +div { + color: tomato !default; + top: 0; +} diff --git a/test/scss/each.scss b/test/scss/each.scss new file mode 100644 index 00000000..af596e68 --- /dev/null +++ b/test/scss/each.scss @@ -0,0 +1,7 @@ +div { + @each $animal in puma, sea-slug, erget { + .#{$animal}-icon { + background-image: url("/images/#{$animal}.png"); + } + } +} diff --git a/test/scss/extend-1.scss b/test/scss/extend-1.scss new file mode 100644 index 00000000..c72afeac --- /dev/null +++ b/test/scss/extend-1.scss @@ -0,0 +1,4 @@ +div { + @extend .nani; + top: 0; +} diff --git a/test/scss/extend-2.scss b/test/scss/extend-2.scss new file mode 100644 index 00000000..c7bf3b58 --- /dev/null +++ b/test/scss/extend-2.scss @@ -0,0 +1,4 @@ +div { + @extend %nani; + top: 0; +} diff --git a/test/scss/for.scss b/test/scss/for.scss new file mode 100644 index 00000000..64ff25aa --- /dev/null +++ b/test/scss/for.scss @@ -0,0 +1,7 @@ +div { + @for $i from 1 through 3 { + .item-#{$i} { + width: 2em * 1; + } + } +} diff --git a/test/scss/function.scss b/test/scss/function.scss new file mode 100644 index 00000000..8fcce438 --- /dev/null +++ b/test/scss/function.scss @@ -0,0 +1,3 @@ +@function nani($n) { + @return $n * 2; +} diff --git a/test/scss/if-else-if.scss b/test/scss/if-else-if.scss new file mode 100644 index 00000000..f16e287d --- /dev/null +++ b/test/scss/if-else-if.scss @@ -0,0 +1,7 @@ +div { + @if $type == ocean { + top: 0; + } @else if $type == monster { + left: 0; + } +} diff --git a/test/scss/if-else.scss b/test/scss/if-else.scss new file mode 100644 index 00000000..236a6217 --- /dev/null +++ b/test/scss/if-else.scss @@ -0,0 +1,7 @@ +div { + @if $type == ocean { + top: 0; + } @else { + left: 0; + } +} diff --git a/test/scss/if.scss b/test/scss/if.scss new file mode 100644 index 00000000..af9f4856 --- /dev/null +++ b/test/scss/if.scss @@ -0,0 +1,5 @@ +div { + @if $type == ocean { + top: 0; + } +} diff --git a/test/scss/import.scss b/test/scss/import.scss new file mode 100644 index 00000000..8faa1263 --- /dev/null +++ b/test/scss/import.scss @@ -0,0 +1,4 @@ +div { + @import "foo.css"; + top: 0; +} diff --git a/test/scss/include.scss b/test/scss/include.scss new file mode 100644 index 00000000..2c918808 --- /dev/null +++ b/test/scss/include.scss @@ -0,0 +1,4 @@ +div { + @include nani($panda); + top: 0; +} diff --git a/test/scss/interpolated-variable-1.scss b/test/scss/interpolated-variable-1.scss new file mode 100644 index 00000000..a6c28cf4 --- /dev/null +++ b/test/scss/interpolated-variable-1.scss @@ -0,0 +1,4 @@ +div.#{$nani} { + color: tomato; + top:0; +} diff --git a/test/scss/interpolated-variable-2.scss b/test/scss/interpolated-variable-2.scss new file mode 100644 index 00000000..a19498f5 --- /dev/null +++ b/test/scss/interpolated-variable-2.scss @@ -0,0 +1,4 @@ +div { + color: #{$tomato}; + top: 0; +} diff --git a/test/scss/mixin-1.scss b/test/scss/mixin-1.scss new file mode 100644 index 00000000..32a77150 --- /dev/null +++ b/test/scss/mixin-1.scss @@ -0,0 +1,6 @@ +@mixin nani { + color: tomato; +} +.foo { + @include nani; +} diff --git a/test/scss/mixin-2.scss b/test/scss/mixin-2.scss new file mode 100644 index 00000000..b779b6de --- /dev/null +++ b/test/scss/mixin-2.scss @@ -0,0 +1,6 @@ +@mixin nani($tomato) { + color: $tomato; +} +.foo { + @include nani(red); +} diff --git a/test/scss/mixin-3.scss b/test/scss/mixin-3.scss new file mode 100644 index 00000000..2c19b358 --- /dev/null +++ b/test/scss/mixin-3.scss @@ -0,0 +1,6 @@ +@mixin nani($shadows...) { + box-shadow: $shadows; +} +.foo { + @include nani(0px 4px 5px #666, 2px 6px 10px #999); +} diff --git a/test/scss/mixin-4.scss b/test/scss/mixin-4.scss new file mode 100644 index 00000000..4e46ffed --- /dev/null +++ b/test/scss/mixin-4.scss @@ -0,0 +1,6 @@ +.foo { + @include nani { + color: tomato; + top: 0 + } +} diff --git a/test/scss/nested-media.scss b/test/scss/nested-media.scss new file mode 100644 index 00000000..346ddbf4 --- /dev/null +++ b/test/scss/nested-media.scss @@ -0,0 +1,6 @@ +div { + @media screen and (orientation: landscape) { + color: tomato; + } + top: 0; +} diff --git a/test/scss/nested-property.scss b/test/scss/nested-property.scss new file mode 100644 index 00000000..2f6384a3 --- /dev/null +++ b/test/scss/nested-property.scss @@ -0,0 +1,8 @@ +div { + color: tomato; + font: 2px/3px { + family: fantasy; + size: 30em; + } + left: 0; +} diff --git a/test/scss/nested-rule.scss b/test/scss/nested-rule.scss new file mode 100644 index 00000000..5f74a2d9 --- /dev/null +++ b/test/scss/nested-rule.scss @@ -0,0 +1,6 @@ +div { + color: tomato; + a { + top: 0; + } +} diff --git a/test/scss/parent-selector.scss b/test/scss/parent-selector.scss new file mode 100644 index 00000000..61760301 --- /dev/null +++ b/test/scss/parent-selector.scss @@ -0,0 +1,8 @@ +div { + color: tomato; + &.top { + color: nani; + top: 0; + } + left: 0; +} diff --git a/test/scss/variable.scss b/test/scss/variable.scss new file mode 100644 index 00000000..d553b927 --- /dev/null +++ b/test/scss/variable.scss @@ -0,0 +1,4 @@ +$red: tomato; +div { + color: $tomato; +} diff --git a/test/scss/warn.scss b/test/scss/warn.scss new file mode 100644 index 00000000..c7961a18 --- /dev/null +++ b/test/scss/warn.scss @@ -0,0 +1,4 @@ +div { + @warn "nani"; + top: 0; +} diff --git a/test/scss/while.scss b/test/scss/while.scss new file mode 100644 index 00000000..89c6cfa5 --- /dev/null +++ b/test/scss/while.scss @@ -0,0 +1,8 @@ +div { + @while $i > 6 { + .item { + width: 2em * $i; + } + $i: $i - 2; + } +} diff --git a/test/sort-order-less.js b/test/sort-order-less.js index 8cd123fd..91588cce 100644 --- a/test/sort-order-less.js +++ b/test/sort-order-less.js @@ -1,5 +1,6 @@ var Comb = require('../lib/csscomb'); var assert = require('assert'); +var fs = require('fs'); describe('options/sort-order (less)', function() { var comb; @@ -7,23 +8,24 @@ describe('options/sort-order (less)', function() { var input; var expected; + function readFile(path) { + return fs.readFileSync('test/sort-order-less/' + path, 'utf8'); + } + beforeEach(function() { comb = new Comb(); }); - afterEach(function() { - comb.configure(config); - assert.equal(comb.processString(input, 'less'), expected); - }); - it('Should sort properties inside rules', function() { config = { 'sort-order': [ ['top', 'color'] ] }; - input = 'div { color: tomato; top: 0; }'; + input = readFile('rule.less'); + expected = readFile('rule.expected.less'); - expected = 'div {top: 0; color: tomato; }'; + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should sort properties inside nested rules', function() { @@ -31,9 +33,11 @@ describe('options/sort-order (less)', function() { ['top', 'color'] ] }; - input = 'div { color: tomato; a { color: nani; top: 0; } }'; + input = readFile('nested-rule-1.less'); + expected = readFile('nested-rule-1.expected.less'); - expected = 'div { color: tomato; a {top: 0; color: nani; } }'; + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should sort properties divided by nested rules', function() { @@ -41,9 +45,11 @@ describe('options/sort-order (less)', function() { ['top', 'left', 'color'] ] }; - input = 'div { color: tomato; a { color: nani; top: 0; } left: 0; }'; + input = readFile('nested-rule-2.less'); + expected = readFile('nested-rule-2.expected.less'); - expected = 'div { left: 0; color: tomato; a {top: 0; color: nani; }}'; + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should group declarations with proper comments and spaces (single line)', function() { @@ -51,9 +57,11 @@ describe('options/sort-order (less)', function() { ['top', 'color'] ] }; - input = 'div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}'; + input = readFile('comments-1.less'); + expected = readFile('comments-1.expected.less'); - expected = 'div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }'; + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should group declarations with proper comments and spaces (multiple lines). Test 1', function() { @@ -61,21 +69,11 @@ describe('options/sort-order (less)', function() { ['top', 'color'] ] }; - input = 'div {\n' + - ' color: tomato; /* 1 */\n' + - ' /* 2 */\n' + - ' /* 3 */\n' + - ' top: 0; /* 4 */\n' + - ' /* 5 */\n' + - '}'; - - expected = 'div {\n' + - ' /* 2 */\n' + - ' /* 3 */\n' + - ' top: 0; /* 4 */\n' + - ' color: tomato; /* 1 */\n' + - ' /* 5 */\n' + - '}'; + input = readFile('comments-2.less'); + expected = readFile('comments-2.expected.less'); + + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should group declarations with proper comments and spaces (multiple lines). Test 2', function() { @@ -83,19 +81,11 @@ describe('options/sort-order (less)', function() { ['$variable', 'color'] ] }; - input = 'p {\n' + - ' /* One hell of a comment */\n' + - ' color: tomato;\n' + - ' // Get in line!\n' + - ' @var: white;\n' + - ' }'; - - expected = 'p {\n' + - ' // Get in line!\n' + - ' @var: white;\n' + - ' /* One hell of a comment */\n' + - ' color: tomato;\n' + - ' }'; + input = readFile('comments-3.less'); + expected = readFile('comments-3.expected.less'); + + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should group declarations with proper comments and spaces (multiple lines). Test 3', function() { @@ -103,15 +93,11 @@ describe('options/sort-order (less)', function() { ['$variable', 'color'] ] }; - input = 'p {\n' + - ' color: tomato; /* One hell of a comment */\n' + - ' @var: white; // Get in line!\n' + - ' }'; + input = readFile('comments-3.less'); + expected = readFile('comments-3.expected.less'); - expected = 'p {\n' + - ' @var: white; // Get in line!\n' + - ' color: tomato; /* One hell of a comment */\n' + - ' }'; + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should divide properties from different groups with an empty line', function() { @@ -119,16 +105,11 @@ describe('options/sort-order (less)', function() { ['top'], ['color'] ] }; - input = 'div {\n' + - ' color: tomato;\n' + - ' top: 0;\n' + - '}'; + input = readFile('different-groups.less'); + expected = readFile('different-groups.expected.less'); - expected = 'div {\n' + - ' top: 0;\n' + - '\n' + - ' color: tomato;\n' + - '}'; + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should sort variables', function() { @@ -136,9 +117,11 @@ describe('options/sort-order (less)', function() { ['$variable', 'color'] ] }; - input = 'div { color: @red; @red: tomato; }'; + input = readFile('variable.less'); + expected = readFile('variable.expected.less'); - expected = 'div {@red: tomato; color: @red; }'; + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should sort imports', function() { @@ -146,9 +129,11 @@ describe('options/sort-order (less)', function() { ['$import', 'color'] ] }; - input = 'div { color: tomato; @import "foo.css"; }'; + input = readFile('import.less'); + expected = readFile('import.expected.less'); - expected = 'div {@import "foo.css"; color: tomato; }'; + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should sort included mixins. Test 1', function() { @@ -156,31 +141,11 @@ describe('options/sort-order (less)', function() { ['$include', 'color', 'border-top', 'border-bottom'] ] }; - input = '.bordered {\n' + - ' border-bottom: solid 2px black;\n' + - ' border-top: dotted 1px black;\n' + - ' }\n' + - '#menu a {\n' + - ' color: #111;\n' + - ' .bordered;\n' + - ' }\n' + - '.post a {\n' + - ' color: red;\n' + - ' .bordered;\n' + - ' }'; - - expected = '.bordered {\n' + - ' border-top: dotted 1px black;\n' + - ' border-bottom: solid 2px black;\n' + - ' }\n' + - '#menu a {\n' + - ' .bordered;\n' + - ' color: #111;\n' + - ' }\n' + - '.post a {\n' + - ' .bordered;\n' + - ' color: red;\n' + - ' }'; + input = readFile('mixin-1.less'); + expected = readFile('mixin-1.expected.less'); + + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should sort included mixins. Test 2', function() { @@ -188,19 +153,11 @@ describe('options/sort-order (less)', function() { ['$include', 'top', 'color'] ] }; - input = '.test {\n' + - ' .test1();\n' + - ' color: tomato;\n' + - ' .test2();\n' + - ' top: 0;\n' + - ' }'; - - expected = '.test {\n' + - ' .test1();\n' + - ' .test2();\n' + - ' top: 0;\n' + - ' color: tomato;\n' + - ' }'; + input = readFile('mixin-2.less'); + expected = readFile('mixin-2.expected.less'); + + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); it('Should sort included mixins. Test 3', function() { @@ -208,16 +165,10 @@ describe('options/sort-order (less)', function() { ['$include', 'border', 'color'] ] }; - input = '.foo {\n' + - ' color: #0f0;\n' + - ' border: 1px solid #f00;\n' + - ' .linear-gradient(#fff; #000);\n' + - '}'; - - expected = '.foo {\n' + - ' .linear-gradient(#fff; #000);\n' + - ' border: 1px solid #f00;\n' + - ' color: #0f0;\n' + - '}'; + input = readFile('mixin-3.less'); + expected = readFile('mixin-3.expected.less'); + + comb.configure(config); + assert.equal(comb.processString(input, 'less'), expected); }); }); diff --git a/test/sort-order-less/comments-1.expected.less b/test/sort-order-less/comments-1.expected.less new file mode 100644 index 00000000..a9b2976c --- /dev/null +++ b/test/sort-order-less/comments-1.expected.less @@ -0,0 +1 @@ +div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ } diff --git a/test/sort-order-less/comments-1.less b/test/sort-order-less/comments-1.less new file mode 100644 index 00000000..617d4fdc --- /dev/null +++ b/test/sort-order-less/comments-1.less @@ -0,0 +1 @@ +div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */} diff --git a/test/sort-order-less/comments-2.expected.less b/test/sort-order-less/comments-2.expected.less new file mode 100644 index 00000000..0aa257a7 --- /dev/null +++ b/test/sort-order-less/comments-2.expected.less @@ -0,0 +1,7 @@ +div { + /* 2 */ + /* 3 */ + top: 0; /* 4 */ + color: tomato; /* 1 */ + /* 5 */ +} diff --git a/test/sort-order-less/comments-2.less b/test/sort-order-less/comments-2.less new file mode 100644 index 00000000..18e6319b --- /dev/null +++ b/test/sort-order-less/comments-2.less @@ -0,0 +1,7 @@ +div { + color: tomato; /* 1 */ + /* 2 */ + /* 3 */ + top: 0; /* 4 */ + /* 5 */ +} diff --git a/test/sort-order-less/comments-3.expected.less b/test/sort-order-less/comments-3.expected.less new file mode 100644 index 00000000..7bf0938d --- /dev/null +++ b/test/sort-order-less/comments-3.expected.less @@ -0,0 +1,6 @@ +p { + // Get in line! + @var: white; + /* One hell of a comment */ + color: tomato; +} diff --git a/test/sort-order-less/comments-3.less b/test/sort-order-less/comments-3.less new file mode 100644 index 00000000..c7f0a84f --- /dev/null +++ b/test/sort-order-less/comments-3.less @@ -0,0 +1,6 @@ +p { + /* One hell of a comment */ + color: tomato; + // Get in line! + @var: white; +} diff --git a/test/sort-order-less/comments-4.expected.less b/test/sort-order-less/comments-4.expected.less new file mode 100644 index 00000000..349b7702 --- /dev/null +++ b/test/sort-order-less/comments-4.expected.less @@ -0,0 +1,4 @@ +p { + @var: white; // Get in line! + color: tomato; /* One hell of a comment */ + } diff --git a/test/sort-order-less/comments-4.less b/test/sort-order-less/comments-4.less new file mode 100644 index 00000000..b1db838f --- /dev/null +++ b/test/sort-order-less/comments-4.less @@ -0,0 +1,4 @@ +p { + color: tomato; /* One hell of a comment */ + @var: white; // Get in line! + } diff --git a/test/sort-order-less/different-groups.expected.less b/test/sort-order-less/different-groups.expected.less new file mode 100644 index 00000000..dddc16f8 --- /dev/null +++ b/test/sort-order-less/different-groups.expected.less @@ -0,0 +1,5 @@ +div { + top: 0; + + color: tomato; +} diff --git a/test/sort-order-less/different-groups.less b/test/sort-order-less/different-groups.less new file mode 100644 index 00000000..7ffa908a --- /dev/null +++ b/test/sort-order-less/different-groups.less @@ -0,0 +1,4 @@ +div { + color: tomato; + top: 0; +} diff --git a/test/sort-order-less/import.expected.less b/test/sort-order-less/import.expected.less new file mode 100644 index 00000000..9c403dfe --- /dev/null +++ b/test/sort-order-less/import.expected.less @@ -0,0 +1 @@ +div {@import "foo.css"; color: tomato; } diff --git a/test/sort-order-less/import.less b/test/sort-order-less/import.less new file mode 100644 index 00000000..681ccd06 --- /dev/null +++ b/test/sort-order-less/import.less @@ -0,0 +1 @@ +div { color: tomato; @import "foo.css"; } diff --git a/test/sort-order-less/mixin-1.expected.less b/test/sort-order-less/mixin-1.expected.less new file mode 100644 index 00000000..11ac5fa5 --- /dev/null +++ b/test/sort-order-less/mixin-1.expected.less @@ -0,0 +1,12 @@ +.bordered { + border-top: dotted 1px black; + border-bottom: solid 2px black; +} +#menu a { + .bordered; + color: #111; + } +.post a { + .bordered; + color: red; + } diff --git a/test/sort-order-less/mixin-1.less b/test/sort-order-less/mixin-1.less new file mode 100644 index 00000000..e5d5ad0b --- /dev/null +++ b/test/sort-order-less/mixin-1.less @@ -0,0 +1,12 @@ +.bordered { + border-bottom: solid 2px black; + border-top: dotted 1px black; +} +#menu a { + color: #111; + .bordered; + } +.post a { + color: red; + .bordered; + } diff --git a/test/sort-order-less/mixin-2.expected.less b/test/sort-order-less/mixin-2.expected.less new file mode 100644 index 00000000..1535db16 --- /dev/null +++ b/test/sort-order-less/mixin-2.expected.less @@ -0,0 +1,6 @@ +.test { + .test1(); + .test2(); + top: 0; + color: tomato; + } diff --git a/test/sort-order-less/mixin-2.less b/test/sort-order-less/mixin-2.less new file mode 100644 index 00000000..cbd0c8ea --- /dev/null +++ b/test/sort-order-less/mixin-2.less @@ -0,0 +1,6 @@ +.test { + .test1(); + color: tomato; + .test2(); + top: 0; + } diff --git a/test/sort-order-less/mixin-3.expected.less b/test/sort-order-less/mixin-3.expected.less new file mode 100644 index 00000000..0e4c018d --- /dev/null +++ b/test/sort-order-less/mixin-3.expected.less @@ -0,0 +1,5 @@ +.foo { + .linear-gradient(#fff; #000); + border: 1px solid #f00; + color: #0f0; +} diff --git a/test/sort-order-less/mixin-3.less b/test/sort-order-less/mixin-3.less new file mode 100644 index 00000000..fce214a5 --- /dev/null +++ b/test/sort-order-less/mixin-3.less @@ -0,0 +1,5 @@ +.foo { + color: #0f0; + border: 1px solid #f00; + .linear-gradient(#fff; #000); +} diff --git a/test/sort-order-less/nested-rule-1.expected.less b/test/sort-order-less/nested-rule-1.expected.less new file mode 100644 index 00000000..e85581f5 --- /dev/null +++ b/test/sort-order-less/nested-rule-1.expected.less @@ -0,0 +1,6 @@ +div { + color: tomato; a { + top: 0; + color: nani; + } +} diff --git a/test/sort-order-less/nested-rule-1.less b/test/sort-order-less/nested-rule-1.less new file mode 100644 index 00000000..af115947 --- /dev/null +++ b/test/sort-order-less/nested-rule-1.less @@ -0,0 +1,6 @@ +div { + color: tomato; a { + color: nani; + top: 0; + } +} diff --git a/test/sort-order-less/nested-rule-2.expected.less b/test/sort-order-less/nested-rule-2.expected.less new file mode 100644 index 00000000..30c8b653 --- /dev/null +++ b/test/sort-order-less/nested-rule-2.expected.less @@ -0,0 +1,7 @@ +div { + left: 0; + color: tomato; a { + top: 0; + color: nani; + } +} diff --git a/test/sort-order-less/nested-rule-2.less b/test/sort-order-less/nested-rule-2.less new file mode 100644 index 00000000..52df5998 --- /dev/null +++ b/test/sort-order-less/nested-rule-2.less @@ -0,0 +1,7 @@ +div { + color: tomato; a { + color: nani; + top: 0; + } + left: 0; +} diff --git a/test/sort-order-less/rule.expected.less b/test/sort-order-less/rule.expected.less new file mode 100644 index 00000000..918bd37c --- /dev/null +++ b/test/sort-order-less/rule.expected.less @@ -0,0 +1,4 @@ +div { + top: 0; + color: tomato; +} diff --git a/test/sort-order-less/rule.less b/test/sort-order-less/rule.less new file mode 100644 index 00000000..7ffa908a --- /dev/null +++ b/test/sort-order-less/rule.less @@ -0,0 +1,4 @@ +div { + color: tomato; + top: 0; +} diff --git a/test/sort-order-less/variable.expected.less b/test/sort-order-less/variable.expected.less new file mode 100644 index 00000000..e391754f --- /dev/null +++ b/test/sort-order-less/variable.expected.less @@ -0,0 +1 @@ +div {@red: tomato; color: @red; } diff --git a/test/sort-order-less/variable.less b/test/sort-order-less/variable.less new file mode 100644 index 00000000..5c6756b9 --- /dev/null +++ b/test/sort-order-less/variable.less @@ -0,0 +1 @@ +div { color: @red; @red: tomato; } diff --git a/test/sort-order-scss.js b/test/sort-order-scss.js index 84e7c5b9..5105f914 100644 --- a/test/sort-order-scss.js +++ b/test/sort-order-scss.js @@ -1,5 +1,6 @@ var Comb = require('../lib/csscomb'); var assert = require('assert'); +var fs = require('fs'); describe('options/sort-order (scss)', function() { var comb; @@ -7,23 +8,24 @@ describe('options/sort-order (scss)', function() { var input; var expected; + function readFile(path) { + return fs.readFileSync('test/sort-order-scss/' + path, 'utf8'); + } + beforeEach(function() { comb = new Comb(); }); - afterEach(function() { - comb.configure(config); - assert.equal(comb.processString(input, 'scss'), expected); - }); - it('Should sort properties inside rules', function() { config = { 'sort-order': [ ['top', 'color'] ] }; - input = 'div { color: tomato; top: 0; }'; + input = readFile('rule.scss'); + expected = readFile('rule.expected.scss'); - expected = 'div {top: 0; color: tomato; }'; + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); }); it('Should sort properties inside nested rules', function() { @@ -31,9 +33,11 @@ describe('options/sort-order (scss)', function() { ['top', 'color'] ] }; - input = 'div { color: tomato; a { color: nani; top: 0; } }'; + input = readFile('nested-rule-1.scss'); + expected = readFile('nested-rule-1.expected.scss'); - expected = 'div { color: tomato; a {top: 0; color: nani; } }'; + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); }); it('Should sort properties divided by nested rules', function() { @@ -41,9 +45,11 @@ describe('options/sort-order (scss)', function() { ['top', 'left', 'color'] ] }; - input = 'div { color: tomato; a { color: nani; top: 0; } left: 0; }'; + input = readFile('nested-rule-2.scss'); + expected = readFile('nested-rule-2.expected.scss'); - expected = 'div { left: 0; color: tomato; a {top: 0; color: nani; }}'; + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); }); it('Should group declarations with proper comments and spaces (multiple lines)', function() { @@ -51,21 +57,11 @@ describe('options/sort-order (scss)', function() { ['top', 'color'] ] }; - input = 'div {\n' + - ' color: tomato; /* 1 */\n' + - ' /* 2 */\n' + - ' /* 3 */\n' + - ' top: 0; /* 4 */\n' + - ' /* 5 */\n' + - '}'; - - expected = 'div {\n' + - ' /* 2 */\n' + - ' /* 3 */\n' + - ' top: 0; /* 4 */\n' + - ' color: tomato; /* 1 */\n' + - ' /* 5 */\n' + - '}'; + input = readFile('comments-1.scss'); + expected = readFile('comments-1.expected.scss'); + + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); }); it('Should group declarations with proper comments and spaces (single line)', function() { @@ -73,9 +69,11 @@ describe('options/sort-order (scss)', function() { ['top', 'color'] ] }; - input = 'div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */}'; + input = readFile('comments-2.scss'); + expected = readFile('comments-2.expected.scss'); - expected = 'div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ }'; + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); }); it('Should divide properties from different groups with an empty line', function() { @@ -83,16 +81,11 @@ describe('options/sort-order (scss)', function() { ['top'], ['color'] ] }; - input = 'div {\n' + - ' color: tomato;\n' + - ' top: 0;\n' + - '}'; + input = readFile('different-groups.scss'); + expected = readFile('different-groups.expected.scss'); - expected = 'div {\n' + - ' top: 0;\n' + - '\n' + - ' color: tomato;\n' + - '}'; + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); }); it('Should sort variables', function() { @@ -100,9 +93,11 @@ describe('options/sort-order (scss)', function() { ['$variable', 'color'] ] }; - input = 'div { color: $tomato; $red: tomato; }'; + input = readFile('variable.scss'); + expected = readFile('variable.expected.scss'); - expected = 'div {$red: tomato; color: $tomato; }'; + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); }); it('Should sort imports', function() { @@ -110,9 +105,11 @@ describe('options/sort-order (scss)', function() { ['$import', 'color'] ] }; - input = 'div { color: tomato; @import "foo.css"; }'; + input = readFile('import.scss'); + expected = readFile('import.expected.scss'); - expected = 'div {@import "foo.css"; color: tomato; }'; + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); }); it('Should sort @include-s', function() { @@ -120,9 +117,11 @@ describe('options/sort-order (scss)', function() { ['$include', 'color'] ] }; - input = 'div { color: tomato; @include .nani; }'; + input = readFile('include.scss'); + expected = readFile('include.expected.scss'); - expected = 'div {@include .nani; color: tomato; }'; + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); }); it('Should sort @extend-s', function() { @@ -130,9 +129,11 @@ describe('options/sort-order (scss)', function() { ['$include', 'color'] ] }; - input = 'div { color: tomato; @extend %nani; }'; + input = readFile('extend.scss'); + expected = readFile('extend.expected.scss'); - expected = 'div {@extend %nani; color: tomato; }'; + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); }); it('Should sort properties inside blocks passed to mixins', function() { @@ -140,8 +141,10 @@ describe('options/sort-order (scss)', function() { ['top', 'color'] ] }; - input = '.foo { @include nani { color: tomato; top: 0; } }'; + input = readFile('mixin.scss'); + expected = readFile('mixin.expected.scss'); - expected = '.foo { @include nani {top: 0; color: tomato; } }'; + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); }); }); diff --git a/test/sort-order-scss/comments-1.expected.scss b/test/sort-order-scss/comments-1.expected.scss new file mode 100644 index 00000000..0aa257a7 --- /dev/null +++ b/test/sort-order-scss/comments-1.expected.scss @@ -0,0 +1,7 @@ +div { + /* 2 */ + /* 3 */ + top: 0; /* 4 */ + color: tomato; /* 1 */ + /* 5 */ +} diff --git a/test/sort-order-scss/comments-1.scss b/test/sort-order-scss/comments-1.scss new file mode 100644 index 00000000..18e6319b --- /dev/null +++ b/test/sort-order-scss/comments-1.scss @@ -0,0 +1,7 @@ +div { + color: tomato; /* 1 */ + /* 2 */ + /* 3 */ + top: 0; /* 4 */ + /* 5 */ +} diff --git a/test/sort-order-scss/comments-2.expected.scss b/test/sort-order-scss/comments-2.expected.scss new file mode 100644 index 00000000..a9b2976c --- /dev/null +++ b/test/sort-order-scss/comments-2.expected.scss @@ -0,0 +1 @@ +div {top: 0; /* 3 */ /* 4 *//* 1 */ color: tomato; /* 2 */ } diff --git a/test/sort-order-scss/comments-2.scss b/test/sort-order-scss/comments-2.scss new file mode 100644 index 00000000..617d4fdc --- /dev/null +++ b/test/sort-order-scss/comments-2.scss @@ -0,0 +1 @@ +div {/* 1 */ color: tomato; /* 2 */ top: 0; /* 3 */ /* 4 */} diff --git a/test/sort-order-scss/different-groups.expected.scss b/test/sort-order-scss/different-groups.expected.scss new file mode 100644 index 00000000..dddc16f8 --- /dev/null +++ b/test/sort-order-scss/different-groups.expected.scss @@ -0,0 +1,5 @@ +div { + top: 0; + + color: tomato; +} diff --git a/test/sort-order-scss/different-groups.scss b/test/sort-order-scss/different-groups.scss new file mode 100644 index 00000000..7ffa908a --- /dev/null +++ b/test/sort-order-scss/different-groups.scss @@ -0,0 +1,4 @@ +div { + color: tomato; + top: 0; +} diff --git a/test/sort-order-scss/extend.expected.scss b/test/sort-order-scss/extend.expected.scss new file mode 100644 index 00000000..a1ac4c71 --- /dev/null +++ b/test/sort-order-scss/extend.expected.scss @@ -0,0 +1 @@ +div {@extend %nani; color: tomato; } diff --git a/test/sort-order-scss/extend.scss b/test/sort-order-scss/extend.scss new file mode 100644 index 00000000..381bc927 --- /dev/null +++ b/test/sort-order-scss/extend.scss @@ -0,0 +1 @@ +div { color: tomato; @extend %nani; } diff --git a/test/sort-order-scss/import.expected.scss b/test/sort-order-scss/import.expected.scss new file mode 100644 index 00000000..9c403dfe --- /dev/null +++ b/test/sort-order-scss/import.expected.scss @@ -0,0 +1 @@ +div {@import "foo.css"; color: tomato; } diff --git a/test/sort-order-scss/import.scss b/test/sort-order-scss/import.scss new file mode 100644 index 00000000..681ccd06 --- /dev/null +++ b/test/sort-order-scss/import.scss @@ -0,0 +1 @@ +div { color: tomato; @import "foo.css"; } diff --git a/test/sort-order-scss/include.expected.scss b/test/sort-order-scss/include.expected.scss new file mode 100644 index 00000000..ea381a79 --- /dev/null +++ b/test/sort-order-scss/include.expected.scss @@ -0,0 +1 @@ +div {@include .nani; color: tomato; } diff --git a/test/sort-order-scss/include.scss b/test/sort-order-scss/include.scss new file mode 100644 index 00000000..c0ed1345 --- /dev/null +++ b/test/sort-order-scss/include.scss @@ -0,0 +1 @@ +div { color: tomato; @include .nani; } diff --git a/test/sort-order-scss/mixin.expected.scss b/test/sort-order-scss/mixin.expected.scss new file mode 100644 index 00000000..abfbef4e --- /dev/null +++ b/test/sort-order-scss/mixin.expected.scss @@ -0,0 +1 @@ +.foo { @include nani {top: 0; color: tomato; } } diff --git a/test/sort-order-scss/mixin.scss b/test/sort-order-scss/mixin.scss new file mode 100644 index 00000000..b174d3bd --- /dev/null +++ b/test/sort-order-scss/mixin.scss @@ -0,0 +1 @@ +.foo { @include nani { color: tomato; top: 0; } } diff --git a/test/sort-order-scss/nested-rule-1.expected.scss b/test/sort-order-scss/nested-rule-1.expected.scss new file mode 100644 index 00000000..54dada66 --- /dev/null +++ b/test/sort-order-scss/nested-rule-1.expected.scss @@ -0,0 +1 @@ +div { color: tomato; a {top: 0; color: nani; } } diff --git a/test/sort-order-scss/nested-rule-1.scss b/test/sort-order-scss/nested-rule-1.scss new file mode 100644 index 00000000..ee50453a --- /dev/null +++ b/test/sort-order-scss/nested-rule-1.scss @@ -0,0 +1 @@ +div { color: tomato; a { color: nani; top: 0; } } diff --git a/test/sort-order-scss/nested-rule-2.expected.scss b/test/sort-order-scss/nested-rule-2.expected.scss new file mode 100644 index 00000000..f5c2e9fd --- /dev/null +++ b/test/sort-order-scss/nested-rule-2.expected.scss @@ -0,0 +1 @@ +div { left: 0; color: tomato; a {top: 0; color: nani; }} diff --git a/test/sort-order-scss/nested-rule-2.scss b/test/sort-order-scss/nested-rule-2.scss new file mode 100644 index 00000000..0bc62125 --- /dev/null +++ b/test/sort-order-scss/nested-rule-2.scss @@ -0,0 +1 @@ +div { color: tomato; a { color: nani; top: 0; } left: 0; } diff --git a/test/sort-order-scss/rule.expected.scss b/test/sort-order-scss/rule.expected.scss new file mode 100644 index 00000000..a7669bdf --- /dev/null +++ b/test/sort-order-scss/rule.expected.scss @@ -0,0 +1 @@ +div {top: 0; color: tomato; } diff --git a/test/sort-order-scss/rule.scss b/test/sort-order-scss/rule.scss new file mode 100644 index 00000000..561b980a --- /dev/null +++ b/test/sort-order-scss/rule.scss @@ -0,0 +1 @@ +div { color: tomato; top: 0; } diff --git a/test/sort-order-scss/variable.expected.scss b/test/sort-order-scss/variable.expected.scss new file mode 100644 index 00000000..c17c6341 --- /dev/null +++ b/test/sort-order-scss/variable.expected.scss @@ -0,0 +1 @@ +div {$red: tomato; color: $tomato; } diff --git a/test/sort-order-scss/variable.scss b/test/sort-order-scss/variable.scss new file mode 100644 index 00000000..f759f22c --- /dev/null +++ b/test/sort-order-scss/variable.scss @@ -0,0 +1 @@ +div { color: $tomato; $red: tomato; } diff --git a/test/sort-order.js b/test/sort-order.js index 21e310ad..d94af92f 100644 --- a/test/sort-order.js +++ b/test/sort-order.js @@ -1,64 +1,38 @@ var Comb = require('../lib/csscomb'); var assert = require('assert'); +var fs = require('fs'); describe('options/sort-order', function() { - var comb; + function readFile(path) { + return fs.readFileSync('test/sort-order/' + path, 'utf8'); + } + beforeEach(function() { comb = new Comb(); }); it('Should be in expected order in case of 1 group', function() { + var config = { 'sort-order': [ + ['position', 'z-index'] + ] }; - var config = { - 'sort-order': [ - ['position', 'z-index'] - ] - }; - - var input = 'a\n' + - '{\n' + - '\tz-index: 2;\n' + - '\tposition: absolute;\n' + - '}'; - - var expected = 'a\n' + - '{\n' + - '\tposition: absolute;\n' + - '\tz-index: 2;\n' + - '}'; + var input = readFile('single-group.css'); + var expected = readFile('single-group.expected.css'); comb.configure(config); assert.equal(comb.processString(input), expected); - }); it('Shuld be in expected order in case of multiple groups', function() { + var config = { 'sort-order': [ + ['position', 'z-index'], + ['width', 'height'] + ] }; - var config = { - 'sort-order': [ - ['position', 'z-index'], - ['width', 'height'] - ] - }; - - var input = 'a\n' + - '{\n' + - '\tz-index: 2;\n' + - '\tposition: absolute;\n' + - '\theight: 2px;\n' + - '\twidth: 2px;\n' + - '}'; - - var expected = 'a\n' + - '{\n' + - '\tposition: absolute;\n' + - '\tz-index: 2;\n' + - '\n' + - '\twidth: 2px;\n' + - '\theight: 2px;\n' + - '}'; + var input = readFile('multiple-groups.css'); + var expected = readFile('multiple-groups.expected.css'); comb.configure(config); assert.equal(comb.processString(input), expected); @@ -66,51 +40,25 @@ describe('options/sort-order', function() { }); it('Should work correctly with comments in case of 1 group', function() { + var config = { 'sort-order': [ + ['border-bottom', 'font-style'], + ] }; - var config = { - 'sort-order': [ - ['border-bottom', 'font-style'], - ] - }; - - var input = 'div p em {\n' + - '\t/* upline comment */\n' + - '\tfont-style:italic;\n' + - '\tborder-bottom:1px solid red; /* trololo */ /* trololo */\n' + - '}'; - - var expected = 'div p em {\n' + - '\tborder-bottom:1px solid red; /* trololo */ /* trololo */\n' + - '\t/* upline comment */\n' + - '\tfont-style:italic;\n' + - '}'; + var input = readFile('single-group-comments.css'); + var expected = readFile('single-group-comments.expected.css'); comb.configure(config); assert.equal(comb.processString(input), expected); - }); it('Should work correctly with comments in case of multiple groups', function() { + var config = { 'sort-order': [ + ['margin'], + ['padding'] + ] }; - var config = { - 'sort-order': [ - ['margin'], - ['padding'] - ] - }; - - var input = 'a, b, i /* foobar */\n' + - '{\n' + - ' padding: 0;\n' + - ' margin: 0;\n' + - ' }'; - - var expected = 'a, b, i /* foobar */\n' + - '{\n' + - ' margin: 0;\n' + - '\n' + - ' padding: 0;\n' + - ' }'; + var input = readFile('multiple-groups-comments.css'); + var expected = readFile('multiple-groups-comments.expected.css'); comb.configure(config); assert.equal(comb.processString(input), expected); diff --git a/test/sort-order/multiple-groups-comments.css b/test/sort-order/multiple-groups-comments.css new file mode 100644 index 00000000..5012f20f --- /dev/null +++ b/test/sort-order/multiple-groups-comments.css @@ -0,0 +1,5 @@ +a, b, i /* foobar */ +{ + padding: 0; + margin: 0; + } diff --git a/test/sort-order/multiple-groups-comments.expected.css b/test/sort-order/multiple-groups-comments.expected.css new file mode 100644 index 00000000..49a1ba88 --- /dev/null +++ b/test/sort-order/multiple-groups-comments.expected.css @@ -0,0 +1,6 @@ +a, b, i /* foobar */ +{ + margin: 0; + + padding: 0; + } diff --git a/test/sort-order/multiple-groups.css b/test/sort-order/multiple-groups.css new file mode 100644 index 00000000..a16d45e7 --- /dev/null +++ b/test/sort-order/multiple-groups.css @@ -0,0 +1,7 @@ +a +{ + z-index: 2; + position: absolute; + height: 2px; + width: 2px; +} diff --git a/test/sort-order/multiple-groups.expected.css b/test/sort-order/multiple-groups.expected.css new file mode 100644 index 00000000..3f27b458 --- /dev/null +++ b/test/sort-order/multiple-groups.expected.css @@ -0,0 +1,8 @@ +a +{ + position: absolute; + z-index: 2; + + width: 2px; + height: 2px; +} diff --git a/test/sort-order/single-group-comments.css b/test/sort-order/single-group-comments.css new file mode 100644 index 00000000..dc78ba3b --- /dev/null +++ b/test/sort-order/single-group-comments.css @@ -0,0 +1,5 @@ +div p em { + /* upline comment */ + font-style:italic; + border-bottom:1px solid red; /* trololo */ /* trololo */ +} diff --git a/test/sort-order/single-group-comments.expected.css b/test/sort-order/single-group-comments.expected.css new file mode 100644 index 00000000..ca4e1665 --- /dev/null +++ b/test/sort-order/single-group-comments.expected.css @@ -0,0 +1,5 @@ +div p em { + border-bottom:1px solid red; /* trololo */ /* trololo */ + /* upline comment */ + font-style:italic; +} diff --git a/test/sort-order/single-group.css b/test/sort-order/single-group.css new file mode 100644 index 00000000..ed8925d0 --- /dev/null +++ b/test/sort-order/single-group.css @@ -0,0 +1,5 @@ +a +{ + z-index: 2; + position: absolute; +} diff --git a/test/sort-order/single-group.expected.css b/test/sort-order/single-group.expected.css new file mode 100644 index 00000000..5bcb46c2 --- /dev/null +++ b/test/sort-order/single-group.expected.css @@ -0,0 +1,5 @@ +a +{ + position: absolute; + z-index: 2; +} From 35159d84c03b41927f3f3cf3993e9c6c6ea1b70c Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Fri, 15 Nov 2013 12:39:29 +0400 Subject: [PATCH 23/75] Sort order: Handle properties preceeding rulesets Previously, if a property-value pair was followed by a linebreak and anything but property, import or include (e.g. ruleset or condition), the pair appeared twice in the result code. For example, a { color: tomato; span {foo: bar}} would become: a { color: tomato; color: tomato; span {foo: bar}} That was due to incorrect removal of spaces form `deleted` array. This commit fixes the issue. --- lib/options/sort-order.js | 2 + test/sort-order-scss.js | 38 ++++++++++++++++++- test/sort-order-scss/condition.expected.scss | 8 ++++ test/sort-order-scss/condition.scss | 8 ++++ .../rule-multiline.expected.scss | 4 ++ test/sort-order-scss/rule-multiline.scss | 4 ++ test/sort-order-scss/ruleset.expected.scss | 8 ++++ test/sort-order-scss/ruleset.scss | 8 ++++ 8 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 test/sort-order-scss/condition.expected.scss create mode 100644 test/sort-order-scss/condition.scss create mode 100644 test/sort-order-scss/rule-multiline.expected.scss create mode 100644 test/sort-order-scss/rule-multiline.scss create mode 100644 test/sort-order-scss/ruleset.expected.scss create mode 100644 test/sort-order-scss/ruleset.scss diff --git a/lib/options/sort-order.js b/lib/options/sort-order.js index ce2be533..98e5083f 100644 --- a/lib/options/sort-order.js +++ b/lib/options/sort-order.js @@ -223,6 +223,8 @@ module.exports = { } } + // If current node is not property-value pair or import or include, + // skip it and continue with the next node: if (!propertyName) { deleted.splice(deleted.length - sc0.length, deleted.length + 1); continue; diff --git a/test/sort-order-scss.js b/test/sort-order-scss.js index 5105f914..39764c84 100644 --- a/test/sort-order-scss.js +++ b/test/sort-order-scss.js @@ -16,7 +16,19 @@ describe('options/sort-order (scss)', function() { comb = new Comb(); }); - it('Should sort properties inside rules', function() { + it('Should sort properties inside rules (single line)', function() { + config = { 'sort-order': [ + ['top', 'color'] + ] }; + + input = readFile('rule.scss'); + expected = readFile('rule.expected.scss'); + + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); + }); + + it('Should sort properties inside rules (multiple lines)', function() { config = { 'sort-order': [ ['top', 'color'] ] }; @@ -147,4 +159,28 @@ describe('options/sort-order (scss)', function() { comb.configure(config); assert.equal(comb.processString(input, 'scss'), expected); }); + + it('Should handle properties preceeding rulesets', function() { + config = { 'sort-order': [ + ['top', 'left', 'color'] + ] }; + + input = readFile('ruleset.scss'); + expected = readFile('ruleset.expected.scss'); + + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); + }); + + it('Should handle properties preceeding conditions', function() { + config = { 'sort-order': [ + ['font-size', 'display', 'top', 'color'] + ] }; + + input = readFile('condition.scss'); + expected = readFile('condition.expected.scss'); + + comb.configure(config); + assert.equal(comb.processString(input, 'scss'), expected); + }); }); diff --git a/test/sort-order-scss/condition.expected.scss b/test/sort-order-scss/condition.expected.scss new file mode 100644 index 00000000..e56af512 --- /dev/null +++ b/test/sort-order-scss/condition.expected.scss @@ -0,0 +1,8 @@ +div { + top: 0; + color: tomato; + @if @color == tomato { + font-size: 2px; + display: block; + } + } diff --git a/test/sort-order-scss/condition.scss b/test/sort-order-scss/condition.scss new file mode 100644 index 00000000..9211813d --- /dev/null +++ b/test/sort-order-scss/condition.scss @@ -0,0 +1,8 @@ +div { + color: tomato; + @if @color == tomato { + display: block; + font-size: 2px; + } + top: 0; + } diff --git a/test/sort-order-scss/rule-multiline.expected.scss b/test/sort-order-scss/rule-multiline.expected.scss new file mode 100644 index 00000000..5f6d5e30 --- /dev/null +++ b/test/sort-order-scss/rule-multiline.expected.scss @@ -0,0 +1,4 @@ +div { + top: 0; + color: tomato; + } diff --git a/test/sort-order-scss/rule-multiline.scss b/test/sort-order-scss/rule-multiline.scss new file mode 100644 index 00000000..8ae29314 --- /dev/null +++ b/test/sort-order-scss/rule-multiline.scss @@ -0,0 +1,4 @@ +div { + color: tomato; + top: 0; + } diff --git a/test/sort-order-scss/ruleset.expected.scss b/test/sort-order-scss/ruleset.expected.scss new file mode 100644 index 00000000..194ff87b --- /dev/null +++ b/test/sort-order-scss/ruleset.expected.scss @@ -0,0 +1,8 @@ +div { + left: 0; + color: tomato; + a { + top: 0; + color: nani; + } +} diff --git a/test/sort-order-scss/ruleset.scss b/test/sort-order-scss/ruleset.scss new file mode 100644 index 00000000..d7ed6412 --- /dev/null +++ b/test/sort-order-scss/ruleset.scss @@ -0,0 +1,8 @@ +div { + color: tomato; + a { + color: nani; + top: 0; + } + left: 0; +} From bd64801cbe94479fafdc617be4b5b3c16cdbef49 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Sat, 12 Oct 2013 21:00:32 +0200 Subject: [PATCH 24/75] First steps to detection --- lib/csscomb.js | 88 ++++++++++++++++++++++++++++++++- lib/options/always-semicolon.js | 18 +++++++ test/always-semicolon.js | 64 ++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 2 deletions(-) diff --git a/lib/csscomb.js b/lib/csscomb.js index ae4b2370..d8dc24ac 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -31,6 +31,7 @@ var Comb = function() { 'vendor-prefix-align' ]; this._exclude = null; + this._detect = false; }; Comb.prototype = { @@ -65,6 +66,30 @@ Comb.prototype = { this._lint = config.lint; }, + /** + * Sets up the detection environment. + */ + detect: function() { + this._detect = true; + this._detected = {}; + this._handlers = []; + this._options.forEach(function(option) { + try { + var handler = require('./options/' + option); + if (handler) { + handler._name = option; + this._detected[option] = []; + this._handlers.push(handler); + } + } catch (e) { + console.warn('Error loading "%s" handler: %s', option, e.message); + } + }, this); + + this.processed = 0; + this.changed = 0; + }, + /** * Processes stylesheet tree node. * @@ -92,7 +117,16 @@ Comb.prototype = { if (!Array.isArray(node)) return; var nodeType = node.shift(); - handler.process(nodeType, node, level); + if (this._detect) { + if (handler.detect) { + var detected = handler.detect(nodeType, node, level); + if (detected !== undefined) { + this._detected[handler._name].push(detected); + } + } + } else { + handler.process(nodeType, node, level); + } node.unshift(nodeType); if (nodeType === 'atrulers') level++; @@ -120,7 +154,11 @@ Comb.prototype = { throw new Error('Undefined tree at ' + filename + ': ' + string(text) + ' => ' + string(tree)); } tree = this.processTree(tree); - return gonzales.astToCSS({ syntax: syntax, ast: tree }); + if (this._detect) { + return this._getDetectedOptions(this._detected); + } else { + return gonzales.astToCSS({ syntax: syntax, ast: tree }); + } }, /** @@ -238,6 +276,52 @@ Comb.prototype = { } return this._shouldProcess(path); + }, + + /** + * Gets the detected options. + * + * @param {Object} detected + * @returns {Object} + */ + _getDetectedOptions: function(detected) { + var options = {}; + Object.keys(detected).forEach(function(option) { + var values = detected[option]; + var i; + if (values.length) { + if (values.length === 1) { + options[option] = values[0]; + } else { + var variants = {}; + var best_guess = null; + var maximum = 0; + for (i = values.length; i--;) { + if (variants[values[i]]) { + variants[values[i]]++; + } else { + variants[values[i]] = 1; + } + if (variants[values[i]] >= maximum) { + maximum = variants[values[i]]; + best_guess = values[i]; + } + } + if (best_guess !== null) { + options[option] = best_guess; + } + } + } else { + for (i = this._handlers.length; i--;) { + if (this._handlers[i]._name === option && this._handlers[i]._detectDefault !== undefined) { + options[option] = this._handlers[i]._detectDefault; + break; + } + } + } + }, this); + + return options; } }; diff --git a/lib/options/always-semicolon.js b/lib/options/always-semicolon.js index 2fa7cca8..84783ff7 100644 --- a/lib/options/always-semicolon.js +++ b/lib/options/always-semicolon.js @@ -69,5 +69,23 @@ module.exports = { break; } } + }, + + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node) { + if (nodeType === 'block') { + for (var i = node.length; i--;) { + var nodeItem = node[i]; + var type = nodeItem[0]; + if (type === 'declDelim') return true; + + if (type === 'declaration') return false; + } + } } }; diff --git a/test/always-semicolon.js b/test/always-semicolon.js index e6060614..a82b225e 100644 --- a/test/always-semicolon.js +++ b/test/always-semicolon.js @@ -57,4 +57,68 @@ describe('options/always-semicolon', function() { 'div {\ntop: 1px;\nheight: 0; /* 1comment */ /* 2comment */\n}' ); }); + + // Helper to check the detection + function should_detect(a, b) { + comb.detect(); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect semicolon for last property. Test 1', function() { + should_detect( + 'div { height: 0 }', + { + 'always-semicolon': false + } + ); + }); + + it('Should detect semicolon for last property. Test 2', function() { + should_detect( + 'div { height: 0; }', + { + 'always-semicolon': true + } + ); + }); + + it('Should detect semicolon for last property. Test 3', function() { + should_detect( + 'div { height: 0; } div { height: 0 }', + { + 'always-semicolon': true + } + ); + }); + + it('Should detect semicolon for last property. Test 4', function() { + should_detect( + 'div { height: 0 } div { height: 0; } div { height: 0 }', + { + 'always-semicolon': false + } + ); + }); + + it('Should detect semicolon for last property. Test 5', function() { + should_detect( + 'div {\nheight: 0 /* Comment */\n} ' + + 'div { height: 0; }' + + 'div {\ntop: 1px;\nheight: 0 /* 1comment */ /* 2comment */\n}', + { + 'always-semicolon': false + } + ); + }); + + it('Should not detect semicolon for last property if there are no properties', function() { + should_detect( + 'div {}', + {} + ); + }); + }); From 453ded36267ecf7de8240442b6305068c5150833 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Sun, 13 Oct 2013 21:43:52 +0400 Subject: [PATCH 25/75] Added the detection for colon-space option; added way to tell which options to detect --- lib/csscomb.js | 8 +++-- lib/options/colon-space.js | 32 +++++++++++++++++- test/always-semicolon.js | 10 ++++-- test/colon-space.js | 69 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 5 deletions(-) diff --git a/lib/csscomb.js b/lib/csscomb.js index d8dc24ac..fd50fb76 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -69,14 +69,18 @@ Comb.prototype = { /** * Sets up the detection environment. */ - detect: function() { + detect: function(options) { this._detect = true; this._detected = {}; this._handlers = []; this._options.forEach(function(option) { try { var handler = require('./options/' + option); - if (handler) { + var shouldDetect = true; + if (options && options.indexOf(option) === -1) { + shouldDetect = false; + } + if (handler && shouldDetect) { handler._name = option; this._detected[option] = []; this._handlers.push(handler); diff --git a/lib/options/colon-space.js b/lib/options/colon-space.js index 2abfc72f..6aac3cc6 100644 --- a/lib/options/colon-space.js +++ b/lib/options/colon-space.js @@ -53,6 +53,36 @@ module.exports = { if (node[0][0] === 's') node.shift(); if (this._value[1] !== '') node.unshift(['s', this._value[1]]); } - } + }, + + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node) { + var result = []; + + if (nodeType === 'declaration') { + var property = node[0]; + var value = node[2]; + if (property[property.length - 1][0] === 's') { + result[0] = property[property.length - 1][1]; + } else { + result[0] = ''; + } + + if (value[1][0] === 's') { + result[1] = value[1][1]; + } else { + result[1] = ''; + } + + } + if (result.length) { + return result; + } + } }; diff --git a/test/always-semicolon.js b/test/always-semicolon.js index a82b225e..6573a315 100644 --- a/test/always-semicolon.js +++ b/test/always-semicolon.js @@ -59,8 +59,8 @@ describe('options/always-semicolon', function() { }); // Helper to check the detection - function should_detect(a, b) { - comb.detect(); + function should_detect(options, a, b) { + comb.detect(options); assert.equal( JSON.stringify(comb.processString(a)), JSON.stringify(b) @@ -69,6 +69,7 @@ describe('options/always-semicolon', function() { it('Should detect semicolon for last property. Test 1', function() { should_detect( + ['always-semicolon'], 'div { height: 0 }', { 'always-semicolon': false @@ -78,6 +79,7 @@ describe('options/always-semicolon', function() { it('Should detect semicolon for last property. Test 2', function() { should_detect( + ['always-semicolon'], 'div { height: 0; }', { 'always-semicolon': true @@ -87,6 +89,7 @@ describe('options/always-semicolon', function() { it('Should detect semicolon for last property. Test 3', function() { should_detect( + ['always-semicolon'], 'div { height: 0; } div { height: 0 }', { 'always-semicolon': true @@ -96,6 +99,7 @@ describe('options/always-semicolon', function() { it('Should detect semicolon for last property. Test 4', function() { should_detect( + ['always-semicolon'], 'div { height: 0 } div { height: 0; } div { height: 0 }', { 'always-semicolon': false @@ -105,6 +109,7 @@ describe('options/always-semicolon', function() { it('Should detect semicolon for last property. Test 5', function() { should_detect( + ['always-semicolon'], 'div {\nheight: 0 /* Comment */\n} ' + 'div { height: 0; }' + 'div {\ntop: 1px;\nheight: 0 /* 1comment */ /* 2comment */\n}', @@ -116,6 +121,7 @@ describe('options/always-semicolon', function() { it('Should not detect semicolon for last property if there are no properties', function() { should_detect( + ['always-semicolon'], 'div {}', {} ); diff --git a/test/colon-space.js b/test/colon-space.js index 6514bb78..3678516d 100644 --- a/test/colon-space.js +++ b/test/colon-space.js @@ -146,4 +146,73 @@ describe('options/colon-space', function() { 'a {color\t:\tred}' ); }); + + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect no whitespaces around colon', function() { + should_detect( + ['colon-space'], + 'a { color:red }', + { + 'colon-space': ['', ''] + } + ); + }); + + it('Should detect space after colon', function() { + should_detect( + ['colon-space'], + 'a { color: red }', + { + 'colon-space': ['', ' '] + } + ); + }); + + it('Should detect space after colon from two variants.', function() { + should_detect( + ['colon-space'], + 'a { color: red; color:red }', + { + 'colon-space': ['', ' '] + } + ); + }); + + it('Should detect no whitespaces around colon along three variants', function() { + should_detect( + ['colon-space'], + 'a { color: red; background:red } b { width:10px }', + { + 'colon-space': ['', ''] + } + ); + }); + + it('Should detect space around colon', function() { + should_detect( + ['colon-space'], + 'a { color : red; background :red } b { width:10px }', + { + 'colon-space': [' ', ' '] + } + ); + }); + + it('Should detect different whitespaces around colon', function() { + should_detect( + ['colon-space'], + 'a { color : red; background :red } b { width : 10px }', + { + 'colon-space': [' ', ' '] + } + ); + }); }); From 4cf62d4415ac6801f2ecc6e4095016ac577bb202 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Mon, 14 Oct 2013 12:03:13 +0400 Subject: [PATCH 26/75] Added the detection for color-case option --- lib/options/color-case.js | 17 +++++++++- test/color-case.js | 65 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/lib/options/color-case.js b/lib/options/color-case.js index e1bf5480..e4b16b17 100644 --- a/lib/options/color-case.js +++ b/lib/options/color-case.js @@ -26,6 +26,21 @@ module.exports = { node[0] = node[0].toUpperCase(); } } - } + }, + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node) { + if (nodeType === 'vhash') { + if (node[0].match(/^[^A-F]*[a-f][^A-F]*$/)) { + return 'lower'; + } else if (node[0].match(/^[^a-f]*[A-F][^a-f]*$/)) { + return 'upper'; + } + } + } }; diff --git a/test/color-case.js b/test/color-case.js index 01a7f0e6..f4a823d0 100644 --- a/test/color-case.js +++ b/test/color-case.js @@ -50,4 +50,69 @@ describe('options/color-case', function() { ); }); + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect uppercase color', function() { + should_detect( + ['color-case'], + 'a { color: #F3F3F3 }', + { + 'color-case': 'upper' + } + ); + }); + + it('Should detect lowercase color', function() { + should_detect( + ['color-case'], + 'a { color: #f6f6f6 }', + { + 'color-case': 'lower' + } + ); + }); + + it('Should detect uppercase color in a shorthand', function() { + should_detect( + ['color-case'], + 'a { color: #FFF }', + { + 'color-case': 'upper' + } + ); + }); + + it('Should detect lowercase color in a shorthand', function() { + should_detect( + ['color-case'], + 'a { color: #fff }', + { + 'color-case': 'lower' + } + ); + }); + + it('Shouldn’t detect color case if it contains only digits', function() { + should_detect( + ['color-case'], + 'a { color: #333 }', + {} + ); + }); + + it('Shouldn’t detect color case if it is in mixed case', function() { + should_detect( + ['color-case'], + 'a { color: #fFfFfF }', + {} + ); + }); + }); From 8a0da936c1d89650721ed8f2048d648d1dc52ef6 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Mon, 14 Oct 2013 12:08:42 +0400 Subject: [PATCH 27/75] Added the detection for color-shorthand option --- lib/options/color-shorthand.js | 17 ++++++++++++- test/color-shorthand.js | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/options/color-shorthand.js b/lib/options/color-shorthand.js index 39954053..db650a95 100644 --- a/lib/options/color-shorthand.js +++ b/lib/options/color-shorthand.js @@ -26,6 +26,21 @@ module.exports = { node[0] = node[0].replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3'); } } - } + }, + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node) { + if (nodeType === 'vhash') { + if (node[0].match(/^\w{3}$/)) { + return true; + } else if (node[0].match(/^(\w)\1(\w)\2(\w)\3$/)) { + return false; + } + } + } }; diff --git a/test/color-shorthand.js b/test/color-shorthand.js index 19458b0a..74341ac6 100644 --- a/test/color-shorthand.js +++ b/test/color-shorthand.js @@ -38,4 +38,49 @@ describe('options/color-shorthand', function() { ); }); + + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect non-shorthanded color', function() { + should_detect( + ['color-shorthand'], + 'a { color: #FF33EE }', + { + 'color-shorthand': false + } + ); + }); + + it('Should detect shorthanded color', function() { + should_detect( + ['color-shorthand'], + 'a { color: #fff }', + { + 'color-shorthand': true + } + ); + }); + + it('Shouldn’t detect if a color is shorthanded if it can’t be shorthanded', function() { + should_detect( + ['color-shorthand'], + 'a { color: #F3F3F3 }', + {} + ); + }); + + it('Shouldn’t detect if a color is shorthanded if it is not a vhash', function() { + should_detect( + ['color-shorthand'], + 'a { color: rgba(0,0,0,0.5) }', + {} + ); + }); }); From 353e56df91fea2035015926c48c6a0d098c38356 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Mon, 14 Oct 2013 12:45:08 +0400 Subject: [PATCH 28/75] Added integral detection test --- test/integral.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/integral.js b/test/integral.js index bd79b60c..43831564 100644 --- a/test/integral.js +++ b/test/integral.js @@ -23,4 +23,32 @@ describe('integral test', function() { } }); }); + + it('Should detect everything in integral test', function(done) { + comb = new Comb(); + comb.detect(); + vow.all(['origin', 'expect'].map(function(type) { + var fileName = './test/integral.' + type + '.css'; + return vfs.read(fileName, 'utf8').then(function(data) { + return data; + }); + })) + .then(function(results) { + try { + assert.equal( + JSON.stringify(comb.processString(results[0])), + JSON.stringify({ + 'always-semicolon': true, + 'color-case': 'lower', + 'color-shorthand': true, + 'colon-space': ['', ' '] + }) + ); + done(); + } catch (e) { + done(e); + } + }); + }); + }); From 9a34564f6643c4120016d579139a25be44943769 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Mon, 14 Oct 2013 17:31:29 +0400 Subject: [PATCH 29/75] Added the detection for combinator-space option --- lib/options/combinator-space.js | 50 ++++++++++++++++++++- test/combinator-space.js | 77 +++++++++++++++++++++++++++++++++ test/integral.js | 5 ++- 3 files changed, 129 insertions(+), 3 deletions(-) diff --git a/lib/options/combinator-space.js b/lib/options/combinator-space.js index 3a51f2d3..3a0694a7 100644 --- a/lib/options/combinator-space.js +++ b/lib/options/combinator-space.js @@ -45,6 +45,54 @@ module.exports = { } } } - } + }, + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node) { + if (nodeType === 'selector') { + var variants = {}; + var best_guess = null; + var maximum = 0; + for (var i = node.length; i--;) { + var subSelector = node[i]; + for (var j = subSelector.length; j--;) { + var result = []; + if (subSelector[j][0] === 'combinator') { + // Working with the whitespace after the combinator + if (subSelector[j + 1][0] === 's') { + result[1] = subSelector[j + 1][1]; + } else { + result[1] = ''; + } + // Working with the whitespace before the combinator + if (subSelector[j - 1][0] === 's') { + result[0] = subSelector[j - 1][1]; + } else { + result[0] = ''; + } + } + + if (result.length) { + if (variants[result]) { + variants[result]++; + } else { + variants[result] = 1; + } + if (variants[result] > maximum) { + maximum = variants[result]; + best_guess = result; + } + } + } + } + if (best_guess) { + return best_guess; + } + } + } }; diff --git a/test/combinator-space.js b/test/combinator-space.js index 7c269413..452a50b5 100644 --- a/test/combinator-space.js +++ b/test/combinator-space.js @@ -154,4 +154,81 @@ describe('options/combinator-space', function() { 'a ~\nb +\nc >\nd { color: red }' ); }); + + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect no whitespaces around combinator', function() { + should_detect( + ['combinator-space'], + 'a+b { color:red }', + { + 'combinator-space': ['', ''] + } + ); + }); + + it('Should detect a space around combinator', function() { + should_detect( + ['combinator-space'], + 'a + b { color:red }', + { + 'combinator-space': [' ', ' '] + } + ); + }); + + it('Should detect a mixed spaces around combinator', function() { + should_detect( + ['combinator-space'], + 'a + \n b { color:red }', + { + 'combinator-space': [' ', ' \n '] + } + ); + }); + + it('Should detect a space around combinator in long selector', function() { + should_detect( + ['combinator-space'], + 'a + b ~ c>d { color:red }', + { + 'combinator-space': [' ', ' '] + } + ); + }); + + it('Should detect a space around combinator in long selector, test 2', function() { + should_detect( + ['combinator-space'], + 'a>b + c + d { color:red }', + { + 'combinator-space': [' ', ' '] + } + ); + }); + + it('Should detect no whitespaces around combinator in long selector', function() { + should_detect( + ['combinator-space'], + 'a+b ~ c+d { color:red }', + { + 'combinator-space': ['', ''] + } + ); + }); + + it('Shouldn’t detect whitespaces around combinator in selector without combinators', function() { + should_detect( + ['combinator-space'], + 'a { color:red }', + {} + ); + }); }); diff --git a/test/integral.js b/test/integral.js index 43831564..152393ea 100644 --- a/test/integral.js +++ b/test/integral.js @@ -36,12 +36,13 @@ describe('integral test', function() { .then(function(results) { try { assert.equal( - JSON.stringify(comb.processString(results[0])), + JSON.stringify(comb.processString(results[1])), JSON.stringify({ 'always-semicolon': true, 'color-case': 'lower', 'color-shorthand': true, - 'colon-space': ['', ' '] + 'colon-space': ['', ' '], + 'combinator-space': [' ', ' '] }) ); done(); From a8926b466dbbe2e6d0395cfd2db538b62aa2294e Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Mon, 14 Oct 2013 17:49:51 +0400 Subject: [PATCH 30/75] Added the detection for elements-case option --- lib/options/element-case.js | 41 +++++++++++++++++++++++- test/element-case.js | 64 +++++++++++++++++++++++++++++++++++++ test/integral.js | 1 + 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/lib/options/element-case.js b/lib/options/element-case.js index a0acca70..39c8c4ce 100644 --- a/lib/options/element-case.js +++ b/lib/options/element-case.js @@ -31,6 +31,45 @@ module.exports = { } } } - } + }, + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node) { + if (nodeType === 'simpleselector') { + var variants = {}; + var best_guess = null; + var maximum = 0; + for (var i = node.length; i--;) { + var nodeItem = node[i]; + if (nodeItem[0] === 'ident') { + var result; + if (nodeItem[1].match(/^[a-z]+$/)) { + result = 'lower'; + } else if (nodeItem[1].match(/^[A-Z]+$/)) { + result = 'upper'; + } + + if (result) { + if (variants[result]) { + variants[result]++; + } else { + variants[result] = 1; + } + if (variants[result] > maximum) { + maximum = variants[result]; + best_guess = result; + } + } + } + } + if (best_guess) { + return best_guess; + } + } + } }; diff --git a/test/element-case.js b/test/element-case.js index 27fac093..a4db09ff 100644 --- a/test/element-case.js +++ b/test/element-case.js @@ -48,4 +48,68 @@ describe('options/element-case', function() { ); }); + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect lowercase elements', function() { + should_detect( + ['element-case'], + 'a { color: red }', + { + 'element-case': 'lower' + } + ); + }); + + it('Should detect uppercase elements', function() { + should_detect( + ['element-case'], + 'A { color: red }', + { + 'element-case': 'upper' + } + ); + }); + + it('Should detect lowercase elements in a long selector', function() { + should_detect( + ['element-case'], + 'ul li:not(:hover) A { color: red }', + { + 'element-case': 'lower' + } + ); + }); + + it('Should detect uppercase elements in a long selector', function() { + should_detect( + ['element-case'], + 'ul .lol:not(LI) A { color: red }', + { + 'element-case': 'upper' + } + ); + }); + + it('Shouldn’t detect case of elements in a mixed case', function() { + should_detect( + ['element-case'], + 'aRtIcLe { color: red }', + {} + ); + }); + + it('Shouldn’t detect case of elements when there are no such', function() { + should_detect( + ['element-case'], + '*.lol { color: red }', + {} + ); + }); }); diff --git a/test/integral.js b/test/integral.js index 152393ea..24825ee4 100644 --- a/test/integral.js +++ b/test/integral.js @@ -41,6 +41,7 @@ describe('integral test', function() { 'always-semicolon': true, 'color-case': 'lower', 'color-shorthand': true, + 'element-case': 'lower', 'colon-space': ['', ' '], 'combinator-space': [' ', ' '] }) From 3320960c2ceaf09c352f2898476ca6a009f27446 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Mon, 14 Oct 2013 19:04:54 +0400 Subject: [PATCH 31/75] Added the detection for eof-newline option --- lib/options/eof-newline.js | 17 ++++++++++++- test/eof-newline.js | 49 ++++++++++++++++++++++++++++++++++++++ test/integral.js | 1 + 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/lib/options/eof-newline.js b/lib/options/eof-newline.js index e0802228..04784a46 100644 --- a/lib/options/eof-newline.js +++ b/lib/options/eof-newline.js @@ -28,6 +28,21 @@ module.exports = { lastChild[1] = lastChild[1].replace(/\n$/, ''); if (this._value) lastChild[1] += '\n'; } - } + }, + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node) { + if (nodeType === 'stylesheet') { + if (node[node.length - 1][0] === 's' && node[node.length - 1][1].indexOf('\n') !== -1) { + return true; + } else { + return false; + } + } + } }; diff --git a/test/eof-newline.js b/test/eof-newline.js index 9ed5f3f6..192c430c 100644 --- a/test/eof-newline.js +++ b/test/eof-newline.js @@ -31,4 +31,53 @@ describe('options/eof-newline', function() { 'a {color:red} ' ); }); + + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Shouldn’t detect eof newline', function() { + should_detect( + ['eof-newline'], + 'a { color: red }', + { + 'eof-newline': false + } + ); + }); + + it('Should detect eof newline', function() { + should_detect( + ['eof-newline'], + 'a { color: red }\n', + { + 'eof-newline': true + } + ); + }); + + it('Shouldn’t detect eof newline with spaces at the end', function() { + should_detect( + ['eof-newline'], + 'a { color: red } ', + { + 'eof-newline': false + } + ); + }); + + it('Should detect eof newline with mixed spaces at the end', function() { + should_detect( + ['eof-newline'], + 'a { color: red } \n ', + { + 'eof-newline': true + } + ); + }); }); diff --git a/test/integral.js b/test/integral.js index 24825ee4..17071fd5 100644 --- a/test/integral.js +++ b/test/integral.js @@ -42,6 +42,7 @@ describe('integral test', function() { 'color-case': 'lower', 'color-shorthand': true, 'element-case': 'lower', + 'eof-newline': true, 'colon-space': ['', ' '], 'combinator-space': [' ', ' '] }) From 4ba0b748e39e28028cb99eb05a5058443cb33806 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Tue, 15 Oct 2013 04:10:12 +0400 Subject: [PATCH 32/75] Added the detection for unitless zero option --- lib/options/unitless-zero.js | 35 +++++++++++++++++++- test/integral.js | 3 +- test/unitless-zero.js | 64 ++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/lib/options/unitless-zero.js b/lib/options/unitless-zero.js index a4b48d9e..4b4f3035 100644 --- a/lib/options/unitless-zero.js +++ b/lib/options/unitless-zero.js @@ -29,6 +29,39 @@ module.exports = { } }); } - } + }, + + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node) { + var result = null; + // If we see a zero with unit and it is not degree, then we don’t have an option + if (nodeType === 'percentage' || nodeType === 'dimension') { + if (node[0][1] === '0' && node[1][1] !== 'deg') { + result = false; + } + } + + // If we see a zero and previous node is not percentage or dimension, then we have an option + if ( + nodeType === 'number' && + node[0] === '0' && + this._prev !== 'percentage' && + this._prev !== 'dimension' + ) { + result = true; + } + + // Store the previous nodeType + this._prev = nodeType; + + if (result !== null) { + return result; + } + } }; diff --git a/test/integral.js b/test/integral.js index 17071fd5..4fcdb353 100644 --- a/test/integral.js +++ b/test/integral.js @@ -44,7 +44,8 @@ describe('integral test', function() { 'element-case': 'lower', 'eof-newline': true, 'colon-space': ['', ' '], - 'combinator-space': [' ', ' '] + 'combinator-space': [' ', ' '], + 'unitless-zero': true }) ); done(); diff --git a/test/unitless-zero.js b/test/unitless-zero.js index 6e6b2b9e..580de063 100644 --- a/test/unitless-zero.js +++ b/test/unitless-zero.js @@ -42,4 +42,68 @@ describe('options/unitless-zero', function() { ); }); + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect unitless zero option', function() { + should_detect( + ['unitless-zero'], + 'a { width: 0 }', + { + 'unitless-zero': true + } + ); + }); + + it('Should detect zero with unit', function() { + should_detect( + ['unitless-zero'], + 'a { width: 0px }', + { + 'unitless-zero': false + } + ); + }); + + it('Should detect unitless zero option with multiple values', function() { + should_detect( + ['unitless-zero'], + 'a { padding: 0px 0 0 }', + { + 'unitless-zero': true + } + ); + }); + + it('Should detect zero with unit and multiple values', function() { + should_detect( + ['unitless-zero'], + 'a { padding: 0px 0 0em }', + { + 'unitless-zero': false + } + ); + }); + + it('Shouldn’t detect unitless zero option if there is no unit', function() { + should_detect( + ['unitless-zero'], + 'a { color: red }', + {} + ); + }); + + it('Shouldn’t detect unitless zero option if there is `deg` unit', function() { + should_detect( + ['unitless-zero'], + 'a { transform: rotate(0deg) }', + {} + ); + }); }); From 77df9ef6f18420f2deb695451dd628a55505606d Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Tue, 15 Oct 2013 04:17:54 +0400 Subject: [PATCH 33/75] Added the detection for leading-zero option --- lib/options/leading-zero.js | 17 ++++++++++++++++- test/integral.js | 1 + test/leading-zero.js | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/options/leading-zero.js b/lib/options/leading-zero.js index cb105845..94f52cfc 100644 --- a/lib/options/leading-zero.js +++ b/lib/options/leading-zero.js @@ -27,6 +27,21 @@ module.exports = { node[0] = node[0].replace(/^0+(?=\.)/, ''); } } - } + }, + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node) { + if (nodeType === 'number') { + if (node.toString().match(/^\.[0-9]+/)) { + return false; + } else if (node.toString().match(/^0\.[0-9]+/)) { + return true; + } + } + } }; diff --git a/test/integral.js b/test/integral.js index 4fcdb353..b8c5e9fc 100644 --- a/test/integral.js +++ b/test/integral.js @@ -42,6 +42,7 @@ describe('integral test', function() { 'color-case': 'lower', 'color-shorthand': true, 'element-case': 'lower', + 'leading-zero': false, 'eof-newline': true, 'colon-space': ['', ' '], 'combinator-space': [' ', ' '], diff --git a/test/leading-zero.js b/test/leading-zero.js index 96c6ff03..42987d4b 100644 --- a/test/leading-zero.js +++ b/test/leading-zero.js @@ -28,4 +28,40 @@ describe('options/leading-zero', function() { ); }); + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect leading zero option', function() { + should_detect( + ['leading-zero'], + 'a { width: 0.5em }', + { + 'leading-zero': true + } + ); + }); + + it('Should detect leading zero option set to false', function() { + should_detect( + ['leading-zero'], + 'a { width: .5em }', + { + 'leading-zero': false + } + ); + }); + + it('Shouldn’t detect leading zero option', function() { + should_detect( + ['leading-zero'], + 'a { width: 10.5em }', + {} + ); + }); }); From 3851fb91029249598a856e995b9bf9b09ea7b40e Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Wed, 16 Oct 2013 21:48:12 +0400 Subject: [PATCH 34/75] Added the detection for strip-spaces option --- lib/options/strip-spaces.js | 24 +++++++++- test/integral.js | 1 + test/strip-spaces.js | 91 ++++++++++++++++++++++++++++++++++++- 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/lib/options/strip-spaces.js b/lib/options/strip-spaces.js index 1b6743c4..992eb0c5 100644 --- a/lib/options/strip-spaces.js +++ b/lib/options/strip-spaces.js @@ -39,6 +39,28 @@ module.exports = { */ _trim: function(s) { return s.replace(/[ \t]+\n/g, '\n'); - } + }, + + /** + * Detects the syntax at the tree node. + * This option is treated as `true` by default, but any trailing space would invalidate it. + * + * @param {String} nodeType + * @param {node} node + */ + _detectDefault: true, + detect: function(nodeType, node) { + if (nodeType === 's') { + if (node[0].match(/[ \t]\n/)) { + return false; + } + } + if (nodeType === 'stylesheet') { + var lastChild = node[node.length - 1]; + if (lastChild[0] === 's' && lastChild[1] !== '\n' && lastChild[1].match(/^[ \n\t]+$/)) { + return false; + } + } + } }; diff --git a/test/integral.js b/test/integral.js index b8c5e9fc..3ed002bd 100644 --- a/test/integral.js +++ b/test/integral.js @@ -43,6 +43,7 @@ describe('integral test', function() { 'color-shorthand': true, 'element-case': 'lower', 'leading-zero': false, + 'strip-spaces': true, 'eof-newline': true, 'colon-space': ['', ' '], 'combinator-space': [' ', ' '], diff --git a/test/strip-spaces.js b/test/strip-spaces.js index 6090272d..c403e108 100644 --- a/test/strip-spaces.js +++ b/test/strip-spaces.js @@ -1,7 +1,7 @@ var Comb = require('../lib/csscomb'); var assert = require('assert'); -describe('options/strip-space', function() { +describe('options/strip-spaces', function() { var comb; beforeEach(function() { comb = new Comb(); @@ -35,4 +35,93 @@ describe('options/strip-space', function() { 'a {color:red}' ); }); + + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect strip-spaces option set to `true`', function() { + should_detect( + ['strip-spaces'], + 'a { color: red }', + { + 'strip-spaces': true + } + ); + }); + + it('Should detect strip-spaces option set to `false`', function() { + should_detect( + ['strip-spaces'], + 'a { color: red } ', + { + 'strip-spaces': false + } + ); + }); + + it('Should detect strip-spaces option set to `true` with newline', function() { + should_detect( + ['strip-spaces'], + 'a { color: red }\nb { color: blue }', + { + 'strip-spaces': true + } + ); + }); + + it('Should detect strip-spaces option set to `false` with newline', function() { + should_detect( + ['strip-spaces'], + 'a { color: red } \nb { color: blue }', + { + 'strip-spaces': false + } + ); + }); + + it('Should detect strip-spaces option set to `true` inside a value', function() { + should_detect( + ['strip-spaces'], + 'a {\n color:\n red }', + { + 'strip-spaces': true + } + ); + }); + + it('Should detect strip-spaces option set to `false` inside a value', function() { + should_detect( + ['strip-spaces'], + 'a {\n color: \n red }', + { + 'strip-spaces': false + } + ); + }); + + it('Should detect strip-spaces option set to `true` if the only trailing space is the last newline', function() { + should_detect( + ['strip-spaces'], + 'a { color: red }\n', + { + 'strip-spaces': true + } + ); + }); + + it('Should detect strip-spaces option set to `false` if there is more than one newline at the end', function() { + should_detect( + ['strip-spaces'], + 'a { color: red }\n\n', + { + 'strip-spaces': false + } + ); + }); }); From f0a241e72f9033792a56a762f214d4c6f759b09a Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Thu, 17 Oct 2013 13:38:50 +0400 Subject: [PATCH 35/75] Added the detection for remove-empty-rulesets option --- lib/options/remove-empty-rulesets.js | 18 +++++- test/integral.js | 1 + test/remove-empty-rulesets.js | 91 ++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/lib/options/remove-empty-rulesets.js b/lib/options/remove-empty-rulesets.js index 51acdca9..6ff791e7 100644 --- a/lib/options/remove-empty-rulesets.js +++ b/lib/options/remove-empty-rulesets.js @@ -80,6 +80,22 @@ module.exports = { _isWhitespace: function(node) { return node[0] === 's'; - } + }, + + /** + * Detects the syntax at the tree node. + * This option is treated as `true` by default, but any trailing space would invalidate it. + * + * @param {String} nodeType + * @param {node} node + */ + _detectDefault: true, + detect: function(nodeType, node) { + if (nodeType === 'atrulers' || nodeType === 'block') { + if (node.length === 0 || (node.length === 1 && node[0][0] === 's')) { + return false; + } + } + } }; diff --git a/test/integral.js b/test/integral.js index 3ed002bd..603e92b1 100644 --- a/test/integral.js +++ b/test/integral.js @@ -38,6 +38,7 @@ describe('integral test', function() { assert.equal( JSON.stringify(comb.processString(results[1])), JSON.stringify({ + 'remove-empty-rulesets': true, 'always-semicolon': true, 'color-case': 'lower', 'color-shorthand': true, diff --git a/test/remove-empty-rulesets.js b/test/remove-empty-rulesets.js index ec9ecead..33674086 100644 --- a/test/remove-empty-rulesets.js +++ b/test/remove-empty-rulesets.js @@ -35,6 +35,97 @@ describe('options/remove-empty-rulesets', function() { assert.equal(comb.processString('a { /* comment */ }\nb {} '), 'a { /* comment */ }\n '); }); }); + + describe('detecting the value', function() { + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect this option set to `true`', function() { + should_detect( + ['remove-empty-rulesets'], + 'a { color: red }', + { + 'remove-empty-rulesets': true + } + ); + }); + + it('Should detect this option set to `false` with empty block', function() { + should_detect( + ['remove-empty-rulesets'], + 'a {}', + { + 'remove-empty-rulesets': false + } + ); + }); + + it('Should detect this option set to `false` with block containing whitespace', function() { + should_detect( + ['remove-empty-rulesets'], + 'a { }', + { + 'remove-empty-rulesets': false + } + ); + }); + + it('Should detect this option set to `true` with block containing comment', function() { + should_detect( + ['remove-empty-rulesets'], + 'a { /* Hello */ }', + { + 'remove-empty-rulesets': true + } + ); + }); + + it('Should detect this option set to `true` with media query containing block', function() { + should_detect( + ['remove-empty-rulesets'], + '@media all and (min-width:0) { a { /* Hello */ } }', + { + 'remove-empty-rulesets': true + } + ); + }); + + it('Should detect this option set to `true` with media query containing comment', function() { + should_detect( + ['remove-empty-rulesets'], + '@media all and (min-width:0) {/* Hello */}', + { + 'remove-empty-rulesets': true + } + ); + }); + + it('Should detect this option set to `false` with empty media query', function() { + should_detect( + ['remove-empty-rulesets'], + '@media all and (min-width:0) {}', + { + 'remove-empty-rulesets': false + } + ); + }); + + it('Should detect this option set to `false` with media query containing whitespace', function() { + should_detect( + ['remove-empty-rulesets'], + '@media all and (min-width:0) { \n }', + { + 'remove-empty-rulesets': false + } + ); + }); + }); }); describe('options/remove-empty-rulesets AST manipulation', function() { From 615a66b9ad30033dc1ab601758ec51dfadd07562 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Thu, 17 Oct 2013 19:11:48 +0400 Subject: [PATCH 36/75] Added the detection for stick-brace option --- lib/options/stick-brace.js | 27 +++++++++++++++++++++ test/integral.js | 1 + test/stick-brace.js | 49 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/lib/options/stick-brace.js b/lib/options/stick-brace.js index f1d4d77c..76b1ff49 100644 --- a/lib/options/stick-brace.js +++ b/lib/options/stick-brace.js @@ -31,6 +31,33 @@ module.exports = { } } } + }, + + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node, level) { + if (nodeType === 'selector' || nodeType === 'atruler') { + for (var i = node.length; i--;) { + var nodeItem = node[i]; + if (nodeItem[0] === 'simpleselector' || nodeItem[0] === 'atrulerq') { + var result = ''; + if (nodeItem[nodeItem.length - 1][0] === 's') { + result = nodeItem[nodeItem.length - 1][1]; + } + if (this._prev !== undefined && this._prev[0] < level) { + result = result.replace(result.replace(this._prev[1], ''), ''); + } + if (this._prev === undefined || this._prev[0] !== level) { + this._prev = [level, result]; + } + return result; + } + } + } } }; diff --git a/test/integral.js b/test/integral.js index 603e92b1..0e02b2c9 100644 --- a/test/integral.js +++ b/test/integral.js @@ -46,6 +46,7 @@ describe('integral test', function() { 'leading-zero': false, 'strip-spaces': true, 'eof-newline': true, + 'stick-brace': '\n', 'colon-space': ['', ' '], 'combinator-space': [' ', ' '], 'unitless-zero': true diff --git a/test/stick-brace.js b/test/stick-brace.js index 29c52f99..166611ee 100644 --- a/test/stick-brace.js +++ b/test/stick-brace.js @@ -44,4 +44,53 @@ describe('options/stick-brace', function() { '@media all\n{ .input__control\n{ color: #000;\n \n }\t}' ); }); + + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect the empty stick-brace option', function() { + should_detect( + ['stick-brace'], + 'a{ color: red }', + { + 'stick-brace': '' + } + ); + }); + + it('Should detect the stick-brace option equal to a single space', function() { + should_detect( + ['stick-brace'], + 'a {\ncolor: red }', + { + 'stick-brace': ' ' + } + ); + }); + + it('Should detect the stick-brace option equal to a newline with spaces', function() { + should_detect( + ['stick-brace'], + '.input__control\n { color: #000;\n \n }', + { + 'stick-brace': '\n ' + } + ); + }); + + it('Should detect the stick-brace option equal to a newline when nested in mq', function() { + should_detect( + ['stick-brace'], + '@media all\n{\n .input__control\n { color: #000;\n \n }\t}', + { + 'stick-brace': '\n' + } + ); + }); }); From 453818345f0a9bc500f3f67ee5bee4268cc14018 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Thu, 17 Oct 2013 19:47:34 +0400 Subject: [PATCH 37/75] Added the detection for rule-indent option --- lib/options/rule-indent.js | 32 +++++++++++++++++++++++++ test/integral.js | 1 + test/rule-indent.js | 49 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/lib/options/rule-indent.js b/lib/options/rule-indent.js index 470058d0..1e3cdc5d 100644 --- a/lib/options/rule-indent.js +++ b/lib/options/rule-indent.js @@ -43,6 +43,38 @@ module.exports = { } } } + }, + + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node, level) { + var result = null; + + if (nodeType === 'declaration') { + if (this._prev !== undefined) { + result = this._prev.replace(/\n/g, ''); + if (level > 0) { + result = result.substr(0, parseInt(result.length / (level + 1), 10)); + } + } else { + result = ''; + } + } + + // Store the previous nodeType + if (nodeType === 's') { + this._prev = node[0]; + } else { + this._prev = undefined; + } + + if (result !== null) { + return result; + } } }; diff --git a/test/integral.js b/test/integral.js index 0e02b2c9..bcf65cc0 100644 --- a/test/integral.js +++ b/test/integral.js @@ -49,6 +49,7 @@ describe('integral test', function() { 'stick-brace': '\n', 'colon-space': ['', ' '], 'combinator-space': [' ', ' '], + 'rule-indent': ' ', 'unitless-zero': true }) ); diff --git a/test/rule-indent.js b/test/rule-indent.js index 3826a82d..ce55732c 100644 --- a/test/rule-indent.js +++ b/test/rule-indent.js @@ -56,4 +56,53 @@ describe('options/rule-indent', function() { 'a {}' ); }); + + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect the empty rule-indent option', function() { + should_detect( + ['rule-indent'], + 'a{\ncolor: red\n}', + { + 'rule-indent': '' + } + ); + }); + + it('Should detect the rule-indent option equal to four spaces', function() { + should_detect( + ['rule-indent'], + 'a{\n color: red\n}', + { + 'rule-indent': ' ' + } + ); + }); + + it('Should detect the rule-indent option equal to a tab', function() { + should_detect( + ['rule-indent'], + 'a{\n\tcolor: red\n}', + { + 'rule-indent': '\t' + } + ); + }); + + it('Should detect the rule-indent option equal to two spaces inside a mq', function() { + should_detect( + ['rule-indent'], + '@media all {\n .input__control {\n color: #000;\n }\n}', + { + 'rule-indent': ' ' + } + ); + }); }); From 674571dcf79dda37d34dd25855e3aea79271f7a1 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Fri, 18 Oct 2013 18:43:12 +0400 Subject: [PATCH 38/75] Added the detection for block-indent option --- lib/options/block-indent.js | 26 ++++++++++++++++++++ test/block-indent.js | 48 +++++++++++++++++++++++++++++++++++++ test/integral.js | 1 + 3 files changed, 75 insertions(+) diff --git a/lib/options/block-indent.js b/lib/options/block-indent.js index 06e2ee06..a654b216 100644 --- a/lib/options/block-indent.js +++ b/lib/options/block-indent.js @@ -69,6 +69,32 @@ module.exports = { space[1] += new Array(level + 1).join(this._value); } } + }, + + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node, level) { + var result = null; + if (nodeType === 'atrulers' || nodeType === 'block') { + if (node[node.length - 1][0] === 's' && level > 0) { + result = node[node.length - 1][1].replace(/\n/g, ''); + + if (this._prev !== undefined && this._prev[0] < level) { + result = result.replace(result.replace(this._prev[1], ''), ''); + } + if (this._prev === undefined || this._prev[0] !== level) { + this._prev = [level, result]; + } + } + } + + if (result !== null) { + return result; + } } }; diff --git a/test/block-indent.js b/test/block-indent.js index f5a08d57..b2c4acdd 100644 --- a/test/block-indent.js +++ b/test/block-indent.js @@ -41,4 +41,52 @@ describe('options/block-indent', function() { 'a { color: red \n}\n@media all {\n\t.input__control\n\t{ color: #000;\n\t}\n}' ); }); + + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Should detect the block-indent option set to four spaces', function() { + should_detect( + ['block-indent'], + ' \na { color: red \n}\n@media all {\n .input__control { color: #000;\n }\n}', + { + 'block-indent': ' ' + } + ); + }); + + it('Should detect the block-indent option set to three spaces', function() { + should_detect( + ['block-indent'], + 'a\n{ color: red \n}\n@media all {\n .input__control\n { color: #000;\n }\n}', + { + 'block-indent': ' ' + } + ); + }); + + it('Should detect the block-indent option set to a tab', function() { + should_detect( + ['block-indent'], + 'a { color: red \n}\n@media all {\n\t.input__control\n\t{ color: #000;\n\t}\n}', + { + 'block-indent': '\t' + } + ); + }); + it('Should detect the block-indent option set to an empty string', function() { + should_detect( + ['block-indent'], + 'a { color: red \n}\n@media all {\n.input__control\n{ color: #000;\n}\n}', + { + 'block-indent': '' + } + ); + }); }); diff --git a/test/integral.js b/test/integral.js index bcf65cc0..d416c31e 100644 --- a/test/integral.js +++ b/test/integral.js @@ -50,6 +50,7 @@ describe('integral test', function() { 'colon-space': ['', ' '], 'combinator-space': [' ', ' '], 'rule-indent': ' ', + 'block-indent': ' ', 'unitless-zero': true }) ); From 680f006c69334f4e9a02a409d3bd2d1caa0a431d Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Fri, 18 Oct 2013 19:07:07 +0400 Subject: [PATCH 39/75] Making sure the detected options from the result work as a template --- test/integral.js | 57 +++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/test/integral.js b/test/integral.js index d416c31e..1b784eca 100644 --- a/test/integral.js +++ b/test/integral.js @@ -3,40 +3,36 @@ var assert = require('assert'); var vow = require('vow'); var vfs = require('vow-fs'); -describe('integral test', function() { - var comb; - it('Process result must be equal to expect.css', function(done) { - comb = new Comb(); - comb.configure(require('../.csscomb.json')); - vow.all(['origin', 'expect'].map(function(type) { - var fileName = './test/integral.' + type + '.css'; - return vfs.read(fileName, 'utf8').then(function(data) { - return data; - }); - })) - .then(function(results) { +var comb; + +vow.all(['origin', 'expect'].map(function(type) { + var fileName = './test/integral.' + type + '.css'; + return vfs.read(fileName, 'utf8').then(function(data) { + return data; + }); +})) +.then(function(results) { + describe('integral test', function() { + it('Process result must be equal to expect.css', function(done) { try { + comb = new Comb(); + comb.configure(require('../.csscomb.json')); assert.equal(comb.processString(results[0]), results[1]); + done(); } catch (e) { done(e); } + }); - }); - it('Should detect everything in integral test', function(done) { comb = new Comb(); comb.detect(); - vow.all(['origin', 'expect'].map(function(type) { - var fileName = './test/integral.' + type + '.css'; - return vfs.read(fileName, 'utf8').then(function(data) { - return data; - }); - })) - .then(function(results) { + var detectedOptions = comb.processString(results[1]); + it('Should detect everything in integral test', function(done) { try { assert.equal( - JSON.stringify(comb.processString(results[1])), + JSON.stringify(detectedOptions), JSON.stringify({ 'remove-empty-rulesets': true, 'always-semicolon': true, @@ -54,11 +50,26 @@ describe('integral test', function() { 'unitless-zero': true }) ); + done(); } catch (e) { done(e); } }); - }); + var detectedConfiguration = JSON.parse(JSON.stringify(detectedOptions)); + detectedConfiguration['sort-order'] = require('../.csscomb.json')['sort-order']; + it('Result processed with detected options must be equal to expect.css', function(done) { + try { + comb = new Comb(); + comb.configure(detectedConfiguration); + assert.equal(comb.processString(results[0]), results[1]); + + done(); + } catch (e) { + done(e); + } + + }); + }); }); From d03a867908800994bdeef0d23013ce7f3418f8de Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Sun, 20 Oct 2013 20:37:26 +0400 Subject: [PATCH 40/75] Empty string should be a valid value for stick-brace option --- lib/options/stick-brace.js | 2 +- test/stick-brace.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/options/stick-brace.js b/lib/options/stick-brace.js index 76b1ff49..b6415920 100644 --- a/lib/options/stick-brace.js +++ b/lib/options/stick-brace.js @@ -11,7 +11,7 @@ module.exports = { if (value === true) this._value = ' '; if (typeof value === 'number' && value === Math.abs(Math.round(value))) this._value = new Array(value + 1).join(' '); - if (typeof value === 'string' && value.match(/^[ \t\n]+$/)) this._value = value; + if (typeof value === 'string' && value.match(/^[ \t\n]*$/)) this._value = value; if (typeof this._value === 'string') return this; }, diff --git a/test/stick-brace.js b/test/stick-brace.js index 166611ee..b510b2f7 100644 --- a/test/stick-brace.js +++ b/test/stick-brace.js @@ -44,6 +44,23 @@ describe('options/stick-brace', function() { '@media all\n{ .input__control\n{ color: #000;\n \n }\t}' ); }); + it('Empty String value should set no space before brace', function() { + comb.configure({ 'stick-brace': '' }); + assert.equal( + comb.processString( + 'a{ color: red }' + + 'a, b /* i */ { color: red; }' + + 'a \t\t \n{color:red\n \n}' + + 'a /* foo */ {color:red ;\n}' + + '@media all { .input__control { color: #000;\n \n }\t}' + ), + 'a{ color: red }' + + 'a, b /* i */{ color: red; }' + + 'a{color:red\n \n}' + + 'a /* foo */{color:red ;\n}' + + '@media all{ .input__control{ color: #000;\n \n }\t}' + ); + }); // Helper to check the detection function should_detect(options, a, b) { From 735749f0e3a826aa380fbf18ddf7da681eaa085d Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Sun, 20 Oct 2013 20:43:05 +0400 Subject: [PATCH 41/75] Added basic usage of .css file as config --- lib/cli.js | 12 ++++++++++-- lib/csscomb.js | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 0afd8fe4..dcb0e98b 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -25,8 +25,16 @@ if (!program.args.length) { var configPath = program.config || (process.cwd() + '/.csscomb.json'); if (fs.existsSync(configPath)) { - var comb = new Comb(); - var config = require(configPath); + var comb; + var config; + if (configPath.match(/\.css$/)) { + comb = new Comb(); + comb.detect(); + config = comb.processFile(configPath); + } else { + config = require(configPath); + } + comb = new Comb(); console.time('spent'); diff --git a/lib/csscomb.js b/lib/csscomb.js index fd50fb76..52c36f58 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -1,6 +1,7 @@ var gonzales = require('gonzales-pe'); var minimatch = require('minimatch'); var vow = require('vow'); +var fs = require('fs'); var vfs = require('vow-fs'); var doNothing = function() {}; @@ -173,6 +174,9 @@ Comb.prototype = { */ processFile: function(path) { var _this = this; + if (_this._detect) { + return _this.processString(fs.readFileSync(path, 'utf8'), path); + } if (this._shouldProcessFile(path)) { return vfs.read(path, 'utf8').then(function(data) { var syntax = path.split('.').pop(); From e84720c2f625f236d451e7238bd18a4ac0033cb6 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Sun, 20 Oct 2013 21:03:38 +0400 Subject: [PATCH 42/75] Added template option to .csscomb.js for css template file --- lib/cli.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/cli.js b/lib/cli.js index dcb0e98b..bb89d980 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -34,13 +34,29 @@ if (fs.existsSync(configPath)) { } else { config = require(configPath); } - comb = new Comb(); + + if (config.template) { + if (fs.existsSync(config.template)) { + comb = new Comb(); + comb.detect(); + var templateConfig = comb.processFile(config.template); + for (var attrname in templateConfig) { + if (!config[attrname]) { + config[attrname] = templateConfig[attrname]; + } + } + } else { + console.log('Template configuration file ' + config.template + ' was not found.'); + process.exit(1); + } + } console.time('spent'); config.verbose = program.verbose === true || config.verbose; config.lint = program.lint; + comb = new Comb(); comb.configure(config); vow.all(program.args.map(comb.processPath.bind(comb))) From 139c3b8b05da2bc209018883f034ec4b0c103108 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Sun, 20 Oct 2013 21:23:01 +0400 Subject: [PATCH 43/75] Added basic detect cli option (single file ATM) --- lib/cli.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/cli.js b/lib/cli.js index bb89d980..d4ee8ef8 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -14,6 +14,7 @@ program .usage('[options] ') .option('-v, --verbose', 'verbose mode') .option('-c, --config [path]', 'configuration file path') + .option('-d, --detect', 'detect mode (would return detected options)') .option('-l, --lint', 'in case some fixes needed returns an error') .parse(process.argv); @@ -24,6 +25,13 @@ if (!program.args.length) { var configPath = program.config || (process.cwd() + '/.csscomb.json'); +if (program.detect) { + var comb = new Comb(); + comb.detect(); + console.log(JSON.stringify(comb.processFile(program.args[0]), false, 4)); + process.exit(0); +} + if (fs.existsSync(configPath)) { var comb; var config; From 09e59b41ec6bb76e19bba573f719f816c547e91e Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Thu, 7 Nov 2013 13:37:02 +0400 Subject: [PATCH 44/75] Added detection of the vendor-prefix-align option --- lib/options/vendor-prefix-align.js | 83 ++++++++++++++++++++++++++++ test/integral.js | 3 +- test/vendor-prefix-align.js | 87 ++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 1 deletion(-) diff --git a/lib/options/vendor-prefix-align.js b/lib/options/vendor-prefix-align.js index 5359812f..227d5a34 100644 --- a/lib/options/vendor-prefix-align.js +++ b/lib/options/vendor-prefix-align.js @@ -169,5 +169,88 @@ module.exports = { this._walk(node, this._getValName, function(info, i) { node[i][3][1][1] = _this._updateIndent(info, dict, node[i][3][1][1]); }); + }, + + /** + * Detects the syntax at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node) { + if (nodeType !== 'block') return; + + var result = { + true: 0, + false: 0 + }; + + var maybePrefix = false; + var prevPrefixLength = false; + var prevProp; + var prevSum; + var partialResult = null; + + var getResult = function(node, sum, info, i) { + var prop = info.baseName; + + // If this is the last item in a row and we have a result, then catch it + if (prop !== prevProp && partialResult !== null) { + if (partialResult) { + result.true++; + } else { + result.false++; + } + partialResult = null; + } + + if (prop === prevProp && info.prefixLength !== prevPrefixLength) { + maybePrefix = true; + } else { + maybePrefix = false; + } + + if (maybePrefix && partialResult !== false) { + // If there is prefixed prop, check if the prefixes are aligned, + // but only if we hadn't already catched that it is false + if (sum === prevSum) { + partialResult = true; + } else { + partialResult = false; + } + } + + if (node.length === i + 3 && partialResult !== null) { + // If we're at the last property and have a result, catch it + if (partialResult) { + result.true++; + } else { + result.false++; + } + } + + prevPrefixLength = info.prefixLength; + prevProp = prop; + prevSum = sum; + }; + + // Gathering Info + this._walk(node, this._getDeclName, function(info, i) { + var sum = node[i - 1][1].replace(/^[ \t]*\n+/, '').length + info.prefixLength; + getResult(node, sum, info, i); + }); + + this._walk(node, this._getValName, function(info, i) { + var sum = node[i][3][1][1].replace(/^[ \t]*\n+/, '').length + info.prefixLength; + getResult(node, sum, info, i); + }); + + if (result.true > 0 || result.false > 0) { + if (result.true >= result.false) { + return true; + } else { + return false; + } + } } }; diff --git a/test/integral.js b/test/integral.js index 1b784eca..163bd20a 100644 --- a/test/integral.js +++ b/test/integral.js @@ -47,7 +47,8 @@ vow.all(['origin', 'expect'].map(function(type) { 'combinator-space': [' ', ' '], 'rule-indent': ' ', 'block-indent': ' ', - 'unitless-zero': true + 'unitless-zero': true, + 'vendor-prefix-align': true }) ); diff --git a/test/vendor-prefix-align.js b/test/vendor-prefix-align.js index d58be6df..20494b15 100644 --- a/test/vendor-prefix-align.js +++ b/test/vendor-prefix-align.js @@ -48,4 +48,91 @@ describe('options/vendor-prefix-align', function() { assert.equal(comb.processString(input), expected); }); + + // Helper to check the detection + function should_detect(options, a, b) { + comb.detect(options); + assert.equal( + JSON.stringify(comb.processString(a)), + JSON.stringify(b) + ); + } + + it('Shouldn not detect anything if there are no prefixed groups', function() { + should_detect( + ['vendor-prefix-align'], + 'a{ color: red }a{ -webkit-transform: translateZ(0) }', + {} + ); + }); + + it('Shouldn detect vendor-prefix-align as false in properties', function() { + should_detect( + ['vendor-prefix-align'], + fs.readFileSync('./test/vendor-prefix-align/property-align.css', 'utf8'), + { + 'vendor-prefix-align': false + } + ); + }); + + it('Shouldn detect vendor-prefix-align as true in properties', function() { + should_detect( + ['vendor-prefix-align'], + fs.readFileSync('./test/vendor-prefix-align/property-align.expected.css', 'utf8'), + { + 'vendor-prefix-align': true + } + ); + }); + + it('Shouldn detect vendor-prefix-align as false in values', function() { + should_detect( + ['vendor-prefix-align'], + fs.readFileSync('./test/vendor-prefix-align/value-align.css', 'utf8'), + { + 'vendor-prefix-align': false + } + ); + }); + + it('Shouldn detect vendor-prefix-align as true in values', function() { + should_detect( + ['vendor-prefix-align'], + fs.readFileSync('./test/vendor-prefix-align/value-align.expected.css', 'utf8'), + { + 'vendor-prefix-align': true + } + ); + }); + + it('Shouldn detect vendor-prefix-align as true, test 1', function() { + should_detect( + ['vendor-prefix-align'], + fs.readFileSync('./test/vendor-prefix-align/already-aligned.css', 'utf8'), + { + 'vendor-prefix-align': true + } + ); + }); + + it('Shouldn detect vendor-prefix-align as true, test 2', function() { + should_detect( + ['vendor-prefix-align'], + fs.readFileSync('./test/vendor-prefix-align/complex.expected.css', 'utf8'), + { + 'vendor-prefix-align': true + } + ); + }); + + it('Shouldn detect vendor-prefix-align as false', function() { + should_detect( + ['vendor-prefix-align'], + fs.readFileSync('./test/vendor-prefix-align/complex.css', 'utf8'), + { + 'vendor-prefix-align': false + } + ); + }); }); From f4fc7b14dc65aa0b7bcd744e7b4bdcb5f6feea6e Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Thu, 7 Nov 2013 14:54:41 +0400 Subject: [PATCH 45/75] Readme updated on all the detection options --- README.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3021603e..47c40356 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ csscomb -h -V, --version output the version number -v, --verbose verbose mode -c, --config [path] configuration file path + -d, --detect detect mode (would return detected options) -l, --lint in case some fixes needed returns an error ``` @@ -137,6 +138,8 @@ var combedLESS = comb.processString(less, 'less'); ## Configuration +### Through `.csscomb.json` + `csscomb` is configured using [.csscomb.json](https://github.com/csscomb/csscomb.js/blob/master/.csscomb.json) file, located in the project root. Example configuration: @@ -162,6 +165,66 @@ Example configuration: } ``` +### Through `.css`-template + +Instead of configuring all the options one by one, you can use a CSS-template file instead: CSSComb.js would detect the codestyle used in this file and would use it as a config. All the existent properties except for the `sort-order` could be configured this way. + +To provide a template just add `"template"` with the path to the template in the `.csscomb.json`: + +```json +{ + "template": "example.css" +} +``` + +CSSComb.js would detect only those things that could be detected, so if your template don't provide examples of usage for some of the options, or if you would want to override something from it, you can write them in the `.csscomb.json` along the `"template"`: + +```json +{ + "template": "example.css", + "leading-zero": false, + "vendor-prefix-align": true +} +``` + +This config would detect all the options from the `example.css`, then it would use `"leading-zero": false` instead of what it detected, and then it would use `"vendor-prefix-align": true` even if there were no prefixed properties or values inside the `example.css`. + +### Creating `.csscomb.json` from the `.css` file + +If you want to configure everything manually, but based on the codestyle from existing `.css`-file, you can at first detect all the options using `--detect` CLI option, and then add/edit any options you like. So if you have such `example.css`: + +```css +.foo +{ + width: #fff; +} +``` + +then by running + +```bash +./node_modules/.bin/csscomb -d template.css > .csscomb.json +``` + +you would generate this `.csscomb.json`: + +```json +{ + "remove-empty-rulesets": true, + "always-semicolon": true, + "color-case": "lower", + "color-shorthand": true, + "strip-spaces": true, + "eof-newline": true, + "stick-brace": "\n", + "colon-space": [ + "", + " " + ], + "rule-indent": " " +} +``` + ## Options ### exclude @@ -178,7 +241,7 @@ Available value: `{Boolean}` `true` Config mode: `{ "verbose": true }` ```bash -./bin/csscomb ./test +csscomb ./test ✓ test/integral.origin.css test/integral.expect.css @@ -190,8 +253,22 @@ Config mode: `{ "verbose": true }` CLI mode: ```bash -./bin/csscomb ./test --verbose -./bin/csscomb ./test -v +csscomb ./test --verbose +csscomb ./test -v +``` + +### template + +**Note:** see the description of the [configuring through template](#through-css-template). + +Available value: `{String}` path to the `.css` file. + +Example: `{ "template": "example.css" }` + +CLI mode — just provide path to `.css` file instead of `.csscomb.json`: +```bash +csscomb ./test --config example.css +csscomb ./test -c example.css ``` ### always-semicolon From c619dede5fda6d716aa9dcd964f93a066390a5ad Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Tue, 26 Nov 2013 12:48:47 +0400 Subject: [PATCH 46/75] Using the syntax of the given file for the detection --- lib/csscomb.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/csscomb.js b/lib/csscomb.js index 52c36f58..de77a45b 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -175,7 +175,8 @@ Comb.prototype = { processFile: function(path) { var _this = this; if (_this._detect) { - return _this.processString(fs.readFileSync(path, 'utf8'), path); + var syntax = path.split('.').pop(); + return _this.processString(fs.readFileSync(path, 'utf8'), syntax, path); } if (this._shouldProcessFile(path)) { return vfs.read(path, 'utf8').then(function(data) { From 60ffa05df734f503ff4aaf99043b640267b313ca Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Tue, 26 Nov 2013 12:49:15 +0400 Subject: [PATCH 47/75] Fixed some issues with vendor-prefix-align --- lib/options/vendor-prefix-align.js | 16 ++++++++++------ test/vendor-prefix-align.js | 8 ++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/options/vendor-prefix-align.js b/lib/options/vendor-prefix-align.js index 227d5a34..79ec80c7 100644 --- a/lib/options/vendor-prefix-align.js +++ b/lib/options/vendor-prefix-align.js @@ -86,9 +86,9 @@ module.exports = { // TODO: Check that `node[3]` is the node we need if (node[0] !== 'declaration' || !node[3] || !node[3]) return; - if (node[3][2][0] === 'ident') + if (node[3][2] && node[3][2][0] === 'ident') return node[3][2][1]; - if (node[3][2][0] === 'function') + if (node[3][2] && node[3][2][0] === 'function') return node[3][2][1][1]; }, @@ -236,13 +236,17 @@ module.exports = { // Gathering Info this._walk(node, this._getDeclName, function(info, i) { - var sum = node[i - 1][1].replace(/^[ \t]*\n+/, '').length + info.prefixLength; - getResult(node, sum, info, i); + if (node[i - 1]) { + var sum = node[i - 1][1].replace(/^[ \t]*\n+/, '').length + info.prefixLength; + getResult(node, sum, info, i); + } }); this._walk(node, this._getValName, function(info, i) { - var sum = node[i][3][1][1].replace(/^[ \t]*\n+/, '').length + info.prefixLength; - getResult(node, sum, info, i); + if (node[i][3][1]) { + var sum = node[i][3][1][1].replace(/^[ \t]*\n+/, '').length + info.prefixLength; + getResult(node, sum, info, i); + } }); if (result.true > 0 || result.false > 0) { diff --git a/test/vendor-prefix-align.js b/test/vendor-prefix-align.js index 20494b15..ba206ccb 100644 --- a/test/vendor-prefix-align.js +++ b/test/vendor-prefix-align.js @@ -135,4 +135,12 @@ describe('options/vendor-prefix-align', function() { } ); }); + + it('Should not detect anything in simple case', function() { + should_detect( + ['vendor-prefix-align'], + 'a{border:0;}', + {} + ); + }); }); From 2a8c7d75efc5f2e8ae87f08521cf4ab95bea2d9a Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Tue, 26 Nov 2013 12:49:28 +0400 Subject: [PATCH 48/75] Added more passing tests --- test/always-semicolon.js | 11 +++++++++++ test/colon-space.js | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/test/always-semicolon.js b/test/always-semicolon.js index 6573a315..5b659500 100644 --- a/test/always-semicolon.js +++ b/test/always-semicolon.js @@ -119,6 +119,17 @@ describe('options/always-semicolon', function() { ); }); + + it('Should detect semicolon for last property. Test 6', function() { + should_detect( + ['always-semicolon'], + 'a{\n border:0;\n}', + { + 'always-semicolon': true + } + ); + }); + it('Should not detect semicolon for last property if there are no properties', function() { should_detect( ['always-semicolon'], diff --git a/test/colon-space.js b/test/colon-space.js index 3678516d..6dab7eb4 100644 --- a/test/colon-space.js +++ b/test/colon-space.js @@ -215,4 +215,14 @@ describe('options/colon-space', function() { } ); }); + + it('Should detect whitespace after colon', function() { + should_detect( + ['colon-space'], + '.input\n{\n position: relative;\n\n display: inline-block;\n\n width: 100%;\n}', + { + 'colon-space': ['', ' '] + } + ); + }); }); From 74cff688dfa2f4d65e9295a6d2271e38c622a9ec Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Wed, 27 Nov 2013 19:15:58 +0400 Subject: [PATCH 49/75] Detection: Remove `shouldDetect` var --- lib/csscomb.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/csscomb.js b/lib/csscomb.js index de77a45b..de87151a 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -77,15 +77,11 @@ Comb.prototype = { this._options.forEach(function(option) { try { var handler = require('./options/' + option); - var shouldDetect = true; - if (options && options.indexOf(option) === -1) { - shouldDetect = false; - } - if (handler && shouldDetect) { - handler._name = option; - this._detected[option] = []; - this._handlers.push(handler); - } + if (!handler || options && options.indexOf(option) === -1) return; + + handler._name = option; + this._detected[option] = []; + this._handlers.push(handler); } catch (e) { console.warn('Error loading "%s" handler: %s', option, e.message); } From 85f8ca0db5c527890efbef365083ba6956a38941 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Thu, 28 Nov 2013 01:09:18 +0400 Subject: [PATCH 50/75] Add test for csscomb/csscomb#224 --- test/sort-order.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/sort-order.js b/test/sort-order.js index 21e310ad..022b5594 100644 --- a/test/sort-order.js +++ b/test/sort-order.js @@ -116,4 +116,31 @@ describe('options/sort-order', function() { assert.equal(comb.processString(input), expected); }); + + it('Should parse semicolons inside data uri correctly', function() { + + var config = { + 'sort-order': [ + ['position', 'background', 'color'] + ] + }; + + var input = 'a\n' + + '{\n' + + '\tcolor: tomato;\n' + + '\tbackground: #D2D2D2 no-repeat url(\'data:image/svg+xml;charset=US-ASCII.naninani\');\n' + + '\tposition: absolute;\n' + + '}'; + + var expected = 'a\n' + + '{\n' + + '\tposition: absolute;\n' + + '\tbackground: #D2D2D2 no-repeat url(\'data:image/svg+xml;charset=US-ASCII.naninani\');\n' + + '\tcolor: tomato;\n' + + '}'; + + comb.configure(config); + assert.equal(comb.processString(input), expected); + + }); }); From 1ac2aa1308b202f9a2b0ee5bfab711b4c141de84 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Thu, 28 Nov 2013 10:42:23 +0400 Subject: [PATCH 51/75] Proper cli examples --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 47c40356..a2d1d928 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ If you want to configure everything manually, but based on the codestyle from ex then by running ```bash -./node_modules/.bin/csscomb -d template.css > .csscomb.json +csscomb -d template.css > .csscomb.json ``` you would generate this `.csscomb.json`: @@ -253,8 +253,8 @@ csscomb ./test CLI mode: ```bash -csscomb ./test --verbose -csscomb ./test -v +csscomb --verbose ./test +csscomb -v ./test ``` ### template @@ -267,8 +267,8 @@ Example: `{ "template": "example.css" }` CLI mode — just provide path to `.css` file instead of `.csscomb.json`: ```bash -csscomb ./test --config example.css -csscomb ./test -c example.css +csscomb --config example.css ./test +csscomb -c example.css ./test ``` ### always-semicolon From a246c6bc7410f07e88fa66c348c4c239edd3a863 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Thu, 28 Nov 2013 18:08:05 +0400 Subject: [PATCH 52/75] Refactored the detection method --- lib/cli.js | 15 +++------- lib/csscomb.js | 56 ++++++++++++++++++++++++----------- test/always-semicolon.js | 3 +- test/block-indent.js | 3 +- test/colon-space.js | 3 +- test/color-case.js | 3 +- test/color-shorthand.js | 3 +- test/combinator-space.js | 3 +- test/element-case.js | 3 +- test/eof-newline.js | 3 +- test/leading-zero.js | 3 +- test/remove-empty-rulesets.js | 3 +- test/rule-indent.js | 3 +- test/stick-brace.js | 3 +- test/strip-spaces.js | 3 +- test/unitless-zero.js | 3 +- test/vendor-prefix-align.js | 3 +- 17 files changed, 57 insertions(+), 59 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index d4ee8ef8..a8102782 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -24,30 +24,24 @@ if (!program.args.length) { } var configPath = program.config || (process.cwd() + '/.csscomb.json'); +var comb = new Comb(); if (program.detect) { - var comb = new Comb(); - comb.detect(); - console.log(JSON.stringify(comb.processFile(program.args[0]), false, 4)); + console.log(JSON.stringify(comb.detectInFile(program.args[0]), false, 4)); process.exit(0); } if (fs.existsSync(configPath)) { - var comb; var config; if (configPath.match(/\.css$/)) { - comb = new Comb(); - comb.detect(); - config = comb.processFile(configPath); + config = comb.detectInFile(configPath); } else { config = require(configPath); } if (config.template) { if (fs.existsSync(config.template)) { - comb = new Comb(); - comb.detect(); - var templateConfig = comb.processFile(config.template); + var templateConfig = comb.detectInFile(config.template); for (var attrname in templateConfig) { if (!config[attrname]) { config[attrname] = templateConfig[attrname]; @@ -64,7 +58,6 @@ if (fs.existsSync(configPath)) { config.verbose = program.verbose === true || config.verbose; config.lint = program.lint; - comb = new Comb(); comb.configure(config); vow.all(program.args.map(comb.processPath.bind(comb))) diff --git a/lib/csscomb.js b/lib/csscomb.js index de87151a..d7bd4d30 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -68,9 +68,14 @@ Comb.prototype = { }, /** - * Sets up the detection environment. + * Detects the options in the given string + * + * @param {String} text Stylesheet + * @param {Array} options List of options to detect + * @returns {Object} */ - detect: function(options) { + detectInString: function(text, options) { + var result; this._detect = true; this._detected = {}; this._handlers = []; @@ -87,8 +92,21 @@ Comb.prototype = { } }, this); - this.processed = 0; - this.changed = 0; + result = this.processString(text); + this._detect = false; + return result; + }, + + /** + * Detects the options in the given file + * + * @param {String} path Path to the stylesheet + * @param {Array} options List of options to detect + * @returns {Object} + */ + detectInFile: function(path, options) { + var stylesheet = fs.readFileSync(path, 'utf8'); + return this.detectInString(stylesheet, options); }, /** @@ -98,7 +116,6 @@ Comb.prototype = { * @returns {Array} */ processTree: function(tree) { - // We walk across complete tree for each handler, // because we need strictly maintain order in which handlers work, // despite fact that handlers work on different level of the tree. @@ -170,10 +187,6 @@ Comb.prototype = { */ processFile: function(path) { var _this = this; - if (_this._detect) { - var syntax = path.split('.').pop(); - return _this.processString(fs.readFileSync(path, 'utf8'), syntax, path); - } if (this._shouldProcessFile(path)) { return vfs.read(path, 'utf8').then(function(data) { var syntax = path.split('.').pop(); @@ -292,31 +305,38 @@ Comb.prototype = { _getDetectedOptions: function(detected) { var options = {}; Object.keys(detected).forEach(function(option) { + // List of all the detected variants from the stylesheet for the given option: var values = detected[option]; var i; if (values.length) { if (values.length === 1) { options[option] = values[0]; } else { + // If there are more than one value for the option, find the most popular one; + // `variants` would be populated with the popularity for different values. var variants = {}; - var best_guess = null; + var bestGuess = null; var maximum = 0; for (i = values.length; i--;) { - if (variants[values[i]]) { - variants[values[i]]++; + var currentValue = values[i]; + // Count the current value: + if (variants[currentValue]) { + variants[currentValue]++; } else { - variants[values[i]] = 1; + variants[currentValue] = 1; } - if (variants[values[i]] >= maximum) { - maximum = variants[values[i]]; - best_guess = values[i]; + // If the current variant is the most popular one, treat it as the best guess: + if (variants[currentValue] >= maximum) { + maximum = variants[currentValue]; + bestGuess = currentValue; } } - if (best_guess !== null) { - options[option] = best_guess; + if (bestGuess !== null) { + options[option] = bestGuess; } } } else { + // If there are no values for the option, check if there is a default one: for (i = this._handlers.length; i--;) { if (this._handlers[i]._name === option && this._handlers[i]._detectDefault !== undefined) { options[option] = this._handlers[i]._detectDefault; diff --git a/test/always-semicolon.js b/test/always-semicolon.js index 5b659500..06079d90 100644 --- a/test/always-semicolon.js +++ b/test/always-semicolon.js @@ -60,9 +60,8 @@ describe('options/always-semicolon', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/block-indent.js b/test/block-indent.js index b2c4acdd..952986e3 100644 --- a/test/block-indent.js +++ b/test/block-indent.js @@ -44,9 +44,8 @@ describe('options/block-indent', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/colon-space.js b/test/colon-space.js index 6dab7eb4..36c421e1 100644 --- a/test/colon-space.js +++ b/test/colon-space.js @@ -149,9 +149,8 @@ describe('options/colon-space', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/color-case.js b/test/color-case.js index f4a823d0..54de8ed7 100644 --- a/test/color-case.js +++ b/test/color-case.js @@ -52,9 +52,8 @@ describe('options/color-case', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/color-shorthand.js b/test/color-shorthand.js index 74341ac6..395e6e2b 100644 --- a/test/color-shorthand.js +++ b/test/color-shorthand.js @@ -41,9 +41,8 @@ describe('options/color-shorthand', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/combinator-space.js b/test/combinator-space.js index 452a50b5..2c991780 100644 --- a/test/combinator-space.js +++ b/test/combinator-space.js @@ -157,9 +157,8 @@ describe('options/combinator-space', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/element-case.js b/test/element-case.js index a4db09ff..3f3958d5 100644 --- a/test/element-case.js +++ b/test/element-case.js @@ -50,9 +50,8 @@ describe('options/element-case', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/eof-newline.js b/test/eof-newline.js index 192c430c..dff1a528 100644 --- a/test/eof-newline.js +++ b/test/eof-newline.js @@ -34,9 +34,8 @@ describe('options/eof-newline', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/leading-zero.js b/test/leading-zero.js index 42987d4b..30c46c01 100644 --- a/test/leading-zero.js +++ b/test/leading-zero.js @@ -30,9 +30,8 @@ describe('options/leading-zero', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/remove-empty-rulesets.js b/test/remove-empty-rulesets.js index 33674086..e6c4fd3c 100644 --- a/test/remove-empty-rulesets.js +++ b/test/remove-empty-rulesets.js @@ -39,9 +39,8 @@ describe('options/remove-empty-rulesets', function() { describe('detecting the value', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/rule-indent.js b/test/rule-indent.js index ce55732c..164d47cb 100644 --- a/test/rule-indent.js +++ b/test/rule-indent.js @@ -59,9 +59,8 @@ describe('options/rule-indent', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/stick-brace.js b/test/stick-brace.js index b510b2f7..a9176182 100644 --- a/test/stick-brace.js +++ b/test/stick-brace.js @@ -64,9 +64,8 @@ describe('options/stick-brace', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/strip-spaces.js b/test/strip-spaces.js index c403e108..4c7b6b05 100644 --- a/test/strip-spaces.js +++ b/test/strip-spaces.js @@ -38,9 +38,8 @@ describe('options/strip-spaces', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/unitless-zero.js b/test/unitless-zero.js index 580de063..9d2b82ec 100644 --- a/test/unitless-zero.js +++ b/test/unitless-zero.js @@ -44,9 +44,8 @@ describe('options/unitless-zero', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } diff --git a/test/vendor-prefix-align.js b/test/vendor-prefix-align.js index ba206ccb..2e5b1d63 100644 --- a/test/vendor-prefix-align.js +++ b/test/vendor-prefix-align.js @@ -51,9 +51,8 @@ describe('options/vendor-prefix-align', function() { // Helper to check the detection function should_detect(options, a, b) { - comb.detect(options); assert.equal( - JSON.stringify(comb.processString(a)), + JSON.stringify(comb.detectInString(a, options)), JSON.stringify(b) ); } From 5008ebcc117708959e97c4f1e45894a0501e006d Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Thu, 28 Nov 2013 18:32:53 +0400 Subject: [PATCH 53/75] Fix the description of the detect method for options --- lib/options/always-semicolon.js | 2 +- lib/options/block-indent.js | 2 +- lib/options/colon-space.js | 2 +- lib/options/color-case.js | 2 +- lib/options/color-shorthand.js | 2 +- lib/options/combinator-space.js | 2 +- lib/options/element-case.js | 2 +- lib/options/eof-newline.js | 2 +- lib/options/leading-zero.js | 2 +- lib/options/remove-empty-rulesets.js | 2 +- lib/options/rule-indent.js | 2 +- lib/options/stick-brace.js | 2 +- lib/options/strip-spaces.js | 2 +- lib/options/unitless-zero.js | 2 +- lib/options/vendor-prefix-align.js | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/options/always-semicolon.js b/lib/options/always-semicolon.js index 84783ff7..7a0d32a3 100644 --- a/lib/options/always-semicolon.js +++ b/lib/options/always-semicolon.js @@ -72,7 +72,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/block-indent.js b/lib/options/block-indent.js index a654b216..7d07d45a 100644 --- a/lib/options/block-indent.js +++ b/lib/options/block-indent.js @@ -72,7 +72,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/colon-space.js b/lib/options/colon-space.js index 6aac3cc6..91736806 100644 --- a/lib/options/colon-space.js +++ b/lib/options/colon-space.js @@ -56,7 +56,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/color-case.js b/lib/options/color-case.js index e4b16b17..d88212b3 100644 --- a/lib/options/color-case.js +++ b/lib/options/color-case.js @@ -29,7 +29,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/color-shorthand.js b/lib/options/color-shorthand.js index db650a95..39a2dcff 100644 --- a/lib/options/color-shorthand.js +++ b/lib/options/color-shorthand.js @@ -29,7 +29,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/combinator-space.js b/lib/options/combinator-space.js index 3a0694a7..f3db3203 100644 --- a/lib/options/combinator-space.js +++ b/lib/options/combinator-space.js @@ -48,7 +48,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/element-case.js b/lib/options/element-case.js index 39c8c4ce..fcc2f91b 100644 --- a/lib/options/element-case.js +++ b/lib/options/element-case.js @@ -34,7 +34,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/eof-newline.js b/lib/options/eof-newline.js index 04784a46..9208b6a4 100644 --- a/lib/options/eof-newline.js +++ b/lib/options/eof-newline.js @@ -31,7 +31,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/leading-zero.js b/lib/options/leading-zero.js index 94f52cfc..cff306bd 100644 --- a/lib/options/leading-zero.js +++ b/lib/options/leading-zero.js @@ -30,7 +30,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/remove-empty-rulesets.js b/lib/options/remove-empty-rulesets.js index 6ff791e7..5006bc0c 100644 --- a/lib/options/remove-empty-rulesets.js +++ b/lib/options/remove-empty-rulesets.js @@ -83,7 +83,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * This option is treated as `true` by default, but any trailing space would invalidate it. * * @param {String} nodeType diff --git a/lib/options/rule-indent.js b/lib/options/rule-indent.js index 1e3cdc5d..df1f1889 100644 --- a/lib/options/rule-indent.js +++ b/lib/options/rule-indent.js @@ -46,7 +46,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/stick-brace.js b/lib/options/stick-brace.js index b6415920..b6d0ccc2 100644 --- a/lib/options/stick-brace.js +++ b/lib/options/stick-brace.js @@ -34,7 +34,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/strip-spaces.js b/lib/options/strip-spaces.js index 992eb0c5..01f3383e 100644 --- a/lib/options/strip-spaces.js +++ b/lib/options/strip-spaces.js @@ -42,7 +42,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * This option is treated as `true` by default, but any trailing space would invalidate it. * * @param {String} nodeType diff --git a/lib/options/unitless-zero.js b/lib/options/unitless-zero.js index 4b4f3035..d9308076 100644 --- a/lib/options/unitless-zero.js +++ b/lib/options/unitless-zero.js @@ -32,7 +32,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node diff --git a/lib/options/vendor-prefix-align.js b/lib/options/vendor-prefix-align.js index 79ec80c7..33291a98 100644 --- a/lib/options/vendor-prefix-align.js +++ b/lib/options/vendor-prefix-align.js @@ -172,7 +172,7 @@ module.exports = { }, /** - * Detects the syntax at the tree node. + * Detects the value of an option at the tree node. * * @param {String} nodeType * @param {node} node From bdc2d36079459e948505f1d4ea3c75110e194051 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Thu, 28 Nov 2013 18:41:54 +0400 Subject: [PATCH 54/75] Fixed improper newline remover and a small codestyle fix --- lib/options/block-indent.js | 2 +- lib/options/combinator-space.js | 8 ++++---- lib/options/element-case.js | 8 ++++---- lib/options/rule-indent.js | 2 +- test/block-indent.js | 9 +++++++++ test/rule-indent.js | 10 ++++++++++ 6 files changed, 29 insertions(+), 10 deletions(-) diff --git a/lib/options/block-indent.js b/lib/options/block-indent.js index 7d07d45a..7705ed8c 100644 --- a/lib/options/block-indent.js +++ b/lib/options/block-indent.js @@ -81,7 +81,7 @@ module.exports = { var result = null; if (nodeType === 'atrulers' || nodeType === 'block') { if (node[node.length - 1][0] === 's' && level > 0) { - result = node[node.length - 1][1].replace(/\n/g, ''); + result = node[node.length - 1][1].replace(/\s*\n/g, ''); if (this._prev !== undefined && this._prev[0] < level) { result = result.replace(result.replace(this._prev[1], ''), ''); diff --git a/lib/options/combinator-space.js b/lib/options/combinator-space.js index f3db3203..ee5acdae 100644 --- a/lib/options/combinator-space.js +++ b/lib/options/combinator-space.js @@ -56,7 +56,7 @@ module.exports = { detect: function(nodeType, node) { if (nodeType === 'selector') { var variants = {}; - var best_guess = null; + var bestGuess = null; var maximum = 0; for (var i = node.length; i--;) { var subSelector = node[i]; @@ -85,13 +85,13 @@ module.exports = { } if (variants[result] > maximum) { maximum = variants[result]; - best_guess = result; + bestGuess = result; } } } } - if (best_guess) { - return best_guess; + if (bestGuess) { + return bestGuess; } } } diff --git a/lib/options/element-case.js b/lib/options/element-case.js index fcc2f91b..832a576b 100644 --- a/lib/options/element-case.js +++ b/lib/options/element-case.js @@ -42,7 +42,7 @@ module.exports = { detect: function(nodeType, node) { if (nodeType === 'simpleselector') { var variants = {}; - var best_guess = null; + var bestGuess = null; var maximum = 0; for (var i = node.length; i--;) { var nodeItem = node[i]; @@ -62,13 +62,13 @@ module.exports = { } if (variants[result] > maximum) { maximum = variants[result]; - best_guess = result; + bestGuess = result; } } } } - if (best_guess) { - return best_guess; + if (bestGuess) { + return bestGuess; } } } diff --git a/lib/options/rule-indent.js b/lib/options/rule-indent.js index df1f1889..816f0586 100644 --- a/lib/options/rule-indent.js +++ b/lib/options/rule-indent.js @@ -56,7 +56,7 @@ module.exports = { if (nodeType === 'declaration') { if (this._prev !== undefined) { - result = this._prev.replace(/\n/g, ''); + result = this._prev.replace(/\s*\n/g, ''); if (level > 0) { result = result.substr(0, parseInt(result.length / (level + 1), 10)); } diff --git a/test/block-indent.js b/test/block-indent.js index 952986e3..2b7206ef 100644 --- a/test/block-indent.js +++ b/test/block-indent.js @@ -88,4 +88,13 @@ describe('options/block-indent', function() { } ); }); + it('Should detect the block-indent option in a complex case', function() { + should_detect( + ['block-indent'], + 'a { color: red \n}\n@media all {\n\t.input__control\n\t{ color: #000;\n\t\n\t\n\t\n\t}\n}', + { + 'block-indent': '\t' + } + ); + }); }); diff --git a/test/rule-indent.js b/test/rule-indent.js index 164d47cb..441a7d55 100644 --- a/test/rule-indent.js +++ b/test/rule-indent.js @@ -104,4 +104,14 @@ describe('options/rule-indent', function() { } ); }); + + it('Should detect the rule-indent option equal to a tab with a lot of whitespaces', function() { + should_detect( + ['rule-indent'], + 'a{\n\t\n\tcolor: red\n}', + { + 'rule-indent': '\t' + } + ); + }); }); From a01bd72b3252e6f227e08e45f1f4a2344a335a10 Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Thu, 28 Nov 2013 18:49:00 +0400 Subject: [PATCH 55/75] Fixed an issue with unitless-zero and percents --- lib/options/unitless-zero.js | 9 +++++---- test/unitless-zero.js | 10 ++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/options/unitless-zero.js b/lib/options/unitless-zero.js index d9308076..b42d3e1e 100644 --- a/lib/options/unitless-zero.js +++ b/lib/options/unitless-zero.js @@ -41,10 +41,11 @@ module.exports = { var result = null; // If we see a zero with unit and it is not degree, then we don’t have an option - if (nodeType === 'percentage' || nodeType === 'dimension') { - if (node[0][1] === '0' && node[1][1] !== 'deg') { - result = false; - } + if ( + nodeType === 'percentage' && node[0][1] === '0' || + nodeType === 'dimension' && node[0][1] === '0' && node[1][1] !== 'deg' + ) { + result = false; } // If we see a zero and previous node is not percentage or dimension, then we have an option diff --git a/test/unitless-zero.js b/test/unitless-zero.js index 9d2b82ec..8cbb3be7 100644 --- a/test/unitless-zero.js +++ b/test/unitless-zero.js @@ -105,4 +105,14 @@ describe('options/unitless-zero', function() { {} ); }); + + it('Should detect unitless zero option with percents', function() { + should_detect( + ['unitless-zero'], + 'a { padding: 0% 0 0 }', + { + 'unitless-zero': true + } + ); + }); }); From f294f5b1f29411c09dfb0d54e56f144390ea2a7e Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 2 Dec 2013 14:25:21 +0400 Subject: [PATCH 56/75] Update vendor-prefix-align.js Fix for a check if node exists in `_getValName`. It got broken in cd5306. --- lib/options/vendor-prefix-align.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/options/vendor-prefix-align.js b/lib/options/vendor-prefix-align.js index 5359812f..b7da646c 100644 --- a/lib/options/vendor-prefix-align.js +++ b/lib/options/vendor-prefix-align.js @@ -84,7 +84,7 @@ module.exports = { */ _getValName: function(node) { // TODO: Check that `node[3]` is the node we need - if (node[0] !== 'declaration' || !node[3] || !node[3]) + if (node[0] !== 'declaration' || !node[3] || !node[3][2]) return; if (node[3][2][0] === 'ident') return node[3][2][1]; From 91486e00902b847597fdbfb1d5374a7f39bbf128 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Sat, 30 Nov 2013 16:58:58 +0400 Subject: [PATCH 57/75] Block indent: Change acceptable values `block-indent` option now accepts only these kind of values: - `{Number}` of spaces; - `{String}` of whitespaces and tabs. If there is any other character in the string, the value will not be set. --- .csscomb.json | 2 +- README.md | 27 ++++++++++++++++++++++----- lib/options/block-indent.js | 11 +++++++---- test/block-indent.js | 25 ++++++++++++------------- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/.csscomb.json b/.csscomb.json index 883e1ac4..0baf78c8 100644 --- a/.csscomb.json +++ b/.csscomb.json @@ -4,7 +4,7 @@ "node_modules/**" ], "always-semicolon": true, - "block-indent": true, + "block-indent": " ", "colon-space": true, "color-case": "lower", "color-shorthand": true, diff --git a/README.md b/README.md index a2d1d928..a38b002b 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ Example configuration: "verbose": true, "always-semicolon": true, - "block-indent": true, + "block-indent": " ", "colon-space": true, "color-case": "lower", "color-shorthand": true, @@ -330,10 +330,10 @@ div { **Note**: better to use with [rule-indent](#rule-indent) -Available values: - * `{Boolean}` `true` (means 4 spaces) - * `{Number}` of spaces - * `{String}` of whitespace characters (`/[ \t]+/`) +Acceptable values: + * `{Number}` of spaces; + * `{String}` of whitespaces or tabs. If there is any other character in the + string, the value will not be set. Example: `{ "block-indent": 2 }` @@ -351,6 +351,23 @@ a { color: red } ``` +Example: `{ "block-indent": " " }` + +```css +/* before */ + a { color: red } + @media all { a { color: green } } + +/* after */ +a { color: red +} +@media all { + a { color: green + } +} +``` + + ### colon-space Available values: diff --git a/lib/options/block-indent.js b/lib/options/block-indent.js index 7705ed8c..d82c13c8 100644 --- a/lib/options/block-indent.js +++ b/lib/options/block-indent.js @@ -3,15 +3,18 @@ module.exports = { /** * Sets handler value. * - * @param {String|Number|Boolean} value Option value + * @param {String|Number} value Option value * @returns {Object|undefined} */ setValue: function(value) { delete this._value; - if (value === true) this._value = ' '; - if (typeof value === 'number' && value === Math.abs(Math.round(value))) + + if (typeof value === 'number' && value === Math.abs(Math.round(value))) { this._value = new Array(value + 1).join(' '); - if (typeof value === 'string' && value.match(/^[ \t]+$/)) this._value = value; + } else if (typeof value === 'string' && value.match(/^[ \t]+$/)) { + this._value = value; + } + if (typeof this._value === 'string') return this; }, diff --git a/test/block-indent.js b/test/block-indent.js index 2b7206ef..092250d7 100644 --- a/test/block-indent.js +++ b/test/block-indent.js @@ -3,30 +3,29 @@ var assert = require('assert'); describe('options/block-indent', function() { var comb; + beforeEach(function() { comb = new Comb(); }); + it('Invalid Number value should not change space after brace', function() { + var input = 'a { color: red }'; comb.configure({ 'block-indent': 3.5 }); - assert.equal( - comb.processString('a { color: red }'), - 'a { color: red }' - ); + assert.equal(comb.processString(input), input); }); + it('Invalid String value should not change space after brace', function() { + var input = 'a { color: red }'; comb.configure({ 'block-indent': 'foobar' }); - assert.equal( - comb.processString('a { color: red }'), - 'a { color: red }' - ); + assert.equal(comb.processString(input), input); }); - it('True Boolean value should set 4 spaces indent', function() { + + it('Boolean value should not change space after brace', function() { + var input = ' \n a { color: red } @media all {.input__control { color: #000;\n}\n}'; comb.configure({ 'block-indent': true }); - assert.equal( - comb.processString(' \n a { color: red } @media all {.input__control { color: #000;\n}\n}'), - ' \na { color: red \n}\n@media all {\n .input__control { color: #000;\n }\n}' - ); + assert.equal(comb.processString(input), input); }); + it('Valid Number value should set equal space after brace', function() { comb.configure({ 'block-indent': 3 }); assert.equal( From 7cbbe70903a19e4c26eb05d61878226f2da9e5df Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 2 Dec 2013 11:09:56 +0400 Subject: [PATCH 58/75] Colon space: Change acceptable values `colon-space` option now accepts only `{Array}` of these kinds of values: - `{Number}` of spaces; - `{String}` of whitespaces and tabs. If there is any other character in the string, the value will not be set. The first element of array is spaces before colon, and second one is spaces after colon. --- .csscomb.json | 2 +- README.md | 48 ++++---------- lib/options/colon-space.js | 48 ++++++-------- test/colon-space.js | 125 +++++-------------------------------- 4 files changed, 51 insertions(+), 172 deletions(-) diff --git a/.csscomb.json b/.csscomb.json index 0baf78c8..b6eeb4d2 100644 --- a/.csscomb.json +++ b/.csscomb.json @@ -5,7 +5,7 @@ ], "always-semicolon": true, "block-indent": " ", - "colon-space": true, + "colon-space": ["", " "], "color-case": "lower", "color-shorthand": true, "combinator-space": true, diff --git a/README.md b/README.md index a38b002b..8d2cb47a 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Example configuration: "always-semicolon": true, "block-indent": " ", - "colon-space": true, + "colon-space": ["", " "], "color-case": "lower", "color-shorthand": true, "element-case": "lower", @@ -370,57 +370,35 @@ a { color: red ### colon-space -Available values: - * `{Boolean}` `true` (means `after`) or `false` (no whitespace at all) - * `{String}`: `before`, `after`, `both` or any combination of whitespaces - and/or a colon (` `, `: `, `\t:\n\t` etc.) - * `{Array}` with two `{String}` values: for setting left and right whitespace around a colon - -Example: `{ "colon-space": true }` - -```css -/* before */ -a { color:red } - -/* after */ -a { color: red } -``` - -Example: `{ "colon-space": ":\n\t\t" }` - -```css -/* before */ -a { - color: red; -} +Acceptable value is `{Array}` with 2 elements of following types: + * `{Number}` of spaces; + * `{String}` of whitespaces or tabs. If there is any other character in the + string, the value will not be set. -/* after */ -a { - color: - red; -} -``` +The first element of the array sets spaces before colon, and the second one sets +spaces after colon. -Example: `{ "colon-space": "" }` +Example: `{ "colon-space": ["\t", "\t"] }` ```css /* before */ a { color: red } /* after */ -a { color:red } +a { color : red } ``` -Example: `{ "colon-space": ["\t", "\t"] }` +Example: `{ "colon-space": [0, 1] }` ```css /* before */ -a { color: red } +a { color:red } /* after */ -a { color : red } +a { color: red } ``` + ### color-case Available values: `{String}` `lower` or `upper` diff --git a/lib/options/colon-space.js b/lib/options/colon-space.js index 91736806..edba4262 100644 --- a/lib/options/colon-space.js +++ b/lib/options/colon-space.js @@ -3,39 +3,31 @@ module.exports = { /** * Sets handler value. * - * @param {String|Boolean} value Option value + * @param {Array} value Option value * @returns {Object|undefined} */ setValue: function(value) { - this._value = false; - if (value === true) - value = 'after'; - if (value === false) - value = ''; - if (value === 'both') - this._value = [' ', ' ']; - if (value === 'before') - this._value = [' ', '']; - if (value === 'after') - this._value = ['', ' ']; - if (value.constructor === Array && value[0].match(/^[ \t]*$/) && value[1].match(/^[ \t]*$/)) - this._value = value; - if (typeof value === 'string') { - if (value.match(/^[ \t]*$/)) { - this._value = ['', value]; - } else { - var detectSpaces = value.match(/^(([ \t]*):)?([ \t]*)$/); - if (detectSpaces) { - if (detectSpaces[1]) { - this._value = [detectSpaces[2], detectSpaces[3]]; - } else { - this._value = ['', detectSpaces[3]]; - } - } - } + delete this._value; + + if (value.constructor !== Array) return; + + if (typeof value[0] === 'number' && + value[0] === Math.abs(Math.round(value[0]))) { + value[0] = new Array(value[0] + 1).join(' '); + } else if (typeof value[0] !== 'string' || + !value[0].match(/^[ \t\n]*$/)) { + return; + } + + if (typeof value[1] === 'number' && + value[1] === Math.abs(Math.round(value[1]))) { + value[1] = new Array(value[1] + 1).join(' '); + } else if (typeof value[1] !== 'string' || + !value[1].match(/^[ \t\n]*$/)) { + return; } - if (!this._value) return; + this._value = value; return this; }, diff --git a/test/colon-space.js b/test/colon-space.js index 36c421e1..d9596ba4 100644 --- a/test/colon-space.js +++ b/test/colon-space.js @@ -3,18 +3,25 @@ var assert = require('assert'); describe('options/colon-space', function() { var comb; + beforeEach(function() { comb = new Comb(); }); - it('Invalid String should not change space around colon', function() { - comb.configure({ 'colon-space': 'foobar' }); - assert.equal( - comb.processString('a { color : red }'), - 'a { color : red }' - ); + + it('String value should not change space around colon', function() { + var input = 'a { color : red }'; + comb.configure({ 'colon-space': ' ' }); + assert.equal(comb.processString(input), input); }); - it('True Boolean value should set space after colon', function() { + + it('Boolean value should not change space around colon', function() { + var input = 'a { color : red }'; comb.configure({ 'colon-space': true }); + assert.equal(comb.processString(input), input); + }); + + it('Array of strings should set proper space around colon', function() { + comb.configure({ 'colon-space': ['', ' '] }); assert.equal( comb.processString( 'a { color: red }' + @@ -30,25 +37,9 @@ describe('options/colon-space', function() { 'a {color /* bar */: red }' ); }); - it('False Boolean value should set no space around colon', function() { - comb.configure({ 'colon-space': false }); - assert.equal( - comb.processString( - 'a { color: red }' + - 'a{color:red}' + - 'a {color : red}' + - 'a {color : /* foo */ red }' + - 'a {color /* bar */ : red }' - ), - 'a { color:red }' + - 'a{color:red}' + - 'a {color:red}' + - 'a {color:/* foo */ red }' + - 'a {color /* bar */:red }' - ); - }); - it('String `after` value should set space after colon', function() { - comb.configure({ 'colon-space': 'after' }); + + it('Array of numbers should set proper space around colon', function() { + comb.configure({ 'colon-space': [0, 1] }); assert.equal( comb.processString( 'a { color: red }' + @@ -64,88 +55,6 @@ describe('options/colon-space', function() { 'a {color /* bar */: red }' ); }); - it('String `before` value should set space before colon', function() { - comb.configure({ 'colon-space': 'before' }); - assert.equal( - comb.processString( - 'a { color: red }' + - 'a{color:red}' + - 'a {color : red}' + - 'a {color : /* foo */ red }' + - 'a {color /* bar */ : red }' - ), - 'a { color :red }' + - 'a{color :red}' + - 'a {color :red}' + - 'a {color :/* foo */ red }' + - 'a {color /* bar */ :red }' - ); - }); - it('String `both` value should set spaces around colon', function() { - comb.configure({ 'colon-space': 'both' }); - assert.equal( - comb.processString( - 'a { color: red }' + - 'a{color:red}' + - 'a {color : red}' - ), - 'a { color : red }' + - 'a{color : red}' + - 'a {color : red}' - ); - }); - it('String ` ` value should set two spaces after colon', function() { - comb.configure({ 'colon-space': ' ' }); - assert.equal( - comb.processString( - 'a { color: red }' + - 'a{color:red}' + - 'a {color : red}' - ), - 'a { color: red }' + - 'a{color: red}' + - 'a {color: red}' - ); - }); - it('String `:` value should set no space around colon', function() { - comb.configure({ 'colon-space': ':' }); - assert.equal( - comb.processString( - 'a { color: red }' + - 'a{color:red}' + - 'a {color : red}' - ), - 'a { color:red }' + - 'a{color:red}' + - 'a {color:red}' - ); - }); - it('String `` value should set no space around colon', function() { - comb.configure({ 'colon-space': '' }); - assert.equal( - comb.processString( - 'a { color: red }' + - 'a{color:red}' + - 'a {color : red}' - ), - 'a { color:red }' + - 'a{color:red}' + - 'a {color:red}' - ); - }); - it('String `\\t:\\t` value should set tabs around colon', function() { - comb.configure({ 'colon-space': '\t:\t' }); - assert.equal( - comb.processString( - 'a { color: red }' + - 'a{color:red}' + - 'a {color : red}' - ), - 'a { color\t:\tred }' + - 'a{color\t:\tred}' + - 'a {color\t:\tred}' - ); - }); // Helper to check the detection function should_detect(options, a, b) { From a261945a8444659f6904ef2093225b1da53b8046 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 2 Dec 2013 11:13:48 +0400 Subject: [PATCH 59/75] Combinator space: Change acceptable values `combinator-space` option now accepts only `{Array}` of these kinds of values: - `{Number}` of spaces; - `{String}` of whitespaces and tabs. If there is any other character in the string, the value will not be set. The first element of array is spaces before combinator, and second one is spaces after combinator. --- .csscomb.json | 2 +- README.md | 32 +++---- lib/options/combinator-space.js | 32 +++++-- test/combinator-space.js | 155 +++++++++----------------------- 4 files changed, 78 insertions(+), 143 deletions(-) diff --git a/.csscomb.json b/.csscomb.json index b6eeb4d2..10cc618b 100644 --- a/.csscomb.json +++ b/.csscomb.json @@ -8,7 +8,7 @@ "colon-space": ["", " "], "color-case": "lower", "color-shorthand": true, - "combinator-space": true, + "combinator-space": [" ", " "], "element-case": "lower", "eof-newline": true, "leading-zero": false, diff --git a/README.md b/README.md index 8d2cb47a..58179ea7 100644 --- a/README.md +++ b/README.md @@ -429,42 +429,36 @@ 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 } +Acceptable value is `{Array}` with 2 elements of following types: + * `{Number}` of spaces; + * `{String}` of whitespaces, tabs or new lines. If there is any other + character in the string, the value will not be set. -/* after */ -a > b { color: red } -``` +The first element of the array sets spaces before combinator, and the second +one sets spaces after combinator. -Example: `{ "combinator-space": "" }` +Example: `{ "combinator-space": [" ", "\n"] }` ```css /* before */ -a > b { color: red } +a>b { color: red } /* after */ -a>b { color: red } +a > +b { color: red } ``` -Example: `{ "combinator-space": [" ", "\n"] }` +Example: `{ "combinator-space": [1, 1] }` ```css /* before */ a>b { color: red } /* after */ -a > -b { color: red } +a > b { color: red } ``` + ### element-case Available values: `{String}` `lower` or `upper` diff --git a/lib/options/combinator-space.js b/lib/options/combinator-space.js index ee5acdae..78dbdcb6 100644 --- a/lib/options/combinator-space.js +++ b/lib/options/combinator-space.js @@ -3,19 +3,33 @@ module.exports = { /** * Sets handler value. * - * @param {String|Boolean|Array} value Option value - * @returns {Object} + * @param {Array} value Option value + * @returns {Object|undefined} */ 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]; + delete this._value; + + if (value.constructor !== Array) return; + + if (typeof value[0] === 'number' && + value[0] === Math.abs(Math.round(value[0]))) { + value[0] = new Array(value[0] + 1).join(' '); + } else if (typeof value[0] !== 'string' || + !value[0].match(/^[ \t\n]*$/)) { + return; } - if (value.constructor === Array) this._value = value; - if (!this._value) return; + + if (typeof value[1] === 'number' && + value[1] === Math.abs(Math.round(value[1]))) { + value[1] = new Array(value[1] + 1).join(' '); + } else if (typeof value[1] !== 'string' || + !value[1].match(/^[ \t\n]*$/)) { + return; + } + + this._value = value; return this; + }, /** diff --git a/test/combinator-space.js b/test/combinator-space.js index 2c991780..b04209b9 100644 --- a/test/combinator-space.js +++ b/test/combinator-space.js @@ -3,105 +3,31 @@ 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() { + + it('Number value should not change space around combinator', function() { + var input = 'a >b { color: red }'; + comb.configure({ 'combinator-space': 2 }); + assert.equal(comb.processString(input), input); + }); + + it('String value should not change space around combinator', function() { + var input = 'a >b { color: red }'; 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 }' - ); + assert.equal(comb.processString(input), input); }); - it('True Boolean value should set space around combinator to one space', function() { + + it('Boolean value should not change space around combinator', function() { + var input = 'a >b { color: red }'; 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 }' - ); + assert.equal(comb.processString(input), input); }); - 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': ' ' }); + + it('Array of strings should set proper spaces around combinator', function() { + comb.configure({ 'combinator-space': [' ', '\n'] }); assert.equal( comb.processString( 'a>b { color: red }' + @@ -115,20 +41,21 @@ describe('options/combinator-space', function() { '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 }' + '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 }' ); }); - it('Array value should set different spaces around combinator', function() { - comb.configure({ 'combinator-space': [' ', '\n'] }); + + it('Array of numbers should set proper spaces around combinator', function() { + comb.configure({ 'combinator-space': [0, 1] }); assert.equal( comb.processString( 'a>b { color: red }' + @@ -142,16 +69,16 @@ describe('options/combinator-space', function() { '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 }' + '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 }' ); }); From 1428e138f924e6ad50de796ab2c6265cbfd50f7e Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 2 Dec 2013 11:16:27 +0400 Subject: [PATCH 60/75] Rule indent: Change acceptable values `rule-indent` option now accepts only these kinds of values: - `{Number}` of spaces; - `{String}` of whitespaces and tabs. If there is any other character in the string, the value will not be set. --- .csscomb.json | 2 +- README.md | 23 ++++++++++++++++++----- lib/options/rule-indent.js | 11 +++++++---- test/rule-indent.js | 27 +++++++++++---------------- 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/.csscomb.json b/.csscomb.json index 10cc618b..eb2380f9 100644 --- a/.csscomb.json +++ b/.csscomb.json @@ -13,7 +13,7 @@ "eof-newline": true, "leading-zero": false, "remove-empty-rulesets": true, - "rule-indent": true, + "rule-indent": " ", "stick-brace": "\n", "strip-spaces": true, "unitless-zero": true, diff --git a/README.md b/README.md index 58179ea7..b5bd7252 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ Example configuration: "eof-newline": true, "leading-zero": false, "remove-empty-rulesets": true, - "rule-indent": true, + "rule-indent": " ", "stick-brace": true, "strip-spaces": true, "unitless-zero": true, @@ -511,10 +511,10 @@ Example: `{ "remove-empty-rulesets": true }` - remove rulesets that have no decl **Note**: better to use with [block-indent](#block-indent) -Available values: - * `{Boolean}` `true` (means 4 spaces) - * `{Number}` of spaces - * `{String}` of whitespace characters (`/[ \t]+/`) +Acceptable values: + * `{Number}` of spaces; + * `{String}` of whitespaces or tabs. If there is any other character in the + string, the value will not be set. Example: `{ "rule-indent": 2 }` @@ -528,6 +528,19 @@ a { margin:0 } ``` +Example: `{ "rule-indent": " " }` + +```css +/* before */ +a { color:red; margin:0 } + +/* after */ +a { + color:red; + margin:0 } +``` + + ### sort-order **Note**: you can use an example of [.csscomb.json](https://github.com/csscomb/csscomb.js/blob/master/.csscomb.json) to set your own sort order diff --git a/lib/options/rule-indent.js b/lib/options/rule-indent.js index 816f0586..18cd8edb 100644 --- a/lib/options/rule-indent.js +++ b/lib/options/rule-indent.js @@ -3,15 +3,18 @@ module.exports = { /** * Sets handler value. * - * @param {String|Number|Boolean} value Option value + * @param {String|Number} value Option value * @returns {Object|undefined} */ setValue: function(value) { delete this._value; - if (value === true) this._value = ' '; - if (typeof value === 'number' && value === Math.abs(Math.round(value))) + + if (typeof value === 'number' && value === Math.abs(Math.round(value))) { this._value = new Array(value + 1).join(' '); - if (typeof value === 'string' && value.match(/^[ \t]+$/)) this._value = value; + } else if (typeof value === 'string' && value.match(/^[ \t]+$/)) { + this._value = value; + } + if (typeof this._value === 'string') return this; }, diff --git a/test/rule-indent.js b/test/rule-indent.js index 441a7d55..e490ff17 100644 --- a/test/rule-indent.js +++ b/test/rule-indent.js @@ -3,30 +3,23 @@ var assert = require('assert'); describe('options/rule-indent', function() { var comb; + beforeEach(function() { comb = new Comb(); }); + it('Invalid Number value should not change rule indent', function() { + var input = 'a {\n color: red }'; comb.configure({ 'rule-indent': 3.5 }); - assert.equal( - comb.processString('a {\n color: red }'), - 'a {\n color: red }' - ); + assert.equal(comb.processString(input), input); }); + it('Invalid String value should not change rule indent', function() { + var input = 'a {\n color: red }'; comb.configure({ 'rule-indent': 'foobar' }); - assert.equal( - comb.processString('a {\n color: red }'), - 'a {\n color: red }' - ); - }); - it('True Boolean value should set 4 spaces indent', function() { - comb.configure({ 'rule-indent': true }); - assert.equal( - comb.processString('a {\n color: red }'), - 'a {\n color: red }' - ); + assert.equal(comb.processString(input), input); }); + it('Valid Number value should set equal space indent', function() { comb.configure({ 'rule-indent': 3 }); assert.equal( @@ -34,6 +27,7 @@ describe('options/rule-indent', function() { 'a {\n color: red }' ); }); + it('Valid String value should set equal indent', function() { comb.configure({ 'rule-indent': '\t' }); assert.equal( @@ -49,8 +43,9 @@ describe('options/rule-indent', function() { 'a { /* foo */\n\tcolor:red; /* bar */\n\n\tbackground: #fff\n}\n' ); }); + it('Valid value should ignore empty blocks', function() { - comb.configure({ 'rule-indent': true }); + comb.configure({ 'rule-indent': ' ' }); assert.equal( comb.processString('a {}'), 'a {}' From 2b6ea64dbd408f9519f971b6cdbf874c6882070e Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 2 Dec 2013 11:17:32 +0400 Subject: [PATCH 61/75] Stick brace: Change acceptable values `stick-brace` option now accepts only these kinds of values: - `{Number}` of spaces; - `{String}` of whitespaces and tabs. If there is any other character in the string, the value will not be set. --- README.md | 21 ++++++++++++++++----- lib/options/stick-brace.js | 11 +++++++---- test/stick-brace.js | 27 +++++++++++++++++---------- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index b5bd7252..a3d54add 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ Example configuration: "leading-zero": false, "remove-empty-rulesets": true, "rule-indent": " ", - "stick-brace": true, + "stick-brace": "\n", "strip-spaces": true, "unitless-zero": true, "vendor-prefix-align": true @@ -617,10 +617,10 @@ p { ### stick-brace -Available values: - * `{Boolean}` `true` (means 1 space) - * `{Number}` of spaces - * `{String}` of whitespace characters (`/[ \t\n]+/`) +Acceptable values: + * `{Number}` of spaces; + * `{String}` of whitespaces, tabs or newlines. If there is any other + character in the string, the value will not be set. Example: `{ "stick-brace": "\n" }` @@ -633,6 +633,17 @@ a { color:red } ``` +Example: `{ "stick-brace": 1 }` + +```css +/* before */ +a{ color:red } + +/* after */ +a { color:red } +``` + + ### strip-spaces Available value: `{Boolean}` `true` diff --git a/lib/options/stick-brace.js b/lib/options/stick-brace.js index b6d0ccc2..188ee152 100644 --- a/lib/options/stick-brace.js +++ b/lib/options/stick-brace.js @@ -3,15 +3,18 @@ module.exports = { /** * Sets handler value. * - * @param {String|Number|Boolean} value Option value + * @param {String|Number} value Option value * @returns {Object|undefined} */ setValue: function(value) { delete this._value; - if (value === true) this._value = ' '; - if (typeof value === 'number' && value === Math.abs(Math.round(value))) + + if (typeof value === 'number' && value === Math.abs(Math.round(value))) { this._value = new Array(value + 1).join(' '); - if (typeof value === 'string' && value.match(/^[ \t\n]*$/)) this._value = value; + } else if (typeof value === 'string' && value.match(/^[ \t\n]*$/)) { + this._value = value; + } + if (typeof this._value === 'string') return this; }, diff --git a/test/stick-brace.js b/test/stick-brace.js index a9176182..03c31773 100644 --- a/test/stick-brace.js +++ b/test/stick-brace.js @@ -3,23 +3,29 @@ var assert = require('assert'); describe('options/stick-brace', function() { var comb; + beforeEach(function() { comb = new Comb(); }); + + it('Boolean value should not change space before brace', function() { + var input = 'a { color: red }'; + comb.configure({ 'stick-brace': 'foobar' }); + assert.equal(comb.processString(input), input); + }); + it('Invalid String should not change space before brace', function() { + var input = 'a { color: red }'; comb.configure({ 'stick-brace': 'foobar' }); - assert.equal( - comb.processString('a { color: red }'), - 'a { color: red }' - ); + assert.equal(comb.processString(input), input); }); - it('True Boolean value should set 1 space before brace', function() { - comb.configure({ 'stick-brace': true }); - assert.equal( - comb.processString('a{color:red }'), - 'a {color:red }' - ); + + it('Invalid Number should not change space before brace', function() { + var input = 'a { color: red }'; + comb.configure({ 'stick-brace': 3.5 }); + assert.equal(comb.processString(input), input); }); + it('Valid Number value should set equal space before brace', function() { comb.configure({ 'stick-brace': 0 }); assert.equal( @@ -27,6 +33,7 @@ describe('options/stick-brace', function() { 'a{color:red }' ); }); + it('Valid String value should set equal space before brace', function() { comb.configure({ 'stick-brace': '\n' }); assert.equal( From f5500a55658543637c73665713de695b378ef523 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Thu, 28 Nov 2013 01:19:51 +0400 Subject: [PATCH 62/75] Add test for csscomb/csscomb#216 --- test/sort-order.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/sort-order.js b/test/sort-order.js index 022b5594..120182bc 100644 --- a/test/sort-order.js +++ b/test/sort-order.js @@ -143,4 +143,33 @@ describe('options/sort-order', function() { assert.equal(comb.processString(input), expected); }); + + it('Should not add more than 1 line between groups', function() { + + var config = { + 'sort-order': [ + ['top'], ['color'] + ] + }; + + var input = 'a\n' + + '{\n' + + '\tcolor: tomato;\n' + + '\ttop: 0;\n' + + '}'; + + var expected = 'a\n' + + '{\n' + + '\ttop: 0;\n' + + '\n' + + '\tcolor: tomato;\n' + + '}'; + + comb.configure(config); + for (var i = 6; i--;) { + input = comb.processString(input); + } + assert.equal(input, expected); + + }); }); From 87a80c6f1c1a926cbf954e8d952cb955943325ea Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 11 Nov 2013 22:44:52 +0400 Subject: [PATCH 63/75] Move config file to `config` directory --- .csscomb.json => config/csscomb.json | 0 lib/cli.js | 2 +- test/integral.js | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename .csscomb.json => config/csscomb.json (100%) diff --git a/.csscomb.json b/config/csscomb.json similarity index 100% rename from .csscomb.json rename to config/csscomb.json diff --git a/lib/cli.js b/lib/cli.js index a8102782..e298c014 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -23,7 +23,7 @@ if (!program.args.length) { program.help(); } -var configPath = program.config || (process.cwd() + '/.csscomb.json'); +var configPath = program.config || (process.cwd() + '/config/csscomb.json'); var comb = new Comb(); if (program.detect) { diff --git a/test/integral.js b/test/integral.js index 163bd20a..37301801 100644 --- a/test/integral.js +++ b/test/integral.js @@ -16,7 +16,7 @@ vow.all(['origin', 'expect'].map(function(type) { it('Process result must be equal to expect.css', function(done) { try { comb = new Comb(); - comb.configure(require('../.csscomb.json')); + comb.configure(require('../config/csscomb.json')); assert.equal(comb.processString(results[0]), results[1]); done(); From 539c7f87c00c9170f0bf2a38a4b0d732b961b04c Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 11 Nov 2013 22:46:01 +0400 Subject: [PATCH 64/75] Add config files from csscomb.php (close #91) Add config files with sort order from previous (php) version of csscomb. `default` is renamed to `zen`. All other options (spaces, semicolons, etc.) are omitted. --- config/yandex.json | 315 +++++++++++++++++++++++++++++++++++++ config/zen.json | 379 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 694 insertions(+) create mode 100644 config/yandex.json create mode 100644 config/zen.json diff --git a/config/yandex.json b/config/yandex.json new file mode 100644 index 00000000..c6220544 --- /dev/null +++ b/config/yandex.json @@ -0,0 +1,315 @@ +{ + "sort-order": [ + [ + "position", + "z-index", + "top", + "right", + "bottom", + "left" + ], + [ + "display", + "visibility", + "float", + "clear", + "overflow", + "overflow-x", + "overflow-y", + "-ms-overflow-x", + "-ms-overflow-y", + "-webkit-overflow-scrolling", + "clip", + "zoom", + "flex-direction", + "flex-order", + "flex-pack", + "flex-align" + ], + [ + "-webkit-box-sizing", + "-moz-box-sizing", + "box-sizing", + "width", + "min-width", + "max-width", + "height", + "min-height", + "max-height", + "margin", + "margin-top", + "margin-right", + "margin-bottom", + "margin-left", + "padding", + "padding-top", + "padding-right", + "padding-bottom", + "padding-left" + ], + [ + "table-layout", + "empty-cells", + "caption-side", + "border-spacing", + "border-collapse", + "list-style", + "list-style-position", + "list-style-type", + "list-style-image" + ], + [ + "content", + "quotes", + "counter-reset", + "counter-increment", + "resize", + "cursor", + "nav-index", + "nav-up", + "nav-right", + "nav-down", + "nav-left", + "-webkit-transition", + "-moz-transition", + "-ms-transition", + "-o-transition", + "transition", + "-webkit-transition-delay", + "-moz-transition-delay", + "-ms-transition-delay", + "-o-transition-delay", + "transition-delay", + "-webkit-transition-timing-function", + "-moz-transition-timing-function", + "-ms-transition-timing-function", + "-o-transition-timing-function", + "transition-timing-function", + "-webkit-transition-duration", + "-moz-transition-duration", + "-ms-transition-duration", + "-o-transition-duration", + "transition-duration", + "-webkit-transition-property", + "-moz-transition-property", + "-ms-transition-property", + "-o-transition-property", + "transition-property", + "-webkit-transform", + "-moz-transform", + "-ms-transform", + "-o-transform", + "transform", + "-webkit-transform-origin", + "-moz-transform-origin", + "-ms-transform-origin", + "-o-transform-origin", + "transform-origin", + "-webkit-animation", + "-moz-animation", + "-ms-animation", + "-o-animation", + "animation", + "-webkit-animation-name", + "-moz-animation-name", + "-ms-animation-name", + "-o-animation-name", + "animation-name", + "-webkit-animation-duration", + "-moz-animation-duration", + "-ms-animation-duration", + "-o-animation-duration", + "animation-duration", + "-webkit-animation-play-state", + "-moz-animation-play-state", + "-ms-animation-play-state", + "-o-animation-play-state", + "animation-play-state", + "-webkit-animation-timing-function", + "-moz-animation-timing-function", + "-ms-animation-timing-function", + "-o-animation-timing-function", + "animation-timing-function", + "-webkit-animation-delay", + "-moz-animation-delay", + "-ms-animation-delay", + "-o-animation-delay", + "animation-delay", + "-webkit-animation-iteration-count", + "-moz-animation-iteration-count", + "-ms-animation-iteration-count", + "-o-animation-iteration-count", + "animation-iteration-count", + "-webkit-animation-iteration-count", + "-moz-animation-iteration-count", + "-ms-animation-iteration-count", + "-o-animation-iteration-count", + "animation-iteration-count", + "-webkit-animation-direction", + "-moz-animation-direction", + "-ms-animation-direction", + "-o-animation-direction", + "animation-direction", + "text-align", + "-webkit-text-align-last", + "-moz-text-align-last", + "-ms-text-align-last", + "text-align-last", + "vertical-align", + "white-space", + "text-decoration", + "text-emphasis", + "text-emphasis-color", + "text-emphasis-style", + "text-emphasis-position", + "text-indent", + "-ms-text-justify", + "text-justify", + "text-transform", + "letter-spacing", + "word-spacing", + "-ms-writing-mode", + "text-outline", + "text-transform", + "text-wrap", + "text-overflow", + "-ms-text-overflow", + "text-overflow-ellipsis", + "text-overflow-mode", + "-ms-word-wrap", + "word-wrap", + "word-break", + "-ms-word-break", + "-moz-tab-size", + "-o-tab-size", + "tab-size", + "-webkit-hyphens", + "-moz-hyphens", + "hyphens", + "pointer-events" + ], + [ + "opacity", + "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity", + "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", + "-ms-interpolation-mode", + "color", + "border", + "border-collapse", + "border-width", + "border-style", + "border-color", + "border-top", + "border-top-width", + "border-top-style", + "border-top-color", + "border-right", + "border-right-width", + "border-right-style", + "border-right-color", + "border-bottom", + "border-bottom-width", + "border-bottom-style", + "border-bottom-color", + "border-left", + "border-left-width", + "border-left-style", + "border-left-color", + "-webkit-border-radius", + "-moz-border-radius", + "border-radius", + "-webkit-border-top-left-radius", + "-moz-border-radius-topleft", + "border-top-left-radius", + "-webkit-border-top-right-radius", + "-moz-border-radius-topright", + "border-top-right-radius", + "-webkit-border-bottom-right-radius", + "-moz-border-radius-bottomright", + "border-bottom-right-radius", + "-webkit-border-bottom-left-radius", + "-moz-border-radius-bottomleft", + "border-bottom-left-radius", + "-webkit-border-image", + "-moz-border-image", + "-o-border-image", + "border-image", + "-webkit-border-image-source", + "-moz-border-image-source", + "-o-border-image-source", + "border-image-source", + "-webkit-border-image-slice", + "-moz-border-image-slice", + "-o-border-image-slice", + "border-image-slice", + "-webkit-border-image-width", + "-moz-border-image-width", + "-o-border-image-width", + "border-image-width", + "-webkit-border-image-outset", + "-moz-border-image-outset", + "-o-border-image-outset", + "border-image-outset", + "-webkit-border-image-repeat", + "-moz-border-image-repeat", + "-o-border-image-repeat", + "border-image-repeat", + "outline", + "outline-width", + "outline-style", + "outline-color", + "outline-offset", + "background", + "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader", + "background-color", + "background-image", + "background-repeat", + "background-attachment", + "background-position", + "background-position-x", + "-ms-background-position-x", + "background-position-y", + "-ms-background-position-y", + "-webkit-background-clip", + "-moz-background-clip", + "background-clip", + "background-origin", + "-webkit-background-size", + "-moz-background-size", + "-o-background-size", + "background-size", + "box-decoration-break", + "-webkit-box-shadow", + "-moz-box-shadow", + "box-shadow", + "-webkit-box-shadow", + "-moz-box-shadow", + "box-shadow", + "-webkit-box-shadow", + "-moz-box-shadow", + "box-shadow", + "-webkit-box-shadow", + "-moz-box-shadow", + "box-shadow", + "filter:progid:DXImageTransform.Microsoft.gradient", + "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", + "text-shadow" + ], + [ + "font", + "font-family", + "font-size", + "font-weight", + "font-style", + "font-variant", + "font-size-adjust", + "font-stretch", + "font-effect", + "font-emphasize", + "font-emphasize-position", + "font-emphasize-style", + "font-smooth", + "line-height" + ] + ] +} diff --git a/config/zen.json b/config/zen.json new file mode 100644 index 00000000..9a234aa4 --- /dev/null +++ b/config/zen.json @@ -0,0 +1,379 @@ +{ + "sort-order": [ [ + "position", + "top", + "right", + "bottom", + "left", + "z-index", + "display", + "visibility", + "-webkit-flex-direction", + "-moz-flex-direction", + "-ms-flex-direction", + "-o-flex-direction", + "flex-direction", + "-webkit-flex-order", + "-moz-flex-order", + "-ms-flex-order", + "-o-flex-order", + "flex-order", + "-webkit-flex-pack", + "-moz-flex-pack", + "-ms-flex-pack", + "-o-flex-pack", + "flex-pack", + "float", + "clear", + "-webkit-flex-align", + "-moz-flex-align", + "-ms-flex-align", + "-o-flex-align", + "flex-align", + "overflow", + "-ms-overflow-x", + "-ms-overflow-y", + "overflow-x", + "overflow-y", + "-webkit-overflow-scrolling", + "clip", + "-webkit-box-sizing", + "-moz-box-sizing", + "box-sizing", + "margin", + "margin-top", + "margin-right", + "margin-bottom", + "margin-left", + "padding", + "padding-top", + "padding-right", + "padding-bottom", + "padding-left", + "min-width", + "min-height", + "max-width", + "max-height", + "width", + "height", + "outline", + "outline-width", + "outline-style", + "outline-color", + "outline-offset", + "border", + "border-spacing", + "border-collapse", + "border-width", + "border-style", + "border-color", + "border-top", + "border-top-width", + "border-top-style", + "border-top-color", + "border-right", + "border-right-width", + "border-right-style", + "border-right-color", + "border-bottom", + "border-bottom-width", + "border-bottom-style", + "border-bottom-color", + "border-left", + "border-left-width", + "border-left-style", + "border-left-color", + "-webkit-border-radius", + "-moz-border-radius", + "border-radius", + "-webkit-border-top-left-radius", + "-moz-border-radius-topleft", + "border-top-left-radius", + "-webkit-border-top-right-radius", + "-moz-border-radius-topright", + "border-top-right-radius", + "-webkit-border-bottom-right-radius", + "-moz-border-radius-bottomright", + "border-bottom-right-radius", + "-webkit-border-bottom-left-radius", + "-moz-border-radius-bottomleft", + "border-bottom-left-radius", + "-webkit-border-image", + "-moz-border-image", + "-o-border-image", + "border-image", + "-webkit-border-image-source", + "-moz-border-image-source", + "-o-border-image-source", + "border-image-source", + "-webkit-border-image-slice", + "-moz-border-image-slice", + "-o-border-image-slice", + "border-image-slice", + "-webkit-border-image-width", + "-moz-border-image-width", + "-o-border-image-width", + "border-image-width", + "-webkit-border-image-outset", + "-moz-border-image-outset", + "-o-border-image-outset", + "border-image-outset", + "-webkit-border-image-repeat", + "-moz-border-image-repeat", + "-o-border-image-repeat", + "border-image-repeat", + "-webkit-border-top-image", + "-moz-border-top-image", + "-o-border-top-image", + "border-top-image", + "-webkit-border-right-image", + "-moz-border-right-image", + "-o-border-right-image", + "border-right-image", + "-webkit-border-bottom-image", + "-moz-border-bottom-image", + "-o-border-bottom-image", + "border-bottom-image", + "-webkit-border-left-image", + "-moz-border-left-image", + "-o-border-left-image", + "border-left-image", + "-webkit-border-corner-image", + "-moz-border-corner-image", + "-o-border-corner-image", + "border-corner-image", + "-webkit-border-top-left-image", + "-moz-border-top-left-image", + "-o-border-top-left-image", + "border-top-left-image", + "-webkit-border-top-right-image", + "-moz-border-top-right-image", + "-o-border-top-right-image", + "border-top-right-image", + "-webkit-border-bottom-right-image", + "-moz-border-bottom-right-image", + "-o-border-bottom-right-image", + "border-bottom-right-image", + "-webkit-border-bottom-left-image", + "-moz-border-bottom-left-image", + "-o-border-bottom-left-image", + "border-bottom-left-image", + "background", + "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader", + "background-color", + "background-image", + "background-attachment", + "background-position", + "-ms-background-position-x", + "-ms-background-position-y", + "background-position-x", + "background-position-y", + "-webkit-background-clip", + "-moz-background-clip", + "background-clip", + "background-origin", + "-webkit-background-size", + "-moz-background-size", + "-o-background-size", + "background-size", + "background-repeat", + "box-decoration-break", + "-webkit-box-shadow", + "-moz-box-shadow", + "box-shadow", + "color", + "table-layout", + "caption-side", + "empty-cells", + "list-style", + "list-style-position", + "list-style-type", + "list-style-image", + "quotes", + "content", + "counter-increment", + "counter-reset", + "-ms-writing-mode", + "vertical-align", + "text-align", + "-webkit-text-align-last", + "-moz-text-align-last", + "-ms-text-align-last", + "text-align-last", + "text-decoration", + "text-emphasis", + "text-emphasis-position", + "text-emphasis-style", + "text-emphasis-color", + "text-indent", + "-ms-text-justify", + "text-justify", + "text-outline", + "text-transform", + "text-wrap", + "-ms-text-overflow", + "text-overflow", + "text-overflow-ellipsis", + "text-overflow-mode", + "text-shadow", + "white-space", + "word-spacing", + "-ms-word-wrap", + "word-wrap", + "-ms-word-break", + "word-break", + "-moz-tab-size", + "-o-tab-size", + "tab-size", + "-webkit-hyphens", + "-moz-hyphens", + "hyphens", + "letter-spacing", + "font", + "font-weight", + "font-style", + "font-variant", + "font-size-adjust", + "font-stretch", + "font-size", + "font-family", + "src", + "line-height", + "opacity", + "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", + "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity", + "-ms-interpolation-mode", + "-webkit-filter", + "-ms-filter", + "filter", + "resize", + "cursor", + "nav-index", + "nav-up", + "nav-right", + "nav-down", + "nav-left", + "-webkit-transition", + "-moz-transition", + "-ms-transition", + "-o-transition", + "transition", + "-webkit-transition-delay", + "-moz-transition-delay", + "-ms-transition-delay", + "-o-transition-delay", + "transition-delay", + "-webkit-transition-timing-function", + "-moz-transition-timing-function", + "-ms-transition-timing-function", + "-o-transition-timing-function", + "transition-timing-function", + "-webkit-transition-duration", + "-moz-transition-duration", + "-ms-transition-duration", + "-o-transition-duration", + "transition-duration", + "-webkit-transition-property", + "-moz-transition-property", + "-ms-transition-property", + "-o-transition-property", + "transition-property", + "-webkit-transform", + "-moz-transform", + "-ms-transform", + "-o-transform", + "transform", + "-webkit-transform-origin", + "-moz-transform-origin", + "-ms-transform-origin", + "-o-transform-origin", + "transform-origin", + "-webkit-animation", + "-moz-animation", + "-ms-animation", + "-o-animation", + "animation", + "-webkit-animation-name", + "-moz-animation-name", + "-ms-animation-name", + "-o-animation-name", + "animation-name", + "-webkit-animation-duration", + "-moz-animation-duration", + "-ms-animation-duration", + "-o-animation-duration", + "animation-duration", + "-webkit-animation-play-state", + "-moz-animation-play-state", + "-ms-animation-play-state", + "-o-animation-play-state", + "animation-play-state", + "-webkit-animation-timing-function", + "-moz-animation-timing-function", + "-ms-animation-timing-function", + "-o-animation-timing-function", + "animation-timing-function", + "-webkit-animation-delay", + "-moz-animation-delay", + "-ms-animation-delay", + "-o-animation-delay", + "animation-delay", + "-webkit-animation-iteration-count", + "-moz-animation-iteration-count", + "-ms-animation-iteration-count", + "-o-animation-iteration-count", + "animation-iteration-count", + "-webkit-animation-direction", + "-moz-animation-direction", + "-ms-animation-direction", + "-o-animation-direction", + "animation-direction", + "pointer-events", + "unicode-bidi", + "direction", + "-webkit-columns", + "-moz-columns", + "columns", + "-webkit-column-span", + "-moz-column-span", + "column-span", + "-webkit-column-width", + "-moz-column-width", + "column-width", + "-webkit-column-count", + "-moz-column-count", + "column-count", + "-webkit-column-fill", + "-moz-column-fill", + "column-fill", + "-webkit-column-gap", + "-moz-column-gap", + "column-gap", + "-webkit-column-rule", + "-moz-column-rule", + "column-rule", + "-webkit-column-rule-width", + "-moz-column-rule-width", + "column-rule-width", + "-webkit-column-rule-style", + "-moz-column-rule-style", + "column-rule-style", + "-webkit-column-rule-color", + "-moz-column-rule-color", + "column-rule-color", + "break-before", + "break-inside", + "break-after", + "page-break-before", + "page-break-inside", + "page-break-after", + "orphans", + "widows", + "-ms-zoom", + "zoom", + "max-zoom", + "min-zoom", + "user-zoom", + "orientation" + ] ] +} From 492e7eaf9dfa1cb8160c4526bc37893cb74167a7 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Thu, 21 Nov 2013 18:06:52 +0400 Subject: [PATCH 65/75] Add `getConfig()` method --- lib/csscomb.js | 21 ++++++++++++++++++ test/get-config.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 test/get-config.js diff --git a/lib/csscomb.js b/lib/csscomb.js index d7bd4d30..aef4c5d4 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -37,6 +37,27 @@ var Comb = function() { Comb.prototype = { + /** + * Get one of configuration files from `config` directory + * @param {String} name Config's name: 'csscomb', 'zen' or 'yandex' + * @returns {Object} Configuration object + */ + getConfig: function(name) { + name = name || 'csscomb'; + + if (typeof name !== 'string') { + throw new Error('Config name should be a string'); + } + + if (['csscomb', 'zen', 'yandex'].indexOf(name) < 0) { + var message = name + ' is not a valid config name. Try one of ' + + 'the following: \'csscomb\', \'zen\' or \'yandex\'.'; + throw new Error(message); + } + + return require('../config/' + name + '.json'); + }, + /** * Loads configuration from JSON. * Activates and configures required options. diff --git a/test/get-config.js b/test/get-config.js new file mode 100644 index 00000000..5556d395 --- /dev/null +++ b/test/get-config.js @@ -0,0 +1,54 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); + +describe('csscomb methods', function() { + var comb = new Comb(); + + it('getConfig()', function() { + var config = require('../config/csscomb.json'); + + assert.equal(comb.getConfig(), config); + }); + + it('getConfig(number)', function() { + assert.throws(function() { + comb.getConfig(16); + }); + }); + + it('getConfig(boolean)', function() { + assert.throws(function() { + comb.getConfig(true); + }); + }); + + it('getConfig(empty string)', function() { + var config = require('../config/csscomb.json'); + + assert.equal(comb.getConfig(''), config); + }); + + it('getConfig(invalid string)', function() { + assert.throws(function() { + comb.getConfig('nani'); + }); + }); + + it('getConfig(csscomb)', function() { + var config = require('../config/csscomb.json'); + + assert.equal(comb.getConfig('csscomb'), config); + }); + + it('getConfig(zen)', function() { + var config = require('../config/zen.json'); + + assert.equal(comb.getConfig('zen'), config); + }); + + it('getConfig(yandex)', function() { + var config = require('../config/yandex.json'); + + assert.equal(comb.getConfig('yandex'), config); + }); +}); From 3419a911c44db318d84c4d376968ab0c30305740 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Thu, 21 Nov 2013 17:37:53 +0400 Subject: [PATCH 66/75] CLI: Search for a config file recursively Check if there is a `.csscomb.json` file in process directory. If not, go one level up and search there. Stop in HOME dir. If no user's config found, take a default one from our package: `config/csscomb.json`. --- lib/cli.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/cli.js b/lib/cli.js index e298c014..174c9dc0 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -5,6 +5,7 @@ * ./node_modules/.bin/csscomb [options] file1 [dir1 [fileN [dirN]]] */ var fs = require('fs'); +var path = require('path'); var program = require('commander'); var vow = require('vow'); var Comb = require('./csscomb'); @@ -23,7 +24,34 @@ if (!program.args.length) { program.help(); } -var configPath = program.config || (process.cwd() + '/config/csscomb.json'); +/** + * Look for a config file: recursively from current (process) directory + * up to $HOME dir + * @param {String} configPath + * @returns {String} + */ +function getConfigPath(configPath) { + var HOME = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; + // Since `process.cwd()` can be absolutely anything, build default path + // relative to current directory: + var defaultConfigPath = __dirname + '/../config/csscomb.json'; + + configPath = configPath || process.cwd() + '/.csscomb.json'; + + // If we've finally found a config, return its path: + if (fs.existsSync(configPath)) return configPath; + + // If we are in HOME dir already and yet no config file, return a default + // one from our package: + if (path.dirname(configPath) === HOME) return defaultConfigPath; + + // If there is no config in this directory, go one level up and look for + // a config there: + configPath = path.dirname(path.dirname(configPath)) + '/.csscomb.json'; + return getConfigPath(configPath); +} + +var configPath = program.config || getConfigPath(); var comb = new Comb(); if (program.detect) { From 3bd2cf3a301c255e5613f27d26cd6e4af69656ac Mon Sep 17 00:00:00 2001 From: Roman Komarov Date: Thu, 31 Oct 2013 19:53:38 +0400 Subject: [PATCH 67/75] Added `quotes` option, closes #36 --- README.md | 15 +++++ config/csscomb.json | 1 + lib/csscomb.js | 1 + lib/options/quotes.js | 55 ++++++++++++++++ test/integral.expect.css | 6 +- test/integral.js | 1 + test/integral.origin.css | 4 +- test/quotes.js | 134 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 lib/options/quotes.js create mode 100644 test/quotes.js diff --git a/README.md b/README.md index a3d54add..84404f4e 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ Example configuration: "element-case": "lower", "eof-newline": true, "leading-zero": false, + "quotes": "single", "remove-empty-rulesets": true, "rule-indent": " ", "stick-brace": "\n", @@ -499,6 +500,20 @@ p { padding: 0.5em } p { padding: .5em } ``` +### quotes + +Available values: `{String}` `single` or `double` + +Example: `{ "quotes": "single" }` + +```css +/* before */ +p[href^="https://"]:before { content: "secure" } + +/* after */ +p[href^='https://']:before { content: 'secure' } +``` + ### remove-empty-rulesets Available values: `{Boolean}` `true` diff --git a/config/csscomb.json b/config/csscomb.json index eb2380f9..181aae9a 100644 --- a/config/csscomb.json +++ b/config/csscomb.json @@ -12,6 +12,7 @@ "element-case": "lower", "eof-newline": true, "leading-zero": false, + "quotes": "single", "remove-empty-rulesets": true, "rule-indent": " ", "stick-brace": "\n", diff --git a/lib/csscomb.js b/lib/csscomb.js index d7bd4d30..8e7c4832 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -20,6 +20,7 @@ var Comb = function() { 'color-shorthand', 'element-case', 'leading-zero', + 'quotes', 'strip-spaces', 'eof-newline', 'stick-brace', diff --git a/lib/options/quotes.js b/lib/options/quotes.js new file mode 100644 index 00000000..c01e6291 --- /dev/null +++ b/lib/options/quotes.js @@ -0,0 +1,55 @@ +module.exports = { + + /** + * Sets handler value. + * + * @param {String|Boolean} value Option value + * @returns {Object|undefined} + */ + setValue: function(value) { + if (value === 'single' || value === 'double' ) { + this._value = value; + } + + if (!this._value) return; + return this; + }, + + /** + * Processes tree node. + * @param {String} nodeType + * @param {node} node + */ + process: function(nodeType, node) { + if (nodeType === 'string') { + if (node[0][0] === '"' && this._value === 'single') { + node[0] = node[0] + .replace(/\\"/g, '"') // unescape all escaped double quotes + .replace(/([^\\])'/g, '$1\\\'') // escape all the single quotes + .replace(/^"|"$/g, '\''); // replace the first and the last quote + + } else if (node[0][0] === '\'' && this._value === 'double') { + node[0] = node[0] + .replace(/\\'/g, '\'') // unescape all escaped single quotes + .replace(/([^\\])"/g, '$1\\\"') // escape all the double quotes + .replace(/^'|'$/g, '"'); // replace the first and the last quote + } + } + }, + + /** + * Detects the value of an option at the tree node. + * + * @param {String} nodeType + * @param {node} node + */ + detect: function(nodeType, node) { + if (nodeType === 'string') { + if (node[0][0] === '"') { + return 'double'; + } else if (node[0][0] === '\'') { + return 'single'; + } + } + } +}; diff --git a/test/integral.expect.css b/test/integral.expect.css index c65c07ab..c15d9c9d 100644 --- a/test/integral.expect.css +++ b/test/integral.expect.css @@ -14,8 +14,10 @@ } /* :after — фон */ - .radio-button_theme_normal .radio-button__radio:after + .radio-button_theme_normal .radio-button__radio[class^='radio']:after { + content: 'it\'s something different'; + background: #fff; background: -webkit-linear-gradient(top, #fff 0,#eee 100%); background: -moz-linear-gradient(top, #fff 0, #eee 100%); @@ -26,6 +28,8 @@ /* _focused_yes */ .radio-button_theme_normal .radio-button__radio_focused_yes:before { + content: 'hello'; + -moz-box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07); box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0 1px 0 rgba(0,0,0,.07); } diff --git a/test/integral.js b/test/integral.js index 37301801..eae02451 100644 --- a/test/integral.js +++ b/test/integral.js @@ -40,6 +40,7 @@ vow.all(['origin', 'expect'].map(function(type) { 'color-shorthand': true, 'element-case': 'lower', 'leading-zero': false, + 'quotes': 'single', 'strip-spaces': true, 'eof-newline': true, 'stick-brace': '\n', diff --git a/test/integral.origin.css b/test/integral.origin.css index 5c126da3..cb280a33 100644 --- a/test/integral.origin.css +++ b/test/integral.origin.css @@ -12,18 +12,20 @@ } /* :after — фон */ - .radio-button_theme_normal .radio-button__radio:after + .radio-button_theme_normal .radio-button__radio[class^="radio"]:after { background: #fFf; background: -webkit-linear-gradient(top, #FffFff 0,#eeeeEe 100%); background: -moz-linear-gradient(top, #fff 0, #eEe 100%); background: -o-linear-gradient(top, #fff 0,#eee 100%); background: linear-gradient(to bottom, #ffffff 0,#eeEeee 100%); + content: "it's something different"; } /* _focused_yes */ .radio-button_theme_normal .radio-button__radio_focused_yes:before { + content: "hello"; -moz-box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0px 1px 0px rgba(0,0,0,.07); box-shadow: 0 0 6px 2px rgba(255,204,0,.7), 0px 1px 0px rgba(0,0,0,.07); } diff --git a/test/quotes.js b/test/quotes.js new file mode 100644 index 00000000..a1be1cea --- /dev/null +++ b/test/quotes.js @@ -0,0 +1,134 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); + +describe('options/quotes', function() { + var comb; + beforeEach(function() { + comb = new Comb(); + }); + + it('Invalid String should not change quotes', function() { + comb.configure({ quotes: 'foobar' }); + assert.equal( + comb.processString( + 'a { content: "" }' + + 'b { content: \'\' }' + ), + 'a { content: "" }' + + 'b { content: \'\' }' + ); + }); + + it('`single` value should set the quotes to single', function() { + comb.configure({ quotes: 'single' }); + assert.equal( + comb.processString( + 'a { content: "" }' + + 'b { content: \'\' }' + ), + 'a { content: \'\' }' + + 'b { content: \'\' }' + ); + }); + + it('`double` value should set the quotes to double', function() { + comb.configure({ quotes: 'double' }); + assert.equal( + comb.processString( + 'a { content: "" }' + + 'b { content: \'\' }' + ), + 'a { content: "" }' + + 'b { content: "" }' + ); + }); + + it('`double` value should set the quotes to double in attrs and urls', function() { + comb.configure({ quotes: 'double' }); + assert.equal( + comb.processString( + 'a[class^=\'foo\'] { background: url(\'foo.png\') }' + ), + 'a[class^="foo"] { background: url("foo.png") }' + ); + }); + + it('`double` value should escape the unescaped double quotes on change', function() { + comb.configure({ quotes: 'double' }); + assert.equal( + comb.processString( + 'a { content: "\\"" }' + + 'b { content: \'"\' }' + ), + 'a { content: "\\"" }' + + 'b { content: "\\"" }' + ); + }); + + + it('`single` value should unescape the escaped double quotes on change', function() { + comb.configure({ quotes: 'single' }); + assert.equal( + comb.processString( + 'a { content: "\\"" }' + ), + 'a { content: \'"\' }' + ); + }); + + // Helper to check the detection + function should_detect(options, a, b) { + assert.equal( + JSON.stringify(comb.detectInString(a, options)), + JSON.stringify(b) + ); + } + + it('Should not detect quotes when there are none', function() { + should_detect( + ['quotes'], + 'a { color:red }', + {} + ); + }); + + it('Should detect double quotes', function() { + should_detect( + ['quotes'], + 'a { content: "foo" }', + { + quotes: 'double' + } + ); + }); + + it('Should detect single quotes', function() { + should_detect( + ['quotes'], + 'a { content: \'foo\' }', + { + quotes: 'single' + } + ); + }); + + it('Should detect single quotes in attribute', function() { + should_detect( + ['quotes'], + 'a[class^=\'foo\'] { color: red }', + { + quotes: 'single' + } + ); + }); + + it('Should detect double quotes in url', function() { + should_detect( + ['quotes'], + 'a { background: url("foo.png") }', + { + quotes: 'double' + } + ); + }); +}); From ea97641e5bc89fccdc1585f327b29d4964555d80 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Thu, 12 Dec 2013 18:17:45 +0400 Subject: [PATCH 68/75] Pass config to Comb constructor You can now create new Comb instance and configure it at the same time. To use one of predefined configs, pass its name: var comb = new Comb('zen'); // or `csscomb`, or `yandex` To use custom config, pass its object: var comb = new Comb({ 'always-semicolon': true }); --- lib/csscomb.js | 10 +++++++++- test/configure.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 test/configure.js diff --git a/lib/csscomb.js b/lib/csscomb.js index ac9a76ed..b2019270 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -11,7 +11,7 @@ var doNothing = function() {}; * @constructor * @name Comb */ -var Comb = function() { +var Comb = function(config) { this.SUPPORTED_SYNTAXES = ['css', 'scss', 'less']; this._options = [ 'remove-empty-rulesets', @@ -34,6 +34,14 @@ var Comb = function() { ]; this._exclude = null; this._detect = false; + + // If config was passed, configure: + if (typeof config === 'string' && + ['csscomb', 'zen', 'yandex'].indexOf(config) > -1) { + config = require('../config/' + config + '.json'); + } + + if (typeof config === 'object') this.configure(config); }; Comb.prototype = { diff --git a/test/configure.js b/test/configure.js new file mode 100644 index 00000000..aa3a15d6 --- /dev/null +++ b/test/configure.js @@ -0,0 +1,32 @@ +var Comb = require('../lib/csscomb'); +var assert = require('assert'); + +describe('csscomb methods', function() { + var comb; + var input; + var output; + var expected; + + it('Passing no config to constructor should not configure anything', function() { + comb = new Comb(); + assert.equal(undefined, comb._handlers); + }); + + it('Passing valid config name to constructor should configure using correct config', function() { + comb = new Comb('zen'); + input = 'a { color: tomato; top: 0; }'; + expected = 'a {top: 0; color: tomato; }'; + output = comb.processString(input); + + assert.equal(expected, output); + }); + + it('Passing config object to constructor should configure using that object', function() { + comb = new Comb({ 'always-semicolon': true }); + input = 'a { color: tomato }'; + expected = 'a { color: tomato; }'; + output = comb.processString(input); + + assert.equal(expected, output); + }); +}); From 78f8e2aa0b4d8a6b1789951829d7797b1ee72f91 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Thu, 12 Dec 2013 18:40:31 +0400 Subject: [PATCH 69/75] Make `new Comb()` and `configure()` chainable You can now do things like: // Init, configure and process: var css = new Comb('csscomb').processFile('nani.scss'); // Comb a file with one line of code: new Comb().configure({ 'always-semicolon': true }).processFile('panda.less'); --- lib/csscomb.js | 6 ++++++ test/configure.js | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/csscomb.js b/lib/csscomb.js index b2019270..37d75765 100644 --- a/lib/csscomb.js +++ b/lib/csscomb.js @@ -42,6 +42,9 @@ var Comb = function(config) { } if (typeof config === 'object') this.configure(config); + + // Return Comb's object to make creating new instance chainable: + return this; }; Comb.prototype = { @@ -95,6 +98,9 @@ Comb.prototype = { this.changed = 0; this._verbose = config.verbose; this._lint = config.lint; + + // Return Comb's object to make the method chainable: + return this; }, /** diff --git a/test/configure.js b/test/configure.js index aa3a15d6..3cbda4ce 100644 --- a/test/configure.js +++ b/test/configure.js @@ -29,4 +29,20 @@ describe('csscomb methods', function() { assert.equal(expected, output); }); + + it('new Comb() should be chainable', function() { + input = 'a { color: tomato; top: 0; }'; + expected = 'a {top: 0; color: tomato; }'; + output = new Comb('zen').processString(input); + + assert.equal(expected, output); + }); + + it('configure() should be chainable', function() { + input = 'a { color: tomato }'; + expected = 'a { color: tomato; }'; + output = new Comb().configure({ 'always-semicolon': true }).processString(input); + + assert.equal(expected, output); + }); }); From 5c3729bc4c9f9709f4fbf191047369183ebd6929 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Sun, 15 Dec 2013 00:52:12 +0400 Subject: [PATCH 70/75] Sort order: Remove empty lines between declarations --- lib/options/sort-order.js | 10 ++++++++++ test/integral.expect.css | 1 - test/sort-order.js | 10 ++++++++++ test/sort-order/issue-94-1.css | 18 ++++++++++++++++++ test/sort-order/issue-94-1.expected.css | 17 +++++++++++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 test/sort-order/issue-94-1.css create mode 100644 test/sort-order/issue-94-1.expected.css diff --git a/lib/options/sort-order.js b/lib/options/sort-order.js index 98e5083f..e8d43312 100644 --- a/lib/options/sort-order.js +++ b/lib/options/sort-order.js @@ -69,6 +69,11 @@ module.exports = { return false; } + // Remove any empty lines: + if (currentNode[0] === 's') { + currentNode[1] = currentNode[1].replace(/\n[\s\t\n\r]*\n/, '\n'); + } + // If the node is declaration or @-rule, stop and return all // found nodes with spaces and comments (if there are any): if (SC.indexOf(currentNode[0]) === -1) break; @@ -102,6 +107,11 @@ module.exports = { // If there is no node, or it is nor spaces neither comment, stop: if (!currentNode || SC.indexOf(currentNode[0]) === -1) break; + // Remove any empty lines: + if (currentNode[0] === 's') { + currentNode[1] = currentNode[1].replace(/\n[\s\t\n\r]*\n/, '\n'); + } + if (['commentML', 'commentSL'].indexOf(currentNode[0]) > -1) { sc.push(currentNode); d.push(i + 1); diff --git a/test/integral.expect.css b/test/integral.expect.css index c15d9c9d..cf2d68de 100644 --- a/test/integral.expect.css +++ b/test/integral.expect.css @@ -8,7 +8,6 @@ background: -moz-linear-gradient(top, rgba(0,0,0,.2) 0, rgba(0,0,0,.4) 100%); background: -o-linear-gradient(top, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%); background: linear-gradient(to bottom, rgba(0,0,0,.2) 0,rgba(0,0,0,.4) 100%); - -moz-box-shadow: 0 1px 0 rgba(0,0,0,.07); box-shadow: 0 1px 0 rgba(0,0,0,.07); } diff --git a/test/sort-order.js b/test/sort-order.js index bd787c5d..52275ebd 100644 --- a/test/sort-order.js +++ b/test/sort-order.js @@ -120,4 +120,14 @@ describe('options/sort-order', function() { assert.equal(input, expected); }); + + it('Issue 94. Test 1', function() { + var config = comb.getConfig('csscomb'); + + var input = readFile('issue-94-1.css'); + var expected = readFile('issue-94-1.expected.css'); + + comb.configure(config); + assert.equal(comb.processString(input), expected); + }); }); diff --git a/test/sort-order/issue-94-1.css b/test/sort-order/issue-94-1.css new file mode 100644 index 00000000..54131c51 --- /dev/null +++ b/test/sort-order/issue-94-1.css @@ -0,0 +1,18 @@ +.test +{ + position: absolute; + top: -1px; + right: -1px; + bottom: -1px; + left: -1px; + + border: 1px solid transparent; + border-color: rgba(0,0,0,0.38) rgba(0,0,0,0.27) rgba(0,0,0,0.16); + + background: -webkit-linear-gradient(#fff, #fff); + background: linear-gradient(#fff, #fff); + background-clip: padding-box; + background-size: 16px 16px; + + -webkit-appearance: textfield; +} diff --git a/test/sort-order/issue-94-1.expected.css b/test/sort-order/issue-94-1.expected.css new file mode 100644 index 00000000..0943808b --- /dev/null +++ b/test/sort-order/issue-94-1.expected.css @@ -0,0 +1,17 @@ +.test +{ + position: absolute; + top: -1px; + right: -1px; + bottom: -1px; + left: -1px; + + border: 1px solid transparent; + border-color: rgba(0,0,0,.38) rgba(0,0,0,.27) rgba(0,0,0,.16); + background: -webkit-linear-gradient(#fff, #fff); + background: linear-gradient(#fff, #fff); + background-clip: padding-box; + background-size: 16px 16px; + + -webkit-appearance: textfield; +} From b6b8ecbbb2c814e3e98166baeb28e68afda4f0fc Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Sun, 15 Dec 2013 02:12:19 +0400 Subject: [PATCH 71/75] Sort order: Fix for properties with equal index If two declarations have the same group index and the same property index, after sorting they should stay in order of their original appearance. Due to some strange behaviour of `Array.prototype.sort` properties get mixed from time to time. For example, this: a { width: 0; width: auto; } can suddenly become this: a { width: auto; width: 0; } In order to avoid such situations, save node index value within extended node and take it into account while doing `sorted.sort()`. --- lib/options/sort-order.js | 8 +++++++- test/sort-order.js | 11 +++++++++++ test/sort-order/issue-94-2.css | 19 +++++++++++++++++++ test/sort-order/issue-94-2.expected.css | 19 +++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 test/sort-order/issue-94-2.css create mode 100644 test/sort-order/issue-94-2.expected.css diff --git a/lib/options/sort-order.js b/lib/options/sort-order.js index e8d43312..5af60b57 100644 --- a/lib/options/sort-order.js +++ b/lib/options/sort-order.js @@ -156,6 +156,7 @@ module.exports = { var orderProperty = order[propertyName]; extendedNode = { + i: i, node: currentNode, sc0: sc0, delim: [] @@ -259,7 +260,12 @@ module.exports = { // If a and b have the same group index, and a's property index is // higher than b's property index, in a sorted list a appears after // b: - return a.propertyIndex - b.propertyIndex; + if (a.propertyIndex !== b.propertyIndex) return a.propertyIndex - b.propertyIndex; + + // If a and b have the same group index and the same property index, + // in a sorted list they appear in the same order they were in + // original array: + return a.i - b.i; }); // Build all nodes back together. First go sorted declarations, then diff --git a/test/sort-order.js b/test/sort-order.js index 52275ebd..343ebce2 100644 --- a/test/sort-order.js +++ b/test/sort-order.js @@ -130,4 +130,15 @@ describe('options/sort-order', function() { comb.configure(config); assert.equal(comb.processString(input), expected); }); + + it('Issue 94. Test 2', function() { + var config = comb.getConfig('csscomb'); + + var input = readFile('issue-94-2.css'); + var expected = readFile('issue-94-2.expected.css'); + + comb.configure(config); + assert.equal(comb.processString(input), expected); + }); + }); diff --git a/test/sort-order/issue-94-2.css b/test/sort-order/issue-94-2.css new file mode 100644 index 00000000..0365913c --- /dev/null +++ b/test/sort-order/issue-94-2.css @@ -0,0 +1,19 @@ +.test +{ + width: 0; + width: 100%; + height: 0; + + background: #fff; + color: #000; + + display: -moz-inline-stack; + display: inline-block; + + position: absolute; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; +} diff --git a/test/sort-order/issue-94-2.expected.css b/test/sort-order/issue-94-2.expected.css new file mode 100644 index 00000000..85e618de --- /dev/null +++ b/test/sort-order/issue-94-2.expected.css @@ -0,0 +1,19 @@ +.test +{ + position: absolute; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + + display: -moz-inline-stack; + display: inline-block; + + width: 0; + width: 100%; + height: 0; + + color: #000; + background: #fff; +} From 71029c173c6c841502687dbdc145ddfe04369f43 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Sun, 15 Dec 2013 02:27:39 +0400 Subject: [PATCH 72/75] Add test for #94 --- test/sort-order.js | 9 +++++++++ test/sort-order/issue-94-3.css | 21 +++++++++++++++++++++ test/sort-order/issue-94-3.expected.css | 20 ++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 test/sort-order/issue-94-3.css create mode 100644 test/sort-order/issue-94-3.expected.css diff --git a/test/sort-order.js b/test/sort-order.js index 343ebce2..5dd4ffcf 100644 --- a/test/sort-order.js +++ b/test/sort-order.js @@ -141,4 +141,13 @@ describe('options/sort-order', function() { assert.equal(comb.processString(input), expected); }); + it('Issue 94. Test 3', function() { + var config = comb.getConfig('csscomb'); + + var input = readFile('issue-94-3.css'); + var expected = readFile('issue-94-3.expected.css'); + + comb.configure(config); + assert.equal(comb.processString(input), expected); + }); }); diff --git a/test/sort-order/issue-94-3.css b/test/sort-order/issue-94-3.css new file mode 100644 index 00000000..872eddcf --- /dev/null +++ b/test/sort-order/issue-94-3.css @@ -0,0 +1,21 @@ +.input-view { + position: absolute; + -webkit-appearance: none; + right: -1px; + bottom: -1px; + left: -1px; + + border: 1px solid transparent; + + top: -1px; + + background: -webkit-linear-gradient(#FFF, #FFF); + background: linear-gradient(#FFF, #FFF); + background-clip: padding-box; + background-size: 16px 16px; + box-shadow: 0 1px 0 rgba(255,255,255,0.2), inset 0 1px 1px rgba(0,0,0,0.1); + + border-color: rgba(0,0,0,0.27); + border-top-color: rgba(0,0,0,0.38); + border-bottom-color: rgba(0,0,0,0.16); +} diff --git a/test/sort-order/issue-94-3.expected.css b/test/sort-order/issue-94-3.expected.css new file mode 100644 index 00000000..92f29432 --- /dev/null +++ b/test/sort-order/issue-94-3.expected.css @@ -0,0 +1,20 @@ +.input-view +{ + position: absolute; + top: -1px; + right: -1px; + bottom: -1px; + left: -1px; + + border: 1px solid transparent; + border-color: rgba(0,0,0,.27); + border-top-color: rgba(0,0,0,.38); + border-bottom-color: rgba(0,0,0,.16); + background: -webkit-linear-gradient(#fff, #fff); + background: linear-gradient(#fff, #fff); + background-clip: padding-box; + background-size: 16px 16px; + box-shadow: 0 1px 0 rgba(255,255,255,.2), inset 0 1px 1px rgba(0,0,0,.1); + + -webkit-appearance: none; +} From 3f913878a79a465d9ae7fffa32487289c36c9aa8 Mon Sep 17 00:00:00 2001 From: Tony Ganch Date: Mon, 16 Dec 2013 18:37:29 +0400 Subject: [PATCH 73/75] Sort order: Make array of strings acceptable value --- lib/options/sort-order.js | 15 +++++++++++---- test/sort-order.js | 10 ++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/options/sort-order.js b/lib/options/sort-order.js index 98e5083f..70398f52 100644 --- a/lib/options/sort-order.js +++ b/lib/options/sort-order.js @@ -10,11 +10,18 @@ module.exports = { if (!value) return; this._order = {}; - value.forEach(function(group, groupIndex) { - group.forEach(function(prop, propIndex) { - this._order[prop] = { group: groupIndex, prop: propIndex }; + + if (typeof value[0] === 'string') { + value.forEach(function(prop, propIndex) { + this._order[prop] = { group: 0, prop: propIndex }; + }, this); + } else { + value.forEach(function(group, groupIndex) { + group.forEach(function(prop, propIndex) { + this._order[prop] = { group: groupIndex, prop: propIndex }; + }, this); }, this); - }, this); + } return this; }, diff --git a/test/sort-order.js b/test/sort-order.js index bd787c5d..987f76f2 100644 --- a/test/sort-order.js +++ b/test/sort-order.js @@ -13,6 +13,16 @@ describe('options/sort-order', function() { comb = new Comb(); }); + it('Should be in expected order in case properties are not grouped', function() { + var config = { 'sort-order': ['position', 'z-index'] }; + + var input = readFile('single-group.css'); + var expected = readFile('single-group.expected.css'); + + comb.configure(config); + assert.equal(comb.processString(input), expected); + }); + it('Should be in expected order in case of 1 group', function() { var config = { 'sort-order': [ ['position', 'z-index'] From 95758cc12f072538bfb94d42f892d844a0207603 Mon Sep 17 00:00:00 2001 From: Mikhail Troshev Date: Wed, 18 Dec 2013 00:15:07 +0400 Subject: [PATCH 74/75] node@0.8 no longer supported --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b2118ad4..4a5a560e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: node_js node_js: - - "0.8" - "0.10" - "0.11" From 4da107f2411e7509e79d5ce9dc90e46a4e1eb85e Mon Sep 17 00:00:00 2001 From: Mikhail Troshev Date: Wed, 18 Dec 2013 00:21:43 +0400 Subject: [PATCH 75/75] v2.0.0 --- CHANGELOG.md | 10 +++++++++- README.md | 3 ++- package.json | 9 +++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e967cb93..5ec174ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog -## 1.X.X - 2013-XX-XX +## 2.0.0 - 2013-12-18 +**Great thanks for @tonyganch and @kizu!** - Use Gonzales PE to parse *.scss and *.less files - Support sorting properties in *.scss and *.less files +- Codestyle detection +- Option: always-semicolon (scss + less) +- Option: quotes +- Two more config examples now: `csscomb` and `zen` +- Lots of refactoring and fixes +- Lots of tests added +- Node v0.8 no longer supported ## 1.0.0 - 2013-11-06 - Option: vendor-prefix-align diff --git a/README.md b/README.md index 84404f4e..f77b5365 100644 --- a/README.md +++ b/README.md @@ -724,7 +724,8 @@ review the [guidelines for contributing](CONTRIBUTE.md). ## Authors -[@mishanga](https://github.com/mishanga) +[@mishanga](https://github.com/mishanga), +[@tonyganch](https://github.com/tonyganch) Thanks for assistance and contributions: diff --git a/package.json b/package.json index 9365c1da..30a6dedb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "csscomb", "description": "CSS coding style formatter", - "version": "1.0.0", + "version": "2.0.0", "homepage": "http://csscomb.com/", "author": "Mikhail Troshev ", "repository": "https://github.com/csscomb/csscomb.js", @@ -11,6 +11,11 @@ "email": "mishanga@yandex-team.ru", "web": "http://mishanga.pro/" }, + { + "name": "Tony Ganch", + "email": "tonyganch+github@gmail.com", + "web": "http://tonyganch.com/" + }, { "name": "Slava Oliyanchuk", "email": "miripiruni@gmail.com", @@ -36,7 +41,7 @@ } ], "engines": { - "node": ">= 0.8.0" + "node": ">= 0.10.0" }, "dependencies": { "commander": "2.0.0",