Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support nested pseudo-classes and parent reference (LESS) #427

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions js/lib/beautify-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,20 @@
return restOfLine.indexOf('//') !== -1;
}

// Nested pseudo-class if we are insideRule
// and the next special character found opens
// a new block
function foundNestedPseudoClass() {
for (var i = pos + 1; i < source_text.length; i++){
var 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 +279,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 @@ -1800,6 +1800,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