Skip to content

Commit 35db19c

Browse files
committed
Fix @extend specificity problems
Fixes #592
1 parent fd7f20f commit 35db19c

File tree

5 files changed

+105
-46
lines changed

5 files changed

+105
-46
lines changed

ast.hpp

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1742,7 +1742,9 @@ namespace Sass {
17421742
{ }
17431743
virtual ~Selector() = 0;
17441744
// virtual Selector_Placeholder* find_placeholder();
1745-
virtual int specificity() { return Constants::SPECIFICITY_BASE; }
1745+
virtual unsigned long specificity() {
1746+
return Constants::Specificity_Universal;
1747+
};
17461748
};
17471749
inline Selector::~Selector() { }
17481750

@@ -1770,6 +1772,7 @@ namespace Sass {
17701772
virtual ~Simple_Selector() = 0;
17711773
virtual Compound_Selector* unify_with(Compound_Selector*, Context&);
17721774
virtual bool is_pseudo_element() { return false; }
1775+
virtual bool is_pseudo_class() { return false; }
17731776

17741777
bool operator==(const Simple_Selector& rhs) const;
17751778
inline bool operator!=(const Simple_Selector& rhs) const { return !(*this == rhs); }
@@ -1787,10 +1790,10 @@ namespace Sass {
17871790
Selector_Reference(ParserState pstate, Selector* r = 0)
17881791
: Simple_Selector(pstate), selector_(r)
17891792
{ has_reference(true); }
1790-
virtual int specificity()
1793+
virtual unsigned long specificity()
17911794
{
1792-
if (selector()) return selector()->specificity();
1793-
else return 0;
1795+
if (!selector()) return 0;
1796+
return selector()->specificity();
17941797
}
17951798
ATTACH_OPERATIONS();
17961799
};
@@ -1817,10 +1820,11 @@ namespace Sass {
18171820
Type_Selector(ParserState pstate, string n)
18181821
: Simple_Selector(pstate), name_(n)
18191822
{ }
1820-
virtual int specificity()
1823+
virtual unsigned long specificity()
18211824
{
1822-
if (name() == "*") return 0;
1823-
else return 1;
1825+
// ToDo: What is the specificity of the star selector?
1826+
if (name() == "*") return Constants::Specificity_Universal;
1827+
else return Constants::Specificity_Type;
18241828
}
18251829
virtual Compound_Selector* unify_with(Compound_Selector*, Context&);
18261830
ATTACH_OPERATIONS();
@@ -1835,10 +1839,11 @@ namespace Sass {
18351839
Selector_Qualifier(ParserState pstate, string n)
18361840
: Simple_Selector(pstate), name_(n)
18371841
{ }
1838-
virtual int specificity()
1842+
virtual unsigned long specificity()
18391843
{
1840-
if (name()[0] == '#') return Constants::SPECIFICITY_BASE * Constants::SPECIFICITY_BASE;
1841-
else return Constants::SPECIFICITY_BASE;
1844+
if (name()[0] == '#') return Constants::Specificity_ID;
1845+
if (name()[0] == '.') return Constants::Specificity_Class;
1846+
else return Constants::Specificity_Type;
18421847
}
18431848
virtual Compound_Selector* unify_with(Compound_Selector*, Context&);
18441849
ATTACH_OPERATIONS();
@@ -1855,42 +1860,62 @@ namespace Sass {
18551860
Attribute_Selector(ParserState pstate, string n, string m, String* v)
18561861
: Simple_Selector(pstate), name_(n), matcher_(m), value_(v)
18571862
{ }
1863+
virtual unsigned long specificity()
1864+
{
1865+
return Constants::Specificity_Attr;
1866+
}
18581867
ATTACH_OPERATIONS();
18591868
};
18601869

18611870
//////////////////////////////////////////////////////////////////
18621871
// Pseudo selectors -- e.g., :first-child, :nth-of-type(...), etc.
18631872
//////////////////////////////////////////////////////////////////
1873+
/* '::' starts a pseudo-element, ':' a pseudo-class */
1874+
/* Except :first-line, :first-letter, :before and :after */
1875+
/* Note that pseudo-elements are restricted to one per selector */
1876+
/* and occur only in the last simple_selector_sequence. */
1877+
inline bool is_pseudo_class_element(const string& name)
1878+
{
1879+
return name == ":before" ||
1880+
name == ":after" ||
1881+
name == ":first-line" ||
1882+
name == ":first-letter";
1883+
}
1884+
18641885
class Pseudo_Selector : public Simple_Selector {
18651886
ADD_PROPERTY(string, name);
18661887
ADD_PROPERTY(String*, expression);
18671888
public:
18681889
Pseudo_Selector(ParserState pstate, string n, String* expr = 0)
18691890
: Simple_Selector(pstate), name_(n), expression_(expr)
18701891
{ }
1871-
virtual int specificity()
1892+
1893+
// A pseudo-class always consists of a "colon" (:) followed by the name
1894+
// of the pseudo-class and optionally by a value between parentheses.
1895+
virtual bool is_pseudo_class()
18721896
{
1873-
// TODO: clean up the pseudo-element checking
1874-
if (name() == ":before" || name() == "::before" ||
1875-
name() == ":after" || name() == "::after" ||
1876-
name() == ":first-line" || name() == "::first-line" ||
1877-
name() == ":first-letter" || name() == "::first-letter")
1878-
return 1;
1879-
else
1880-
return Constants::SPECIFICITY_BASE;
1897+
return (name_[0] == ':' && name_[1] != ':')
1898+
&& ! is_pseudo_class_element(name_);
18811899
}
1900+
1901+
// A pseudo-element is made of two colons (::) followed by the name.
1902+
// The `::` notation is introduced by the current document in order to
1903+
// establish a discrimination between pseudo-classes and pseudo-elements.
1904+
// For compatibility with existing style sheets, user agents must also
1905+
// accept the previous one-colon notation for pseudo-elements introduced
1906+
// in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and
1907+
// :after). This compatibility is not allowed for the new pseudo-elements
1908+
// introduced in this specification.
18821909
virtual bool is_pseudo_element()
18831910
{
1884-
if (name() == ":before" || name() == "::before" ||
1885-
name() == ":after" || name() == "::after" ||
1886-
name() == ":first-line" || name() == "::first-line" ||
1887-
name() == ":first-letter" || name() == "::first-letter") {
1888-
return true;
1889-
}
1890-
else {
1891-
// If it's not a known pseudo-element, check whether it looks like one. This is similar to the type method on the Pseudo class in ruby sass.
1892-
return name().find("::") == 0;
1893-
}
1911+
return (name_[0] == ':' && name_[1] == ':')
1912+
|| is_pseudo_class_element(name_);
1913+
}
1914+
virtual unsigned long specificity()
1915+
{
1916+
if (is_pseudo_element())
1917+
return Constants::Specificity_Type;
1918+
return Constants::Specificity_Pseudo;
18941919
}
18951920
virtual Compound_Selector* unify_with(Compound_Selector*, Context&);
18961921
ATTACH_OPERATIONS();
@@ -1906,6 +1931,12 @@ namespace Sass {
19061931
Wrapped_Selector(ParserState pstate, string n, Selector* sel)
19071932
: Simple_Selector(pstate), name_(n), selector_(sel)
19081933
{ }
1934+
// Selectors inside the negation pseudo-class are counted like any
1935+
// other, but the negation itself does not count as a pseudo-class.
1936+
virtual unsigned long specificity()
1937+
{
1938+
return selector_ ? selector_->specificity() : 0;
1939+
}
19091940
ATTACH_OPERATIONS();
19101941
};
19111942

@@ -1946,7 +1977,7 @@ namespace Sass {
19461977
return 0;
19471978
}
19481979
bool is_superselector_of(Compound_Selector* rhs);
1949-
virtual int specificity()
1980+
virtual unsigned long specificity()
19501981
{
19511982
int sum = 0;
19521983
for (size_t i = 0, L = length(); i < L; ++i)
@@ -2007,7 +2038,7 @@ namespace Sass {
20072038
// virtual Selector_Placeholder* find_placeholder();
20082039
Combinator clear_innermost();
20092040
void set_innermost(Complex_Selector*, Combinator);
2010-
virtual int specificity() const
2041+
virtual unsigned long specificity() const
20112042
{
20122043
int sum = 0;
20132044
if (head()) sum += head()->specificity();
@@ -2088,9 +2119,9 @@ namespace Sass {
20882119
: Selector(pstate), Vectorized<Complex_Selector*>(s), wspace_(0)
20892120
{ }
20902121
// virtual Selector_Placeholder* find_placeholder();
2091-
virtual int specificity()
2122+
virtual unsigned long specificity()
20922123
{
2093-
int sum = 0;
2124+
unsigned long sum = 0;
20942125
for (size_t i = 0, L = length(); i < L; ++i)
20952126
{ sum += (*this)[i]->specificity(); }
20962127
return sum;

constants.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22

33
namespace Sass {
44
namespace Constants {
5-
extern const int SPECIFICITY_BASE = 1000;
5+
6+
// https://github.com/sass/libsass/issues/592
7+
// https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity
8+
// https://github.com/sass/sass/issues/1495#issuecomment-61189114
9+
extern const unsigned long Specificity_Star = 0;
10+
extern const unsigned long Specificity_Universal = 1 << 0;
11+
extern const unsigned long Specificity_Type = 1 << 8;
12+
extern const unsigned long Specificity_Class = 1 << 16;
13+
extern const unsigned long Specificity_Attr = 1 << 16;
14+
extern const unsigned long Specificity_Pseudo = 1 << 16;
15+
extern const unsigned long Specificity_ID = 1 << 24;
616

717
// sass keywords
818
extern const char at_root_kwd[] = "@at-root";

constants.hpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@
33

44
namespace Sass {
55
namespace Constants {
6-
extern const int SPECIFICITY_BASE;
6+
7+
// https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity
8+
// The following list of selectors is by increasing specificity:
9+
extern const unsigned long Specificity_Star;
10+
extern const unsigned long Specificity_Universal;
11+
extern const unsigned long Specificity_Type;
12+
extern const unsigned long Specificity_Class;
13+
extern const unsigned long Specificity_Attr;
14+
extern const unsigned long Specificity_Pseudo;
15+
extern const unsigned long Specificity_ID;
716

817
// sass keywords
918
extern const char at_root_kwd[];

debugger.hpp

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define SASS_DEBUGGER_H
33

44
#include <string>
5+
#include <sstream>
56
#include "ast_fwd_decl.hpp"
67

78
using namespace std;
@@ -25,9 +26,15 @@ inline string prettyprint(const string& str) {
2526
return clean;
2627
}
2728

29+
inline string longToHex(long long t) {
30+
std::stringstream is;
31+
is << std::hex << t;
32+
return is.str();
33+
}
34+
2835
inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0)
2936
{
30-
37+
if (node == 0) return;
3138
if (ind == "") cerr << "####################################################################\n";
3239
if (dynamic_cast<Bubble*>(node)) {
3340
Bubble* bubble = dynamic_cast<Bubble*>(node);
@@ -64,6 +71,7 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0)
6471
Complex_Selector* selector = dynamic_cast<Complex_Selector*>(node);
6572
cerr << ind << "Complex_Selector " << selector
6673
<< " [block:" << selector->last_block() << "]"
74+
<< " [weight:" << longToHex(selector->specificity()) << "]"
6775
<< (selector->last_block() && selector->last_block()->is_root() ? " [root]" : "")
6876
<< " [@media:" << selector->media_block() << "]"
6977
<< (selector->is_optional() ? " [is_optional]": " -")
@@ -80,14 +88,15 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0)
8088
debug_ast(selector->tail(), ind + "-", env);
8189
} else if (dynamic_cast<Compound_Selector*>(node)) {
8290
Compound_Selector* selector = dynamic_cast<Compound_Selector*>(node);
83-
cerr << ind << "Compound_Selector " << selector
84-
<< " [block:" << selector->last_block() << "]"
85-
<< (selector->last_block() && selector->last_block()->is_root() ? " [root]" : "")
86-
<< " [@media:" << selector->media_block() << "]"
87-
<< (selector->is_optional() ? " [is_optional]": " -")
88-
<< (selector->has_line_break() ? " [line-break]": " -")
89-
<< (selector->has_line_feed() ? " [line-feed]": " -") <<
90-
" <" << prettyprint(selector->pstate().token.ws_before()) << ">" << endl;
91+
cerr << ind << "Compound_Selector " << selector;
92+
cerr << " [block:" << selector->last_block() << "]";
93+
cerr << " [weight:" << longToHex(selector->specificity()) << "]";
94+
// cerr << (selector->last_block() && selector->last_block()->is_root() ? " [root]" : "");
95+
cerr << " [@media:" << selector->media_block() << "]";
96+
cerr << (selector->is_optional() ? " [is_optional]": " -");
97+
cerr << (selector->has_line_break() ? " [line-break]": " -");
98+
cerr << (selector->has_line_feed() ? " [line-feed]": " -");
99+
cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << endl;
91100
for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); }
92101
} else if (dynamic_cast<Propset*>(node)) {
93102
Propset* selector = dynamic_cast<Propset*>(node);

extend.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,14 +517,14 @@ namespace Sass {
517517
// had an extra source that the ruby version did not have. Without a failing test case, this is going to be extra hard to find. My
518518
// best guess at this point is that we're cloning an object somewhere and maintaining the sources when we shouldn't be. This is purely
519519
// a guess though.
520-
int maxSpecificity = 0;
520+
unsigned long maxSpecificity = 0;
521521
SourcesSet sources = pSeq1->sources();
522522

523523
DEBUG_PRINTLN(TRIM, "TRIMASDF SEQ1: " << seq1)
524524
DEBUG_EXEC(TRIM, printSourcesSet(sources, ctx, "TRIMASDF SOURCES: "))
525525

526526
for (SourcesSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) {
527-
const Complex_Selector* const pCurrentSelector = *sourcesSetIterator;
527+
const Complex_Selector* const pCurrentSelector = *sourcesSetIterator;
528528
maxSpecificity = max(maxSpecificity, pCurrentSelector->specificity());
529529
}
530530

0 commit comments

Comments
 (0)