From aa5043d558bf2362a9f61697b489c2d172b77e97 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Tue, 12 May 2015 20:22:42 +0200 Subject: [PATCH 1/2] Implement `is_superselector` sass function Fixes https://github.com/sass/libsass/issues/1091 Fixes https://github.com/sass/libsass/issues/1063 Fixes https://github.com/sass/libsass/issues/823 --- ast.cpp | 74 ++++++++++++++++++++++++++++++++++++++++++++++++--- ast.hpp | 12 ++++++--- context.cpp | 2 ++ functions.cpp | 14 ++++++++++ functions.hpp | 2 ++ parser.cpp | 8 ++++++ parser.hpp | 2 ++ 7 files changed, 108 insertions(+), 6 deletions(-) diff --git a/ast.cpp b/ast.cpp index 1477240a5e..3347fd0361 100644 --- a/ast.cpp +++ b/ast.cpp @@ -242,7 +242,23 @@ namespace Sass { if (!lbase) // no lbase; just see if the left-hand qualifiers are a subset of the right-hand selector { for (size_t i = 0, L = length(); i < L; ++i) - { lset.insert((*this)[i]->perform(&to_string)); } + { + Selector* lhs = (*this)[i]; + if (Wrapped_Selector* wrapped = dynamic_cast(lhs)) { + if ( + wrapped->name() == ":matches(" || + wrapped->name() == ":-moz-any(" + ) { + lhs = wrapped->selector(); + if (Selector_List* list = dynamic_cast(lhs)) { + if (Compound_Selector* comp = dynamic_cast(rhs)) { + if (list->is_superselector_of(comp)) return true; + } + } + } + } + lset.insert(lhs->perform(&to_string)); + } for (size_t i = 0, L = rhs->length(); i < L; ++i) { rset.insert((*rhs)[i]->perform(&to_string)); } return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); @@ -326,8 +342,6 @@ namespace Sass { bool Complex_Selector::is_superselector_of(Compound_Selector* rhs) { - if (length() != 1) - { return false; } return base()->is_superselector_of(rhs); } @@ -352,6 +366,16 @@ namespace Sass { if (l_len == 1) { return lhs->head()->is_superselector_of(rhs->base()); } + // we have to look one tail deeper, since we cary the + // combinator around for it (which is important here) + if (rhs->tail() && lhs->tail() && combinator() != Complex_Selector::ANCESTOR_OF) { + Complex_Selector* lhs_tail = lhs->tail(); + Complex_Selector* rhs_tail = rhs->tail(); + if (lhs_tail->combinator() != rhs_tail->combinator()) return false; + if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) return false; + } + + bool found = false; Complex_Selector* marker = rhs; for (size_t i = 0, L = rhs->length(); i < L; ++i) { @@ -493,6 +517,50 @@ namespace Sass { #endif } + // it's a superselector if every selector of the right side + // list is a superselector of the given left side selector + bool Complex_Selector::is_superselector_of(Selector_List *sub) + { + // Check every rhs selector against left hand list + for(size_t i = 0, L = sub->length(); i < L; ++i) { + if (!is_superselector_of((*sub)[i])) return false; + } + return true; + } + + // it's a superselector if every selector of the right side + // list is a superselector of the given left side selector + bool Selector_List::is_superselector_of(Selector_List *sub) + { + // Check every rhs selector against left hand list + for(size_t i = 0, L = sub->length(); i < L; ++i) { + if (!is_superselector_of((*sub)[i])) return false; + } + return true; + } + + // it's a superselector if every selector on the right side + // is a superselector of any one of the left side selectors + bool Selector_List::is_superselector_of(Compound_Selector *sub) + { + // Check every lhs selector against right hand + for(size_t i = 0, L = length(); i < L; ++i) { + if ((*this)[i]->is_superselector_of(sub)) return true; + } + return false; + } + + // it's a superselector if every selector on the right side + // is a superselector of any one of the left side selectors + bool Selector_List::is_superselector_of(Complex_Selector *sub) + { + // Check every lhs selector against right hand + for(size_t i = 0, L = length(); i < L; ++i) { + if ((*this)[i]->is_superselector_of(sub)) return true; + } + return false; + } + /* not used anymore - remove? Selector_Placeholder* Selector_List::find_placeholder() { diff --git a/ast.hpp b/ast.hpp index d32ad335db..bbf70f8f98 100644 --- a/ast.hpp +++ b/ast.hpp @@ -1943,7 +1943,9 @@ namespace Sass { return (*this)[0]; return 0; } - bool is_superselector_of(Compound_Selector* rhs); + bool is_superselector_of(Compound_Selector* sub); + // bool is_superselector_of(Complex_Selector* sub); + // bool is_superselector_of(Selector_List* sub); virtual unsigned long specificity() { int sum = 0; @@ -2000,8 +2002,9 @@ namespace Sass { Complex_Selector* context(Context&); Complex_Selector* innermost(); size_t length(); - bool is_superselector_of(Compound_Selector*); - bool is_superselector_of(Complex_Selector*); + bool is_superselector_of(Compound_Selector* sub); + bool is_superselector_of(Complex_Selector* sub); + bool is_superselector_of(Selector_List* sub); // virtual Selector_Placeholder* find_placeholder(); Combinator clear_innermost(); void set_innermost(Complex_Selector*, Combinator); @@ -2086,6 +2089,9 @@ namespace Sass { : Selector(pstate), Vectorized(s), wspace_(0) { } // virtual Selector_Placeholder* find_placeholder(); + bool is_superselector_of(Compound_Selector* sub); + bool is_superselector_of(Complex_Selector* sub); + bool is_superselector_of(Selector_List* sub); virtual unsigned long specificity() { unsigned long sum = 0; diff --git a/context.cpp b/context.cpp index 4492529bfa..5620764072 100644 --- a/context.cpp +++ b/context.cpp @@ -539,6 +539,8 @@ namespace Sass { // Misc Functions register_function(ctx, inspect_sig, inspect, env); register_function(ctx, unique_id_sig, unique_id, env); + // Selector functions + register_function(ctx, is_superselector_sig, is_superselector, env); } void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) diff --git a/functions.cpp b/functions.cpp index 204c7dfa56..2563bf1f62 100644 --- a/functions.cpp +++ b/functions.cpp @@ -1564,6 +1564,20 @@ namespace Sass { // return v; } + Signature is_superselector_sig = "is-superselector($super, $sub)"; + BUILT_IN(is_superselector) + { + To_String to_string(&ctx, false); + Expression* ex_sup = ARG("$super", Expression); + Expression* ex_sub = ARG("$sub", Expression); + string sup_src = ex_sup->perform(&to_string) + "{"; + string sub_src = ex_sub->perform(&to_string) + "{"; + Selector_List* sel_sup = Parser::parse_selector(sup_src.c_str(), ctx); + Selector_List* sel_sub = Parser::parse_selector(sub_src.c_str(), ctx); + bool result = sel_sup->is_superselector_of(sel_sub); + return new (ctx.mem) Boolean(pstate, result); + } + Signature unique_id_sig = "unique-id()"; BUILT_IN(unique_id) { diff --git a/functions.hpp b/functions.hpp index a6d0b0fd22..c0307f6ed4 100644 --- a/functions.hpp +++ b/functions.hpp @@ -101,6 +101,7 @@ namespace Sass { extern Signature keywords_sig; extern Signature set_nth_sig; extern Signature unique_id_sig; + extern Signature is_superselector_sig; BUILT_IN(rgb); BUILT_IN(rgba_4); @@ -175,6 +176,7 @@ namespace Sass { BUILT_IN(keywords); BUILT_IN(set_nth); BUILT_IN(unique_id); + BUILT_IN(is_superselector); } } diff --git a/parser.cpp b/parser.cpp index 4d970aaae0..535d5fb839 100644 --- a/parser.cpp +++ b/parser.cpp @@ -40,6 +40,14 @@ namespace Sass { return p; } + Selector_List* Parser::parse_selector(const char* src, Context& ctx, ParserState pstate) + { + Parser p = Parser::from_c_str(src, ctx, pstate); + // ToDo: ruby sass errors on parent references + // ToDo: remap the source-map entries somehow + return p.parse_selector_group(); + } + bool Parser::peek_newline(const char* start) { return peek_linefeed(start ? start : position); diff --git a/parser.hpp b/parser.hpp index d2bbf1aabd..0ce4f7d525 100644 --- a/parser.hpp +++ b/parser.hpp @@ -57,6 +57,8 @@ namespace Sass { static Parser from_c_str(const char* src, Context& ctx, ParserState pstate = ParserState("[CSTRING]")); static Parser from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate = ParserState("[CSTRING]")); static Parser from_token(Token t, Context& ctx, ParserState pstate = ParserState("[TOKEN]")); + // special static parsers to convert strings into certain selectors + static Selector_List* parse_selector(const char* src, Context& ctx, ParserState pstate = ParserState("[SELECTOR]")); #ifdef __clang__ From f8074a0efe34eb3f4743f5171ed15dafa1ab0b1e Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Wed, 13 May 2015 01:34:18 +0200 Subject: [PATCH 2/2] Clean up white-space in code --- ast.cpp | 82 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/ast.cpp b/ast.cpp index 3347fd0361..7f7091e9fc 100644 --- a/ast.cpp +++ b/ast.cpp @@ -26,29 +26,29 @@ namespace Sass { } bool Complex_Selector::operator==(const Complex_Selector& rhs) const { - // TODO: We have to access the tail directly using tail_ since ADD_PROPERTY doesn't provide a const version. + // TODO: We have to access the tail directly using tail_ since ADD_PROPERTY doesn't provide a const version. - const Complex_Selector* pOne = this; + const Complex_Selector* pOne = this; const Complex_Selector* pTwo = &rhs; // Consume any empty references at the beginning of the Complex_Selector if (pOne->combinator() == Complex_Selector::ANCESTOR_OF && pOne->head()->is_empty_reference()) { - pOne = pOne->tail_; + pOne = pOne->tail_; } if (pTwo->combinator() == Complex_Selector::ANCESTOR_OF && pTwo->head()->is_empty_reference()) { - pTwo = pTwo->tail_; + pTwo = pTwo->tail_; } while (pOne && pTwo) { - if (pOne->combinator() != pTwo->combinator()) { - return false; + if (pOne->combinator() != pTwo->combinator()) { + return false; } if (*(pOne->head()) != *(pTwo->head())) { - return false; + return false; } - pOne = pOne->tail_; + pOne = pOne->tail_; pTwo = pTwo->tail_; } @@ -68,10 +68,10 @@ namespace Sass { bool Simple_Selector::operator==(const Simple_Selector& rhs) const { - // Compare the string representations for equality. + // Compare the string representations for equality. - // Cast away const here. To_String should take a const object, but it doesn't. - Simple_Selector* pLHS = const_cast(this); + // Cast away const here. To_String should take a const object, but it doesn't. + Simple_Selector* pLHS = const_cast(this); Simple_Selector* pRHS = const_cast(&rhs); To_String to_string; @@ -79,10 +79,10 @@ namespace Sass { } bool Simple_Selector::operator<(const Simple_Selector& rhs) const { - // Use the string representation for ordering. + // Use the string representation for ordering. - // Cast away const here. To_String should take a const object, but it doesn't. - Simple_Selector* pLHS = const_cast(this); + // Cast away const here. To_String should take a const object, but it doesn't. + Simple_Selector* pLHS = const_cast(this); Simple_Selector* pRHS = const_cast(&rhs); To_String to_string; @@ -217,25 +217,25 @@ namespace Sass { set lpsuedoset, rpsuedoset; for (size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->is_pseudo_element()) { - string pseudo((*this)[i]->perform(&to_string)); + if ((*this)[i]->is_pseudo_element()) { + string pseudo((*this)[i]->perform(&to_string)); pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving - lpsuedoset.insert(pseudo); + lpsuedoset.insert(pseudo); } } for (size_t i = 0, L = rhs->length(); i < L; ++i) { - if ((*rhs)[i]->is_pseudo_element()) { - string pseudo((*rhs)[i]->perform(&to_string)); + if ((*rhs)[i]->is_pseudo_element()) { + string pseudo((*rhs)[i]->perform(&to_string)); pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving - rpsuedoset.insert(pseudo); + rpsuedoset.insert(pseudo); } } - if (lpsuedoset != rpsuedoset) { + if (lpsuedoset != rpsuedoset) { return false; } - // Check the Simple_Selectors + // Check the Simple_Selectors set lset, rset; @@ -244,19 +244,17 @@ namespace Sass { for (size_t i = 0, L = length(); i < L; ++i) { Selector* lhs = (*this)[i]; + // very special case for wrapped matches selector if (Wrapped_Selector* wrapped = dynamic_cast(lhs)) { - if ( - wrapped->name() == ":matches(" || - wrapped->name() == ":-moz-any(" - ) { - lhs = wrapped->selector(); - if (Selector_List* list = dynamic_cast(lhs)) { - if (Compound_Selector* comp = dynamic_cast(rhs)) { - if (list->is_superselector_of(comp)) return true; + if (wrapped->name() == ":matches(" || wrapped->name() == ":-moz-any(") { + if (Selector_List* list = dynamic_cast(wrapped->selector())) { + if (Compound_Selector* comp = dynamic_cast(rhs)) { + if (list->is_superselector_of(comp)) return true; + } } } - } } + // match from here on as strings lset.insert(lhs->perform(&to_string)); } for (size_t i = 0, L = rhs->length(); i < L; ++i) @@ -290,33 +288,33 @@ namespace Sass { set lpsuedoset, rpsuedoset; for (size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->is_pseudo_element()) { - string pseudo((*this)[i]->perform(&to_string)); + if ((*this)[i]->is_pseudo_element()) { + string pseudo((*this)[i]->perform(&to_string)); pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving - lpsuedoset.insert(pseudo); + lpsuedoset.insert(pseudo); } } for (size_t i = 0, L = rhs.length(); i < L; ++i) { - if (rhs[i]->is_pseudo_element()) { - string pseudo(rhs[i]->perform(&to_string)); + if (rhs[i]->is_pseudo_element()) { + string pseudo(rhs[i]->perform(&to_string)); pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving - rpsuedoset.insert(pseudo); + rpsuedoset.insert(pseudo); } } - if (lpsuedoset != rpsuedoset) { + if (lpsuedoset != rpsuedoset) { return false; } - // Check the base + // Check the base const Simple_Selector* const lbase = base(); const Simple_Selector* const rbase = rhs.base(); if ((lbase && !rbase) || - (!lbase && rbase) || + (!lbase && rbase) || ((lbase && rbase) && (*lbase != *rbase))) { - return false; + return false; } @@ -482,7 +480,7 @@ namespace Sass { Complex_Selector* cpy = new (ctx.mem) Complex_Selector(*this); if (head()) { - cpy->head(head()->clone(ctx)); + cpy->head(head()->clone(ctx)); } if (tail()) {