Skip to content

Commit a6482aa

Browse files
committed
Merge pull request #1202 from mgreter/feature/selector-functions-basic
Implement `is_superselector` sass function
2 parents 28ee088 + f8074a0 commit a6482aa

File tree

7 files changed

+139
-39
lines changed

7 files changed

+139
-39
lines changed

ast.cpp

Lines changed: 102 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,29 @@ namespace Sass {
2626
}
2727

2828
bool Complex_Selector::operator==(const Complex_Selector& rhs) const {
29-
// TODO: We have to access the tail directly using tail_ since ADD_PROPERTY doesn't provide a const version.
29+
// TODO: We have to access the tail directly using tail_ since ADD_PROPERTY doesn't provide a const version.
3030

31-
const Complex_Selector* pOne = this;
31+
const Complex_Selector* pOne = this;
3232
const Complex_Selector* pTwo = &rhs;
3333

3434
// Consume any empty references at the beginning of the Complex_Selector
3535
if (pOne->combinator() == Complex_Selector::ANCESTOR_OF && pOne->head()->is_empty_reference()) {
36-
pOne = pOne->tail_;
36+
pOne = pOne->tail_;
3737
}
3838
if (pTwo->combinator() == Complex_Selector::ANCESTOR_OF && pTwo->head()->is_empty_reference()) {
39-
pTwo = pTwo->tail_;
39+
pTwo = pTwo->tail_;
4040
}
4141

4242
while (pOne && pTwo) {
43-
if (pOne->combinator() != pTwo->combinator()) {
44-
return false;
43+
if (pOne->combinator() != pTwo->combinator()) {
44+
return false;
4545
}
4646

4747
if (*(pOne->head()) != *(pTwo->head())) {
48-
return false;
48+
return false;
4949
}
5050

51-
pOne = pOne->tail_;
51+
pOne = pOne->tail_;
5252
pTwo = pTwo->tail_;
5353
}
5454

@@ -68,21 +68,21 @@ namespace Sass {
6868

6969
bool Simple_Selector::operator==(const Simple_Selector& rhs) const
7070
{
71-
// Compare the string representations for equality.
71+
// Compare the string representations for equality.
7272

73-
// Cast away const here. To_String should take a const object, but it doesn't.
74-
Simple_Selector* pLHS = const_cast<Simple_Selector*>(this);
73+
// Cast away const here. To_String should take a const object, but it doesn't.
74+
Simple_Selector* pLHS = const_cast<Simple_Selector*>(this);
7575
Simple_Selector* pRHS = const_cast<Simple_Selector*>(&rhs);
7676

7777
To_String to_string;
7878
return pLHS->perform(&to_string) == pRHS->perform(&to_string);
7979
}
8080

8181
bool Simple_Selector::operator<(const Simple_Selector& rhs) const {
82-
// Use the string representation for ordering.
82+
// Use the string representation for ordering.
8383

84-
// Cast away const here. To_String should take a const object, but it doesn't.
85-
Simple_Selector* pLHS = const_cast<Simple_Selector*>(this);
84+
// Cast away const here. To_String should take a const object, but it doesn't.
85+
Simple_Selector* pLHS = const_cast<Simple_Selector*>(this);
8686
Simple_Selector* pRHS = const_cast<Simple_Selector*>(&rhs);
8787

8888
To_String to_string;
@@ -217,32 +217,46 @@ namespace Sass {
217217
set<string> lpsuedoset, rpsuedoset;
218218
for (size_t i = 0, L = length(); i < L; ++i)
219219
{
220-
if ((*this)[i]->is_pseudo_element()) {
221-
string pseudo((*this)[i]->perform(&to_string));
220+
if ((*this)[i]->is_pseudo_element()) {
221+
string pseudo((*this)[i]->perform(&to_string));
222222
pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving
223-
lpsuedoset.insert(pseudo);
223+
lpsuedoset.insert(pseudo);
224224
}
225225
}
226226
for (size_t i = 0, L = rhs->length(); i < L; ++i)
227227
{
228-
if ((*rhs)[i]->is_pseudo_element()) {
229-
string pseudo((*rhs)[i]->perform(&to_string));
228+
if ((*rhs)[i]->is_pseudo_element()) {
229+
string pseudo((*rhs)[i]->perform(&to_string));
230230
pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving
231-
rpsuedoset.insert(pseudo);
231+
rpsuedoset.insert(pseudo);
232232
}
233233
}
234-
if (lpsuedoset != rpsuedoset) {
234+
if (lpsuedoset != rpsuedoset) {
235235
return false;
236236
}
237237

238-
// Check the Simple_Selectors
238+
// Check the Simple_Selectors
239239

240240
set<string> lset, rset;
241241

242242
if (!lbase) // no lbase; just see if the left-hand qualifiers are a subset of the right-hand selector
243243
{
244244
for (size_t i = 0, L = length(); i < L; ++i)
245-
{ lset.insert((*this)[i]->perform(&to_string)); }
245+
{
246+
Selector* lhs = (*this)[i];
247+
// very special case for wrapped matches selector
248+
if (Wrapped_Selector* wrapped = dynamic_cast<Wrapped_Selector*>(lhs)) {
249+
if (wrapped->name() == ":matches(" || wrapped->name() == ":-moz-any(") {
250+
if (Selector_List* list = dynamic_cast<Selector_List*>(wrapped->selector())) {
251+
if (Compound_Selector* comp = dynamic_cast<Compound_Selector*>(rhs)) {
252+
if (list->is_superselector_of(comp)) return true;
253+
}
254+
}
255+
}
256+
}
257+
// match from here on as strings
258+
lset.insert(lhs->perform(&to_string));
259+
}
246260
for (size_t i = 0, L = rhs->length(); i < L; ++i)
247261
{ rset.insert((*rhs)[i]->perform(&to_string)); }
248262
return includes(rset.begin(), rset.end(), lset.begin(), lset.end());
@@ -274,33 +288,33 @@ namespace Sass {
274288
set<string> lpsuedoset, rpsuedoset;
275289
for (size_t i = 0, L = length(); i < L; ++i)
276290
{
277-
if ((*this)[i]->is_pseudo_element()) {
278-
string pseudo((*this)[i]->perform(&to_string));
291+
if ((*this)[i]->is_pseudo_element()) {
292+
string pseudo((*this)[i]->perform(&to_string));
279293
pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving
280-
lpsuedoset.insert(pseudo);
294+
lpsuedoset.insert(pseudo);
281295
}
282296
}
283297
for (size_t i = 0, L = rhs.length(); i < L; ++i)
284298
{
285-
if (rhs[i]->is_pseudo_element()) {
286-
string pseudo(rhs[i]->perform(&to_string));
299+
if (rhs[i]->is_pseudo_element()) {
300+
string pseudo(rhs[i]->perform(&to_string));
287301
pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving
288-
rpsuedoset.insert(pseudo);
302+
rpsuedoset.insert(pseudo);
289303
}
290304
}
291-
if (lpsuedoset != rpsuedoset) {
305+
if (lpsuedoset != rpsuedoset) {
292306
return false;
293307
}
294308

295-
// Check the base
309+
// Check the base
296310

297311
const Simple_Selector* const lbase = base();
298312
const Simple_Selector* const rbase = rhs.base();
299313

300314
if ((lbase && !rbase) ||
301-
(!lbase && rbase) ||
315+
(!lbase && rbase) ||
302316
((lbase && rbase) && (*lbase != *rbase))) {
303-
return false;
317+
return false;
304318
}
305319

306320

@@ -326,8 +340,6 @@ namespace Sass {
326340

327341
bool Complex_Selector::is_superselector_of(Compound_Selector* rhs)
328342
{
329-
if (length() != 1)
330-
{ return false; }
331343
return base()->is_superselector_of(rhs);
332344
}
333345

@@ -352,6 +364,16 @@ namespace Sass {
352364
if (l_len == 1)
353365
{ return lhs->head()->is_superselector_of(rhs->base()); }
354366

367+
// we have to look one tail deeper, since we cary the
368+
// combinator around for it (which is important here)
369+
if (rhs->tail() && lhs->tail() && combinator() != Complex_Selector::ANCESTOR_OF) {
370+
Complex_Selector* lhs_tail = lhs->tail();
371+
Complex_Selector* rhs_tail = rhs->tail();
372+
if (lhs_tail->combinator() != rhs_tail->combinator()) return false;
373+
if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) return false;
374+
}
375+
376+
355377
bool found = false;
356378
Complex_Selector* marker = rhs;
357379
for (size_t i = 0, L = rhs->length(); i < L; ++i) {
@@ -458,7 +480,7 @@ namespace Sass {
458480
Complex_Selector* cpy = new (ctx.mem) Complex_Selector(*this);
459481

460482
if (head()) {
461-
cpy->head(head()->clone(ctx));
483+
cpy->head(head()->clone(ctx));
462484
}
463485

464486
if (tail()) {
@@ -493,6 +515,50 @@ namespace Sass {
493515
#endif
494516
}
495517

518+
// it's a superselector if every selector of the right side
519+
// list is a superselector of the given left side selector
520+
bool Complex_Selector::is_superselector_of(Selector_List *sub)
521+
{
522+
// Check every rhs selector against left hand list
523+
for(size_t i = 0, L = sub->length(); i < L; ++i) {
524+
if (!is_superselector_of((*sub)[i])) return false;
525+
}
526+
return true;
527+
}
528+
529+
// it's a superselector if every selector of the right side
530+
// list is a superselector of the given left side selector
531+
bool Selector_List::is_superselector_of(Selector_List *sub)
532+
{
533+
// Check every rhs selector against left hand list
534+
for(size_t i = 0, L = sub->length(); i < L; ++i) {
535+
if (!is_superselector_of((*sub)[i])) return false;
536+
}
537+
return true;
538+
}
539+
540+
// it's a superselector if every selector on the right side
541+
// is a superselector of any one of the left side selectors
542+
bool Selector_List::is_superselector_of(Compound_Selector *sub)
543+
{
544+
// Check every lhs selector against right hand
545+
for(size_t i = 0, L = length(); i < L; ++i) {
546+
if ((*this)[i]->is_superselector_of(sub)) return true;
547+
}
548+
return false;
549+
}
550+
551+
// it's a superselector if every selector on the right side
552+
// is a superselector of any one of the left side selectors
553+
bool Selector_List::is_superselector_of(Complex_Selector *sub)
554+
{
555+
// Check every lhs selector against right hand
556+
for(size_t i = 0, L = length(); i < L; ++i) {
557+
if ((*this)[i]->is_superselector_of(sub)) return true;
558+
}
559+
return false;
560+
}
561+
496562
/* not used anymore - remove?
497563
Selector_Placeholder* Selector_List::find_placeholder()
498564
{

ast.hpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,7 +1943,9 @@ namespace Sass {
19431943
return (*this)[0];
19441944
return 0;
19451945
}
1946-
bool is_superselector_of(Compound_Selector* rhs);
1946+
bool is_superselector_of(Compound_Selector* sub);
1947+
// bool is_superselector_of(Complex_Selector* sub);
1948+
// bool is_superselector_of(Selector_List* sub);
19471949
virtual unsigned long specificity()
19481950
{
19491951
int sum = 0;
@@ -2000,8 +2002,9 @@ namespace Sass {
20002002
Complex_Selector* context(Context&);
20012003
Complex_Selector* innermost();
20022004
size_t length();
2003-
bool is_superselector_of(Compound_Selector*);
2004-
bool is_superselector_of(Complex_Selector*);
2005+
bool is_superselector_of(Compound_Selector* sub);
2006+
bool is_superselector_of(Complex_Selector* sub);
2007+
bool is_superselector_of(Selector_List* sub);
20052008
// virtual Selector_Placeholder* find_placeholder();
20062009
Combinator clear_innermost();
20072010
void set_innermost(Complex_Selector*, Combinator);
@@ -2086,6 +2089,9 @@ namespace Sass {
20862089
: Selector(pstate), Vectorized<Complex_Selector*>(s), wspace_(0)
20872090
{ }
20882091
// virtual Selector_Placeholder* find_placeholder();
2092+
bool is_superselector_of(Compound_Selector* sub);
2093+
bool is_superselector_of(Complex_Selector* sub);
2094+
bool is_superselector_of(Selector_List* sub);
20892095
virtual unsigned long specificity()
20902096
{
20912097
unsigned long sum = 0;

context.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,8 @@ namespace Sass {
539539
// Misc Functions
540540
register_function(ctx, inspect_sig, inspect, env);
541541
register_function(ctx, unique_id_sig, unique_id, env);
542+
// Selector functions
543+
register_function(ctx, is_superselector_sig, is_superselector, env);
542544
}
543545

544546
void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs)

functions.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,6 +1564,20 @@ namespace Sass {
15641564
// return v;
15651565
}
15661566

1567+
Signature is_superselector_sig = "is-superselector($super, $sub)";
1568+
BUILT_IN(is_superselector)
1569+
{
1570+
To_String to_string(&ctx, false);
1571+
Expression* ex_sup = ARG("$super", Expression);
1572+
Expression* ex_sub = ARG("$sub", Expression);
1573+
string sup_src = ex_sup->perform(&to_string) + "{";
1574+
string sub_src = ex_sub->perform(&to_string) + "{";
1575+
Selector_List* sel_sup = Parser::parse_selector(sup_src.c_str(), ctx);
1576+
Selector_List* sel_sub = Parser::parse_selector(sub_src.c_str(), ctx);
1577+
bool result = sel_sup->is_superselector_of(sel_sub);
1578+
return new (ctx.mem) Boolean(pstate, result);
1579+
}
1580+
15671581
Signature unique_id_sig = "unique-id()";
15681582
BUILT_IN(unique_id)
15691583
{

functions.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ namespace Sass {
101101
extern Signature keywords_sig;
102102
extern Signature set_nth_sig;
103103
extern Signature unique_id_sig;
104+
extern Signature is_superselector_sig;
104105

105106
BUILT_IN(rgb);
106107
BUILT_IN(rgba_4);
@@ -175,6 +176,7 @@ namespace Sass {
175176
BUILT_IN(keywords);
176177
BUILT_IN(set_nth);
177178
BUILT_IN(unique_id);
179+
BUILT_IN(is_superselector);
178180

179181
}
180182
}

parser.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ namespace Sass {
4040
return p;
4141
}
4242

43+
Selector_List* Parser::parse_selector(const char* src, Context& ctx, ParserState pstate)
44+
{
45+
Parser p = Parser::from_c_str(src, ctx, pstate);
46+
// ToDo: ruby sass errors on parent references
47+
// ToDo: remap the source-map entries somehow
48+
return p.parse_selector_group();
49+
}
50+
4351
bool Parser::peek_newline(const char* start)
4452
{
4553
return peek_linefeed(start ? start : position);

parser.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ namespace Sass {
5757
static Parser from_c_str(const char* src, Context& ctx, ParserState pstate = ParserState("[CSTRING]"));
5858
static Parser from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate = ParserState("[CSTRING]"));
5959
static Parser from_token(Token t, Context& ctx, ParserState pstate = ParserState("[TOKEN]"));
60+
// special static parsers to convert strings into certain selectors
61+
static Selector_List* parse_selector(const char* src, Context& ctx, ParserState pstate = ParserState("[SELECTOR]"));
6062

6163
#ifdef __clang__
6264

0 commit comments

Comments
 (0)