diff --git a/js/lib/beautify-css.js b/js/lib/beautify-css.js index 77916e972..6de75d334 100644 --- a/js/lib/beautify-css.js +++ b/js/lib/beautify-css.js @@ -58,7 +58,7 @@ // http://www.w3.org/TR/CSS21/syndata.html#tokenization // http://www.w3.org/TR/css3-syntax/ -(function () { +(function() { function css_beautify(source_text, options) { options = options || {}; var indentSize = options.indent_size || 4; @@ -103,6 +103,14 @@ return source_text.substring(start, pos + 1); } + function peekString(endChar) { + var prev_pos = pos; + var str = eatString(endChar); + pos = prev_pos - 1; + next(); + return str; + } + function eatWhitespace() { var start = pos; while (whiteRe.test(peek())) { @@ -164,22 +172,22 @@ } var print = {}; - print["{"] = function (ch) { + print["{"] = function(ch) { print.singleSpace(); output.push(ch); print.newLine(); }; - print["}"] = function (ch) { + print["}"] = function(ch) { print.newLine(); output.push(ch); print.newLine(); }; - print._lastCharWhitespace = function () { + print._lastCharWhitespace = function() { return whiteRe.test(output[output.length - 1]); }; - print.newLine = function (keepWhitespace) { + print.newLine = function(keepWhitespace) { if (!keepWhitespace) { while (print._lastCharWhitespace()) { output.pop(); @@ -193,7 +201,7 @@ output.push(basebaseIndentString); } }; - print.singleSpace = function () { + print.singleSpace = function() { if (output.length && !print._lastCharWhitespace()) { output.push(' '); } @@ -222,11 +230,14 @@ } else if (ch === '/' && peek() === '/') { // single line comment output.push(eatComment(true), basebaseIndentString); } else if (ch === '@') { - // strip trailing space, if present, for hash property checks - var atRule = eatString(" ").replace(/ $/, ''); - // pass along the space we found as a separate item - output.push(atRule, ch); + if (isAfterSpace) { + print.singleSpace(); + } + output.push(ch); + + // strip trailing space, if present, for hash property checks + var atRule = peekString(" ").replace(/\s$/, ''); // might be a nesting at-rule if (atRule in css_beautify.NESTED_AT_RULE) { @@ -261,7 +272,7 @@ } } else if (ch === ":") { eatWhitespace(); - if (insideRule || enteringConditionalGroup) { + if ((insideRule || enteringConditionalGroup) && !lookBack("&")) { // 'property: value' delimiter // which could be in a conditional group query output.push(ch, " "); @@ -362,8 +373,10 @@ /*global define */ if (typeof define === "function" && define.amd) { // Add support for AMD ( https://github.com/amdjs/amdjs-api/wiki/AMD#defineamd-property- ) - define([], function () { - return { css_beautify: css_beautify }; + define([], function() { + return { + css_beautify: css_beautify + }; }); } else if (typeof exports !== "undefined") { // Add support for CommonJS. Just put this file somewhere on your require.paths diff --git a/js/test/beautify-tests.js b/js/test/beautify-tests.js index f28d2b9f6..c2c833c3d 100755 --- a/js/test/beautify-tests.js +++ b/js/test/beautify-tests.js @@ -2291,6 +2291,32 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify, html_beautify, */ btc("@font-face {\n\tfont-family: 'Bitstream Vera Serif Bold';\n\tsrc: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');\n}\n@media screen {\n\t#foo:hover {\n\t\tbackground-image: url(foo.png);\n\t}\n\t@media screen and (min-device-pixel-ratio: 2) {\n\t\t@font-face {\n\t\t\tfont-family: 'Helvetica Neue'\n\t\t}\n\t\t#foo:hover {\n\t\t\tbackground-image: url(foo@2x.png);\n\t\t}\n\t}\n}"); + // less-css cases + btc('.well{@well-bg:@bg-color;@well-fg:@fg-color;}','.well {\n\t@well-bg: @bg-color;\n\t@well-fg: @fg-color;\n}'); + btc('.well {&.active {\nbox-shadow: 0 1px 1px @border-color, 1px 0 1px @border-color;}}', + '.well {\n' + + '\t&.active {\n' + + '\t\tbox-shadow: 0 1px 1px @border-color, 1px 0 1px @border-color;\n' + + '\t}\n' + + '}'); + btc('a {\n' + + '\tcolor: blue;\n' + + '\t&:hover {\n' + + '\t\tcolor: green;\n' + + '\t}\n' + + '\t& & &&&.active {\n' + + '\t\tcolor: green;\n' + + '\t}\n' + + '}'); + + // Not sure if this is sensible + // but I believe it is correct to not remove the space in "&: hover". + btc('a {\n' + + '\t&: hover {\n' + + '\t\tcolor: green;\n' + + '\t}\n' + + '}'); + // test options opts.indent_size = 2; opts.indent_char = ' '; diff --git a/python/cssbeautifier/__init__.py b/python/cssbeautifier/__init__.py index abff39edf..fb63f0e92 100644 --- a/python/cssbeautifier/__init__.py +++ b/python/cssbeautifier/__init__.py @@ -195,6 +195,13 @@ def eatString(self, endChar): break return self.source_text[start:self.pos] + endChar + def peekString(self, endChar): + start = self.pos + st = self.eatString(endChar) + self.pos = start - 1 + self.next() + return st + def eatWhitespace(self): start = self.pos while WHITE_RE.search(self.peek()) is not None: @@ -252,15 +259,16 @@ def beautify(self): printer.comment(self.eatComment(True)[0:-1]) printer.newLine() elif self.ch == '@': + # pass along the space we found as a separate item + if isAfterSpace: + printer.singleSpace() + printer.push(self.ch) + # strip trailing space, if present, for hash property check - atRule = self.eatString(" ") - if(atRule[-1] == " "): + atRule = self.peekString(" ") + if atRule[-1].isspace(): atRule = atRule[:-1] - # pass along the space we found as a separate item - printer.push(atRule) - printer.push(self.ch) - # might be a nesting at-rule if atRule in self.NESTED_AT_RULE: printer.nestedLevel += 1 @@ -290,7 +298,7 @@ def beautify(self): printer.nestedLevel -= 1 elif self.ch == ":": self.eatWhitespace() - if insideRule or enteringConditionalGroup: + if (insideRule or enteringConditionalGroup) and not self.lookBack('&'): # 'property: value' delimiter # which could be in a conditional group query printer.push(self.ch) diff --git a/python/cssbeautifier/tests/test.py b/python/cssbeautifier/tests/test.py index 7e419251c..0c5c70a2f 100644 --- a/python/cssbeautifier/tests/test.py +++ b/python/cssbeautifier/tests/test.py @@ -125,6 +125,35 @@ def testOptions(self): t( "a:not(\"foobar\\\";{}omg\"){\ncontent: 'example\\';{} text';\ncontent: \"example\\\";{} text\";}", "a:not(\"foobar\\\";{}omg\") {\n content: 'example\\';{} text';\n content: \"example\\\";{} text\";\n}") + def testLessCss(self): + self.resetOptions() + t = self.decodesto + + t('.well{@well-bg:@bg-color;@well-fg:@fg-color;}','.well {\n\t@well-bg: @bg-color;\n\t@well-fg: @fg-color;\n}') + t('.well {&.active {\nbox-shadow: 0 1px 1px @border-color, 1px 0 1px @border-color;}}', + '.well {\n' + + '\t&.active {\n' + + '\t\tbox-shadow: 0 1px 1px @border-color, 1px 0 1px @border-color;\n' + + '\t}\n' + + '}') + t('a {\n' + + '\tcolor: blue;\n' + + '\t&:hover {\n' + + '\t\tcolor: green;\n' + + '\t}\n' + + '\t& & &&&.active {\n' + + '\t\tcolor: green;\n' + + '\t}\n' + + '}') + + # Not sure if this is sensible + # but I believe it is correct to not remove the space in "&: hover". + t('a {\n' + + '\t&: hover {\n' + + '\t\tcolor: green;\n' + + '\t}\n' + + '}'); + def decodesto(self, input, expectation=None): if expectation == None: expectation = input