Skip to content

Commit

Permalink
Support SASS nested pseudo-classes and parent reference
Browse files Browse the repository at this point in the history
JavaScript:
css beautifier checks if the colon it encounters
is part of a property: value or part of a nested pseudo-class/
parent reference. In case of nested pseudo-class or parent reference
it does not output a space after the colon.

Added general test for parent reference, nested pseudo-class and
SASS import statement. Added special test for issues beautifier#410 and beautifier#414.

Python:
Best effort to support parent reference and nested pseudo-class.
When it encounters a colon it checks if the colon is part
of a property: value or part of parent reference/nested pseudo-class.
In case of parent reference/nested pseudo-class it does not output
a space after the colon.

Added tests for reference/nested pseudo-class. Add tests for
issues beautifier#410, beautifier#414

Fixes issues beautifier#410 and beautifier#414
Related to issue beautifier#356
  • Loading branch information
nponiros committed Mar 18, 2014
1 parent b5fa6e7 commit 29442ad
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 4 deletions.
27 changes: 24 additions & 3 deletions js/lib/beautify-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,21 @@
return restOfLine.indexOf('//') !== -1;
}

// Nested pseudo-class if we are insideRule
// and the next special character found opens
// a new block
function foundNestedPseudoClass() {
var ch = source_text[pos];
for (var i = pos + 1; i < source_text.length; i++){
ch = source_text.charAt(i);
if (ch === "{"){
return true;
} else if (ch === ";" || ch === "}" || ch === ")") {
return false;
}
}
}

// printer
var indentString = source_text.match(/^[\r\n]*[\t ]*/)[0];
var singleIndent = new Array(indentSize + 1).join(indentCharacter);
Expand Down Expand Up @@ -265,9 +280,15 @@
} else if (ch === ":") {
eatWhitespace();
if (insideRule || enteringConditionalGroup) {
// 'property: value' delimiter
// which could be in a conditional group query
output.push(ch, " ");
// sass/less parent reference don't use a space OR
// sass nested pseudo-class don't use a space
if (lookBack("&") || foundNestedPseudoClass()) {
output.push(ch);
} else {
// 'property: value' delimiter
// which could be in a conditional group query
output.push(ch, " ");
}
} else {
if (peek() === ":") {
// pseudo-element
Expand Down
21 changes: 21 additions & 0 deletions js/test/beautify-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1820,6 +1820,27 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify, html_beautify,
btc("a:not(\"foobar\\\";{}omg\"){\ncontent: 'example\\';{} text';\ncontent: \"example\\\";{} text\";}",
"a:not(\"foobar\\\";{}omg\") {\n content: 'example\\';{} text';\n content: \"example\\\";{} text\";\n}\n");

// SASS support tests
// @import should not be formated
btc("@import \"test\";\n");

// don't break nested pseudo-classes
btc("a:first-child{color:red;div:first-child{color:black;}}",
"a:first-child {\n color: red;\n div:first-child {\n color: black;\n }\n}\n");

btc("a:first-child,a:first-child{color:red;div:first-child,div:hover{color:black;}}",
"a:first-child, a:first-child {\n color: red;\n div:first-child, div:hover {\n color: black;\n }\n}\n");

// handle SASS/LESS parent reference
btc("div{&:first-letter {text-transform: uppercase;}}",
"div {\n &:first-letter {\n text-transform: uppercase;\n }\n}\n");

// Test for issue #410
btc(".rule {\n &:hover {}\n}\n");

// Test for issue #414
btc("time {\n &:first-letter {\n text-transform: uppercase;\n }\n}\n");

return sanitytest;
}

Expand Down
20 changes: 19 additions & 1 deletion python/cssbeautifier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ def colon(self):
self.output.append(":")
self.singleSpace()

def colonNoSpace(self):
self.output.append(":")

def semicolon(self):
self.output.append(";")
self.newLine()
Expand Down Expand Up @@ -204,6 +207,18 @@ def lookBack(self, string):
past = self.source_text[self.pos - len(string):self.pos]
return past.lower() == string

# Nested pseudo-class if we are insideRule
# and the next special character found opens
# a new block
def foundNestedPseudoClass(self):
ch = self.source_text[self.pos]
for i in range(self.pos + 1, len(self.source_text) - 1):
ch = self.source_text[i]
if ch == "{":
return True
elif ch == ";" or self.ch == "}" or self.ch == ")":
return False

def isCommentOnLine(self):
endOfLine = self.source_text.find('\n', self.pos)
if endOfLine == -1:
Expand Down Expand Up @@ -245,7 +260,10 @@ def beautify(self):
insideRule = False
elif self.ch == ":":
self.eatWhitespace()
printer.colon()
if self.lookBack("&") or self.foundNestedPseudoClass():
printer.colonNoSpace()
else:
printer.colon()
insideRule = True
elif self.ch == '"' or self.ch == '\'':
printer.push(self.eatString(self.ch))
Expand Down
46 changes: 46 additions & 0 deletions python/cssbeautifier/tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,52 @@ def testOptions(self):
t("@media print {.tab{}}", "@media print {\n .tab {}\n}\n")
t("#bla, #foo{color:black}", "#bla, #foo {\n color: black\n}\n")

def testPseudoClasses(self):
self.resetOptions()
self.options.indent_size = 2
self.options.indent_char = ' '
self.options.selector_separator_newline = False
t = self.decodesto

t("div:first-child{color:black;}", "div:first-child {\n color: black;\n}\n")

def testParentReference(self):
self.resetOptions()
self.options.indent_size = 2
self.options.indent_char = ' '
self.options.selector_separator_newline = False
t = self.decodesto

t("div:first-child{color: black;&:hover{color:red;}}", "div:first-child {\n color: black;\n &:hover {\n color: red;\n }\n}\n")

def testNestedPseudoClasses(self):
self.resetOptions()
self.options.indent_size = 2
self.options.indent_char = ' '
self.options.selector_separator_newline = False
t = self.decodesto

t("a:first-child{color:red;div:first-child{color:black;}}", "a:first-child {\n color: red;\n div:first-child {\n color: black;\n }\n}\n")
t("a:first-child,a:first-child{color:red;div:first-child,div:hover{color:black;}}", "a:first-child, a:first-child {\n color: red;\n div:first-child, div:hover {\n color: black;\n }\n}\n")

def testForIssue410(self):
self.resetOptions()
self.options.indent_size = 2
self.options.indent_char = ' '
self.options.selector_separator_newline = False
t = self.decodesto

t(".rule {\n &:hover {}\n}\n")

def testForIssue414(self):
self.resetOptions()
self.options.indent_size = 2
self.options.indent_char = ' '
self.options.selector_separator_newline = False
t = self.decodesto

t("time {\n &:first-letter {\n text-transform: uppercase;\n }\n}\n")

def decodesto(self, input, expectation=None):
self.assertEqual(
cssbeautifier.beautify(input, self.options), expectation or input)
Expand Down

0 comments on commit 29442ad

Please sign in to comment.