sass 3.3.14 → 3.4.0.rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +5 -5
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/VERSION_NAME +1 -1
- data/bin/sass +1 -1
- data/bin/scss +1 -1
- data/lib/sass.rb +0 -5
- data/lib/sass/css.rb +1 -3
- data/lib/sass/engine.rb +28 -39
- data/lib/sass/environment.rb +13 -17
- data/lib/sass/error.rb +6 -9
- data/lib/sass/exec.rb +5 -771
- data/lib/sass/exec/base.rb +187 -0
- data/lib/sass/exec/sass_convert.rb +264 -0
- data/lib/sass/exec/sass_scss.rb +419 -0
- data/lib/sass/features.rb +6 -0
- data/lib/sass/importers.rb +0 -1
- data/lib/sass/importers/base.rb +5 -1
- data/lib/sass/importers/filesystem.rb +4 -21
- data/lib/sass/media.rb +1 -4
- data/lib/sass/plugin/compiler.rb +32 -136
- data/lib/sass/script/css_lexer.rb +1 -1
- data/lib/sass/script/functions.rb +363 -39
- data/lib/sass/script/lexer.rb +68 -50
- data/lib/sass/script/parser.rb +29 -14
- data/lib/sass/script/tree.rb +1 -0
- data/lib/sass/script/tree/funcall.rb +1 -1
- data/lib/sass/script/tree/interpolation.rb +19 -1
- data/lib/sass/script/tree/selector.rb +26 -0
- data/lib/sass/script/value.rb +0 -1
- data/lib/sass/script/value/bool.rb +0 -5
- data/lib/sass/script/value/color.rb +32 -12
- data/lib/sass/script/value/helpers.rb +107 -0
- data/lib/sass/script/value/list.rb +0 -15
- data/lib/sass/script/value/null.rb +0 -5
- data/lib/sass/script/value/number.rb +60 -14
- data/lib/sass/script/value/string.rb +53 -9
- data/lib/sass/scss/css_parser.rb +8 -2
- data/lib/sass/scss/parser.rb +175 -319
- data/lib/sass/scss/rx.rb +14 -5
- data/lib/sass/scss/static_parser.rb +298 -1
- data/lib/sass/selector.rb +56 -193
- data/lib/sass/selector/abstract_sequence.rb +28 -13
- data/lib/sass/selector/comma_sequence.rb +91 -12
- data/lib/sass/selector/pseudo.rb +256 -0
- data/lib/sass/selector/sequence.rb +99 -31
- data/lib/sass/selector/simple.rb +14 -25
- data/lib/sass/selector/simple_sequence.rb +101 -37
- data/lib/sass/shared.rb +1 -1
- data/lib/sass/source/map.rb +23 -9
- data/lib/sass/stack.rb +0 -6
- data/lib/sass/supports.rb +1 -1
- data/lib/sass/tree/at_root_node.rb +1 -0
- data/lib/sass/tree/directive_node.rb +7 -1
- data/lib/sass/tree/error_node.rb +18 -0
- data/lib/sass/tree/keyframe_rule_node.rb +15 -0
- data/lib/sass/tree/prop_node.rb +1 -1
- data/lib/sass/tree/rule_node.rb +11 -6
- data/lib/sass/tree/visitors/check_nesting.rb +3 -4
- data/lib/sass/tree/visitors/convert.rb +8 -17
- data/lib/sass/tree/visitors/cssize.rb +12 -24
- data/lib/sass/tree/visitors/deep_copy.rb +5 -0
- data/lib/sass/tree/visitors/perform.rb +43 -28
- data/lib/sass/tree/visitors/set_options.rb +5 -0
- data/lib/sass/tree/visitors/to_css.rb +14 -13
- data/lib/sass/util.rb +94 -90
- data/test/sass/cache_test.rb +1 -1
- data/test/sass/callbacks_test.rb +1 -1
- data/test/sass/compiler_test.rb +5 -14
- data/test/sass/conversion_test.rb +47 -1
- data/test/sass/css2sass_test.rb +3 -3
- data/test/sass/encoding_test.rb +219 -0
- data/test/sass/engine_test.rb +128 -191
- data/test/sass/exec_test.rb +2 -2
- data/test/sass/extend_test.rb +234 -17
- data/test/sass/functions_test.rb +268 -213
- data/test/sass/importer_test.rb +31 -21
- data/test/sass/logger_test.rb +1 -1
- data/test/sass/more_results/more_import.css +1 -1
- data/test/sass/plugin_test.rb +12 -11
- data/test/sass/results/compact.css +1 -1
- data/test/sass/results/complex.css +4 -4
- data/test/sass/results/expanded.css +1 -1
- data/test/sass/results/import.css +1 -1
- data/test/sass/results/import_charset_ibm866.css +2 -2
- data/test/sass/results/mixins.css +17 -17
- data/test/sass/results/nested.css +1 -1
- data/test/sass/results/parent_ref.css +2 -2
- data/test/sass/results/script.css +3 -3
- data/test/sass/results/scss_import.css +1 -1
- data/test/sass/script_conversion_test.rb +7 -4
- data/test/sass/script_test.rb +202 -79
- data/test/sass/scss/css_test.rb +95 -25
- data/test/sass/scss/rx_test.rb +4 -4
- data/test/sass/scss/scss_test.rb +363 -19
- data/test/sass/source_map_test.rb +48 -41
- data/test/sass/superselector_test.rb +191 -0
- data/test/sass/templates/scss_import.scss +2 -1
- data/test/sass/test_helper.rb +1 -1
- data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
- data/test/sass/util/normalized_map_test.rb +1 -1
- data/test/sass/util/subset_map_test.rb +2 -2
- data/test/sass/util_test.rb +1 -1
- data/test/sass/value_helpers_test.rb +3 -3
- data/test/test_helper.rb +2 -2
- metadata +30 -7
- data/lib/sass/importers/deprecated_path.rb +0 -51
- data/lib/sass/script/value/deprecated_false.rb +0 -55
@@ -3,8 +3,8 @@ module Sass
|
|
3
3
|
# The abstract parent class of the various selector sequence classes.
|
4
4
|
#
|
5
5
|
# All subclasses should implement a `members` method that returns an array
|
6
|
-
# of object that respond to `#line=` and `#filename=`, as well as a `
|
7
|
-
# method that returns
|
6
|
+
# of object that respond to `#line=` and `#filename=`, as well as a `to_s`
|
7
|
+
# method that returns the string representation of the selector.
|
8
8
|
class AbstractSequence
|
9
9
|
# The line of the Sass template on which this selector was declared.
|
10
10
|
#
|
@@ -62,22 +62,26 @@ module Sass
|
|
62
62
|
# Whether or not this selector sequence contains a placeholder selector.
|
63
63
|
# Checks recursively.
|
64
64
|
def has_placeholder?
|
65
|
-
@has_placeholder ||=
|
66
|
-
|
65
|
+
@has_placeholder ||= members.any? do |m|
|
66
|
+
next m.has_placeholder? if m.is_a?(AbstractSequence)
|
67
|
+
next m.selector && m.selector.has_placeholder? if m.is_a?(Pseudo)
|
68
|
+
m.is_a?(Placeholder)
|
69
|
+
end
|
67
70
|
end
|
68
71
|
|
69
|
-
#
|
70
|
-
# string, along with any SassScript interpolation that may exist.
|
72
|
+
# Returns the selector string.
|
71
73
|
#
|
72
74
|
# @return [String]
|
73
75
|
def to_s
|
74
|
-
|
76
|
+
Sass::Util.abstract(self)
|
75
77
|
end
|
76
78
|
|
77
|
-
# Returns the specificity of the selector
|
78
|
-
# by {Sass::Selector::SPECIFICITY_BASE}.
|
79
|
+
# Returns the specificity of the selector.
|
79
80
|
#
|
80
|
-
#
|
81
|
+
# The base is given by {Sass::Selector::SPECIFICITY_BASE}. This can be a
|
82
|
+
# number or a range representing possible specificities.
|
83
|
+
#
|
84
|
+
# @return [Fixnum, Range]
|
81
85
|
def specificity
|
82
86
|
_specificity(members)
|
83
87
|
end
|
@@ -85,9 +89,20 @@ module Sass
|
|
85
89
|
protected
|
86
90
|
|
87
91
|
def _specificity(arr)
|
88
|
-
|
89
|
-
|
90
|
-
|
92
|
+
min = 0
|
93
|
+
max = 0
|
94
|
+
arr.each do |m|
|
95
|
+
next if m.is_a?(String)
|
96
|
+
spec = m.specificity
|
97
|
+
if spec.is_a?(Range)
|
98
|
+
min += spec.begin
|
99
|
+
max += spec.end
|
100
|
+
else
|
101
|
+
min += spec
|
102
|
+
max += spec
|
103
|
+
end
|
104
|
+
end
|
105
|
+
min == max ? min : (min..max)
|
91
106
|
end
|
92
107
|
end
|
93
108
|
end
|
@@ -53,20 +53,100 @@ module Sass
|
|
53
53
|
# The extensions to perform on this selector
|
54
54
|
# @param parent_directives [Array<Sass::Tree::DirectiveNode>]
|
55
55
|
# The directives containing this selector.
|
56
|
+
# @param replace [Boolean]
|
57
|
+
# Whether to replace the original selector entirely or include
|
58
|
+
# it in the result.
|
59
|
+
# @param seen [Set<Array<Selector::Simple>>]
|
60
|
+
# The set of simple sequences that are currently being replaced.
|
61
|
+
# @param original [Boolean]
|
62
|
+
# Whether this is the original selector being extended, as opposed to
|
63
|
+
# the result of a previous extension that's being re-extended.
|
56
64
|
# @return [CommaSequence] A copy of this selector,
|
57
65
|
# with extensions made according to `extends`
|
58
|
-
def do_extend(extends, parent_directives
|
66
|
+
def do_extend(extends, parent_directives = [], replace = false, seen = Set.new,
|
67
|
+
original = true)
|
59
68
|
CommaSequence.new(members.map do |seq|
|
60
|
-
|
61
|
-
# First Law of Extend: the result of extending a selector should
|
62
|
-
# always contain the base selector.
|
63
|
-
#
|
64
|
-
# See https://github.com/nex3/sass/issues/324.
|
65
|
-
extended.unshift seq unless seq.has_placeholder? || extended.include?(seq)
|
66
|
-
extended
|
69
|
+
seq.do_extend(extends, parent_directives, replace, seen, original)
|
67
70
|
end.flatten)
|
68
71
|
end
|
69
72
|
|
73
|
+
# Returns whether or not this selector matches all elements
|
74
|
+
# that the given selector matches (as well as possibly more).
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# (.foo).superselector?(.foo.bar) #=> true
|
78
|
+
# (.foo).superselector?(.bar) #=> false
|
79
|
+
# @param cseq [CommaSequence]
|
80
|
+
# @return [Boolean]
|
81
|
+
def superselector?(cseq)
|
82
|
+
cseq.members.all? {|seq1| members.any? {|seq2| seq2.superselector?(seq1)}}
|
83
|
+
end
|
84
|
+
|
85
|
+
# Populates a subset map that can then be used to extend
|
86
|
+
# selectors. This registers an extension with this selector as
|
87
|
+
# the extender and `extendee` as the extendee.
|
88
|
+
#
|
89
|
+
# @param extends [Sass::Util::SubsetMap{Selector::Simple =>
|
90
|
+
# Sass::Tree::Visitors::Cssize::Extend}]
|
91
|
+
# The subset map representing the extensions to perform.
|
92
|
+
# @param extendee [CommaSequence] The selector being extended.
|
93
|
+
# @param extend_node [Sass::Tree::ExtendNode]
|
94
|
+
# The node that caused this extension.
|
95
|
+
# @param parent_directives [Array<Sass::Tree::DirectiveNode>]
|
96
|
+
# The parent directives containing `extend_node`.
|
97
|
+
# @raise [Sass::SyntaxError] if this extension is invalid.
|
98
|
+
def populate_extends(extends, extendee, extend_node = nil, parent_directives = [])
|
99
|
+
extendee.members.each do |seq|
|
100
|
+
if seq.members.size > 1
|
101
|
+
raise Sass::SyntaxError.new("Can't extend #{seq}: can't extend nested selectors")
|
102
|
+
end
|
103
|
+
|
104
|
+
sseq = seq.members.first
|
105
|
+
if !sseq.is_a?(Sass::Selector::SimpleSequence)
|
106
|
+
raise Sass::SyntaxError.new("Can't extend #{seq}: invalid selector")
|
107
|
+
elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)}
|
108
|
+
raise Sass::SyntaxError.new("Can't extend #{seq}: can't extend parent selectors")
|
109
|
+
end
|
110
|
+
|
111
|
+
sel = sseq.members
|
112
|
+
members.each do |member|
|
113
|
+
unless member.members.last.is_a?(Sass::Selector::SimpleSequence)
|
114
|
+
raise Sass::SyntaxError.new("#{member} can't extend: invalid selector")
|
115
|
+
end
|
116
|
+
|
117
|
+
extends[sel] = Sass::Tree::Visitors::Cssize::Extend.new(
|
118
|
+
member, sel, extend_node, parent_directives, :not_found)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Unifies this with another comma selector to produce a selector
|
124
|
+
# that matches (a subset of) the intersection of the two inputs.
|
125
|
+
#
|
126
|
+
# @param other [CommaSequence]
|
127
|
+
# @return [CommaSequence, nil] The unified selector, or nil if unification failed.
|
128
|
+
# @raise [Sass::SyntaxError] If this selector cannot be unified.
|
129
|
+
# This will only ever occur when a dynamic selector,
|
130
|
+
# such as {Parent} or {Interpolation}, is used in unification.
|
131
|
+
# Since these selectors should be resolved
|
132
|
+
# by the time extension and unification happen,
|
133
|
+
# this exception will only ever be raised as a result of programmer error
|
134
|
+
def unify(other)
|
135
|
+
results = members.map {|seq1| other.members.map {|seq2| seq1.unify(seq2)}}.flatten.compact
|
136
|
+
results.empty? ? nil : CommaSequence.new(results.map {|cseq| cseq.members}.flatten)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns a SassScript representation of this selector.
|
140
|
+
#
|
141
|
+
# @return [Sass::Script::Value::List]
|
142
|
+
def to_sass_script
|
143
|
+
Sass::Script::Value::List.new(members.map do |seq|
|
144
|
+
Sass::Script::Value::List.new(seq.members.map do |component|
|
145
|
+
Sass::Script::Value::String.new(component.to_s)
|
146
|
+
end, :space)
|
147
|
+
end, :comma)
|
148
|
+
end
|
149
|
+
|
70
150
|
# Returns a string representation of the sequence.
|
71
151
|
# This is basically the selector string.
|
72
152
|
#
|
@@ -75,10 +155,9 @@ module Sass
|
|
75
155
|
members.map {|m| m.inspect}.join(", ")
|
76
156
|
end
|
77
157
|
|
78
|
-
# @see
|
79
|
-
def
|
80
|
-
|
81
|
-
Sass::Util.replace_subseq(arr, [", ", "\n"], [",\n"])
|
158
|
+
# @see AbstractSequence#to_s
|
159
|
+
def to_s
|
160
|
+
@members.join(", ").gsub(", \n", ",\n")
|
82
161
|
end
|
83
162
|
|
84
163
|
private
|
@@ -0,0 +1,256 @@
|
|
1
|
+
module Sass
|
2
|
+
module Selector
|
3
|
+
# A pseudoclass (e.g. `:visited`) or pseudoelement (e.g. `::first-line`)
|
4
|
+
# selector. It can have arguments (e.g. `:nth-child(2n+1)`) which can
|
5
|
+
# contain selectors (e.g. `:nth-child(2n+1 of .foo)`).
|
6
|
+
class Pseudo < Simple
|
7
|
+
# Some pseudo-class-syntax selectors are actually considered
|
8
|
+
# pseudo-elements and must be treated differently. This is a list of such
|
9
|
+
# selectors.
|
10
|
+
#
|
11
|
+
# @return [Set<String>]
|
12
|
+
ACTUALLY_ELEMENTS = %w[after before first-line first-letter].to_set
|
13
|
+
|
14
|
+
# Like \{#type}, but returns the type of selector this looks like, rather
|
15
|
+
# than the type it is semantically. This only differs from type for
|
16
|
+
# selectors in \{ACTUALLY\_ELEMENTS}.
|
17
|
+
#
|
18
|
+
# @return [Symbol]
|
19
|
+
attr_reader :syntactic_type
|
20
|
+
|
21
|
+
# The name of the selector.
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
attr_reader :name
|
25
|
+
|
26
|
+
# The argument to the selector,
|
27
|
+
# or `nil` if no argument was given.
|
28
|
+
#
|
29
|
+
# @return [String, nil]
|
30
|
+
attr_reader :arg
|
31
|
+
|
32
|
+
# The selector argument, or `nil` if no selector exists.
|
33
|
+
#
|
34
|
+
# If this and \{#arg\} are both set, \{#arg\} is considered a non-selector
|
35
|
+
# prefix.
|
36
|
+
#
|
37
|
+
# @return [CommaSequence]
|
38
|
+
attr_reader :selector
|
39
|
+
|
40
|
+
# @param syntactic_type [Symbol] See \{#syntactic_type}
|
41
|
+
# @param name [String] See \{#name}
|
42
|
+
# @param arg [nil, String] See \{#arg}
|
43
|
+
# @param selector [nil, CommaSequence] See \{#selector}
|
44
|
+
def initialize(syntactic_type, name, arg, selector)
|
45
|
+
@syntactic_type = syntactic_type
|
46
|
+
@name = name
|
47
|
+
@arg = arg
|
48
|
+
@selector = selector
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a copy of this with \{#selector} set to \{#new\_selector}.
|
52
|
+
#
|
53
|
+
# @param new_selector [CommaSequence]
|
54
|
+
# @return [CommaSequence]
|
55
|
+
def with_selector(new_selector)
|
56
|
+
Pseudo.new(syntactic_type, name, arg, CommaSequence.new(new_selector.members.map do |seq|
|
57
|
+
next seq unless seq.members.length == 1
|
58
|
+
sseq = seq.members.first
|
59
|
+
next seq unless sseq.is_a?(SimpleSequence) && sseq.members.length == 1
|
60
|
+
sel = sseq.members.first
|
61
|
+
next seq unless sel.is_a?(Pseudo) && sel.selector
|
62
|
+
|
63
|
+
case normalized_name
|
64
|
+
when 'not'
|
65
|
+
# In theory, if there's a nested :not its contents should be
|
66
|
+
# unified with the return value. For example, if :not(.foo)
|
67
|
+
# extends .bar, :not(.bar) should become .foo:not(.bar). However,
|
68
|
+
# this is a narrow edge case and supporting it properly would make
|
69
|
+
# this code and the code calling it a lot more complicated, so
|
70
|
+
# it's not supported for now.
|
71
|
+
next [] unless sel.normalized_name == 'matches'
|
72
|
+
sel.selector.members
|
73
|
+
when 'matches', 'any', 'current', 'nth-child', 'nth-last-child'
|
74
|
+
# As above, we could theoretically support :not within :matches, but
|
75
|
+
# doing so would require this method and its callers to handle much
|
76
|
+
# more complex cases that likely aren't worth the pain.
|
77
|
+
next [] unless sel.name == name && sel.arg == arg
|
78
|
+
sel.selector.members
|
79
|
+
when 'has', 'host', 'host-context'
|
80
|
+
# We can't expand nested selectors here, because each layer adds an
|
81
|
+
# additional layer of semantics. For example, `:has(:has(img))`
|
82
|
+
# doesn't match `<div><img></div>` but `:has(img)` does.
|
83
|
+
sel
|
84
|
+
else
|
85
|
+
[]
|
86
|
+
end
|
87
|
+
end.flatten))
|
88
|
+
end
|
89
|
+
|
90
|
+
# The type of the selector. `:class` if this is a pseudoclass selector,
|
91
|
+
# `:element` if it's a pseudoelement.
|
92
|
+
#
|
93
|
+
# @return [Symbol]
|
94
|
+
def type
|
95
|
+
ACTUALLY_ELEMENTS.include?(normalized_name) ? :element : syntactic_type
|
96
|
+
end
|
97
|
+
|
98
|
+
# Like \{#name\}, but without any vendor prefix.
|
99
|
+
#
|
100
|
+
# @return [String]
|
101
|
+
def normalized_name
|
102
|
+
@normalized_name ||= name.gsub(/^-[a-zA-Z0-9]+-/, '')
|
103
|
+
end
|
104
|
+
|
105
|
+
# @see Selector#to_s
|
106
|
+
def to_s
|
107
|
+
res = (syntactic_type == :class ? ":" : "::") + @name
|
108
|
+
if @arg || @selector
|
109
|
+
res << "("
|
110
|
+
res << @arg.strip if @arg
|
111
|
+
res << " " if @arg && @selector
|
112
|
+
res << @selector.to_s if @selector
|
113
|
+
res << ")"
|
114
|
+
end
|
115
|
+
res
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns `nil` if this is a pseudoelement selector
|
119
|
+
# and `sels` contains a pseudoelement selector different than this one.
|
120
|
+
#
|
121
|
+
# @see SimpleSequence#unify
|
122
|
+
def unify(sels)
|
123
|
+
return if type == :element && sels.any? do |sel|
|
124
|
+
sel.is_a?(Pseudo) && sel.type == :element &&
|
125
|
+
(sel.name != name || sel.arg != arg || sel.selector != selector)
|
126
|
+
end
|
127
|
+
super
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns whether or not this selector matches all elements
|
131
|
+
# that the given selector matches (as well as possibly more).
|
132
|
+
#
|
133
|
+
# @example
|
134
|
+
# (.foo).superselector?(.foo.bar) #=> true
|
135
|
+
# (.foo).superselector?(.bar) #=> false
|
136
|
+
# @param their_sseq [SimpleSequence]
|
137
|
+
# @param parents [Array<SimpleSequence, String>] The parent selectors of `their_sseq`, if any.
|
138
|
+
# @return [Boolean]
|
139
|
+
def superselector?(their_sseq, parents = [])
|
140
|
+
case normalized_name
|
141
|
+
when 'matches', 'any'
|
142
|
+
# :matches can be a superselector of another selector in one of two
|
143
|
+
# ways. Either its constituent selectors can be a superset of those of
|
144
|
+
# another :matches in the other selector, or any of its constituent
|
145
|
+
# selectors can individually be a superselector of the other selector.
|
146
|
+
(their_sseq.selector_pseudo_classes[normalized_name] || []).any? do |their_sel|
|
147
|
+
next false unless their_sel.is_a?(Pseudo)
|
148
|
+
next false unless their_sel.name == name
|
149
|
+
selector.superselector?(their_sel.selector)
|
150
|
+
end || selector.members.any? do |our_seq|
|
151
|
+
their_seq = Sequence.new(parents + [their_sseq])
|
152
|
+
our_seq.superselector?(their_seq)
|
153
|
+
end
|
154
|
+
when 'has', 'host', 'host-context'
|
155
|
+
# Like :matches, :has (et al) can be a superselector of another
|
156
|
+
# selector if its constituent selectors are a superset of those of
|
157
|
+
# another :has in the other selector. However, the :matches other case
|
158
|
+
# doesn't work, because :has refers to nested elements.
|
159
|
+
(their_sseq.selector_pseudo_classes[normalized_name] || []).any? do |their_sel|
|
160
|
+
next false unless their_sel.is_a?(Pseudo)
|
161
|
+
next false unless their_sel.name == name
|
162
|
+
selector.superselector?(their_sel.selector)
|
163
|
+
end
|
164
|
+
when 'not'
|
165
|
+
selector.members.all? do |our_seq|
|
166
|
+
their_sseq.members.any? do |their_sel|
|
167
|
+
if their_sel.is_a?(Element) || their_sel.is_a?(Id)
|
168
|
+
# `:not(a)` is a superselector of `h1` and `:not(#foo)` is a
|
169
|
+
# superselector of `#bar`.
|
170
|
+
our_sseq = our_seq.members.last
|
171
|
+
next false unless our_sseq.is_a?(SimpleSequence)
|
172
|
+
our_sseq.members.any? do |our_sel|
|
173
|
+
our_sel.class == their_sel.class && our_sel != their_sel
|
174
|
+
end
|
175
|
+
else
|
176
|
+
next false unless their_sel.is_a?(Pseudo)
|
177
|
+
next false unless their_sel.name == name
|
178
|
+
# :not(X) is a superselector of :not(Y) exactly when Y is a
|
179
|
+
# superselector of X.
|
180
|
+
their_sel.selector.superselector?(CommaSequence.new([our_seq]))
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
when 'current'
|
185
|
+
(their_sseq.selector_pseudo_classes['current'] || []).any? do |their_current|
|
186
|
+
next false if their_current.name != name
|
187
|
+
# Explicitly don't check for nested superselector relationships
|
188
|
+
# here. :current(.foo) isn't always a superselector of
|
189
|
+
# :current(.foo.bar), since it matches the *innermost* ancestor of
|
190
|
+
# the current element that matches the selector. For example:
|
191
|
+
#
|
192
|
+
# <div class="foo bar">
|
193
|
+
# <p class="foo">
|
194
|
+
# <span>current element</span>
|
195
|
+
# </p>
|
196
|
+
# </div>
|
197
|
+
#
|
198
|
+
# Here :current(.foo) would match the p element and *not* the div
|
199
|
+
# element, whereas :current(.foo.bar) would match the div and not
|
200
|
+
# the p.
|
201
|
+
selector == their_current.selector
|
202
|
+
end
|
203
|
+
when 'nth-child', 'nth-last-child'
|
204
|
+
their_sseq.members.any? do |their_sel|
|
205
|
+
# This misses a few edge cases. For example, `:nth-child(n of X)`
|
206
|
+
# is a superselector of `X`, and `:nth-child(2n of X)` is a
|
207
|
+
# superselector of `:nth-child(4n of X)`. These seem rare enough
|
208
|
+
# not to be worth worrying about, though.
|
209
|
+
next false unless their_sel.is_a?(Pseudo)
|
210
|
+
next false unless their_sel.name == name
|
211
|
+
next false unless their_sel.arg == arg
|
212
|
+
selector.superselector?(their_sel.selector)
|
213
|
+
end
|
214
|
+
else
|
215
|
+
throw "[BUG] Unknown selector pseudo class #{name}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# @see AbstractSequence#specificity
|
220
|
+
def specificity
|
221
|
+
return 1 if type == :element
|
222
|
+
return SPECIFICITY_BASE unless selector
|
223
|
+
@specificity ||=
|
224
|
+
if normalized_name == 'not'
|
225
|
+
min = 0
|
226
|
+
max = 0
|
227
|
+
selector.members.each do |seq|
|
228
|
+
spec = seq.specificity
|
229
|
+
if spec.is_a?(Range)
|
230
|
+
min = Sass::Util.max(spec.begin, min)
|
231
|
+
max = Sass::Util.max(spec.end, max)
|
232
|
+
else
|
233
|
+
min = Sass::Util.max(spec, min)
|
234
|
+
max = Sass::Util.max(spec, max)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
min == max ? max : (min..max)
|
238
|
+
else
|
239
|
+
min = 0
|
240
|
+
max = 0
|
241
|
+
selector.members.each do |seq|
|
242
|
+
spec = seq.specificity
|
243
|
+
if spec.is_a?(Range)
|
244
|
+
min = Sass::Util.min(spec.begin, min)
|
245
|
+
max = Sass::Util.max(spec.end, max)
|
246
|
+
else
|
247
|
+
min = Sass::Util.min(spec, min)
|
248
|
+
max = Sass::Util.max(spec, max)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
min == max ? max : (min..max)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -78,50 +78,76 @@ module Sass
|
|
78
78
|
# Non-destructively extends this selector with the extensions specified in a hash
|
79
79
|
# (which should come from {Sass::Tree::Visitors::Cssize}).
|
80
80
|
#
|
81
|
-
# @
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
81
|
+
# @param extends [Sass::Util::SubsetMap{Selector::Simple =>
|
82
|
+
# Sass::Tree::Visitors::Cssize::Extend}]
|
83
|
+
# The extensions to perform on this selector
|
84
|
+
# @param parent_directives [Array<Sass::Tree::DirectiveNode>]
|
85
|
+
# The directives containing this selector.
|
86
|
+
# @param replace [Boolean]
|
87
|
+
# Whether to replace the original selector entirely or include
|
88
|
+
# it in the result.
|
89
|
+
# @param seen [Set<Array<Selector::Simple>>]
|
90
|
+
# The set of simple sequences that are currently being replaced.
|
91
|
+
# @param original [Boolean]
|
92
|
+
# Whether this is the original selector being extended, as opposed to
|
93
|
+
# the result of a previous extension that's being re-extended.
|
87
94
|
# @return [Array<Sequence>] A list of selectors generated
|
88
95
|
# by extending this selector with `extends`.
|
89
96
|
# These correspond to a {CommaSequence}'s {CommaSequence#members members array}.
|
90
97
|
# @see CommaSequence#do_extend
|
91
|
-
def do_extend(extends, parent_directives, seen
|
98
|
+
def do_extend(extends, parent_directives, replace, seen, original)
|
92
99
|
extended_not_expanded = members.map do |sseq_or_op|
|
93
100
|
next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)
|
94
|
-
extended = sseq_or_op.do_extend(extends, parent_directives, seen)
|
95
|
-
|
96
|
-
|
97
|
-
|
101
|
+
extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen)
|
102
|
+
|
103
|
+
# The First Law of Extend says that the generated selector should have
|
104
|
+
# specificity greater than or equal to that of the original selector.
|
105
|
+
# In order to ensure that, we record the original selector's
|
106
|
+
# (`extended.first`) original specificity.
|
107
|
+
extended.first.add_sources!([self]) if original && !has_placeholder?
|
108
|
+
|
109
|
+
extended.map {|seq| seq.members}
|
98
110
|
end
|
99
111
|
weaves = Sass::Util.paths(extended_not_expanded).map {|path| weave(path)}
|
100
112
|
trim(weaves).map {|p| Sequence.new(p)}
|
101
113
|
end
|
102
114
|
|
115
|
+
# Unifies this with another selector sequence to produce a selector
|
116
|
+
# that matches (a subset of) the intersection of the two inputs.
|
117
|
+
#
|
118
|
+
# @param other [Sequence]
|
119
|
+
# @return [CommaSequence, nil] The unified selector, or nil if unification failed.
|
120
|
+
# @raise [Sass::SyntaxError] If this selector cannot be unified.
|
121
|
+
# This will only ever occur when a dynamic selector,
|
122
|
+
# such as {Parent} or {Interpolation}, is used in unification.
|
123
|
+
# Since these selectors should be resolved
|
124
|
+
# by the time extension and unification happen,
|
125
|
+
# this exception will only ever be raised as a result of programmer error
|
126
|
+
def unify(other)
|
127
|
+
base = members.last
|
128
|
+
other_base = other.members.last
|
129
|
+
return unless base.is_a?(SimpleSequence) && other_base.is_a?(SimpleSequence)
|
130
|
+
return unless (unified = other_base.unify(base))
|
131
|
+
|
132
|
+
woven = weave([members[0...-1], other.members[0...-1] + [unified]])
|
133
|
+
CommaSequence.new(woven.map {|w| Sequence.new(w)})
|
134
|
+
end
|
135
|
+
|
103
136
|
# Returns whether or not this selector matches all elements
|
104
137
|
# that the given selector matches (as well as possibly more).
|
105
138
|
#
|
106
139
|
# @example
|
107
140
|
# (.foo).superselector?(.foo.bar) #=> true
|
108
141
|
# (.foo).superselector?(.bar) #=> false
|
109
|
-
#
|
110
|
-
# @param sseq [SimpleSequence]
|
142
|
+
# @param cseq [Sequence]
|
111
143
|
# @return [Boolean]
|
112
|
-
def superselector?(
|
113
|
-
|
114
|
-
members.last.superselector?(sseq)
|
144
|
+
def superselector?(seq)
|
145
|
+
_superselector?(members, seq.members)
|
115
146
|
end
|
116
147
|
|
117
|
-
# @see
|
118
|
-
def
|
119
|
-
|
120
|
-
seq_or_op.is_a?(SimpleSequence) ? seq_or_op.to_a : seq_or_op
|
121
|
-
end
|
122
|
-
ary = Sass::Util.intersperse(ary, " ").flatten.compact
|
123
|
-
ary = Sass::Util.replace_subseq(ary, ["\n", " "], ["\n"])
|
124
|
-
Sass::Util.replace_subseq(ary, [" ", "\n"], ["\n"])
|
148
|
+
# @see AbstractSequence#to_s
|
149
|
+
def to_s
|
150
|
+
@members.join(" ").gsub(/ ?\n ?/, "\n")
|
125
151
|
end
|
126
152
|
|
127
153
|
# Returns a string representation of the sequence.
|
@@ -141,6 +167,35 @@ module Sass
|
|
141
167
|
members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m}
|
142
168
|
end
|
143
169
|
|
170
|
+
# Converts the subject operator "!", if it exists, into a ":has()"
|
171
|
+
# selector.
|
172
|
+
#
|
173
|
+
# @retur [Sequence]
|
174
|
+
def subjectless
|
175
|
+
pre_subject = []
|
176
|
+
has = []
|
177
|
+
subject = nil
|
178
|
+
members.each do |sseq_or_op|
|
179
|
+
if subject
|
180
|
+
has << sseq_or_op
|
181
|
+
elsif sseq_or_op.is_a?(String) || !sseq_or_op.subject?
|
182
|
+
pre_subject << sseq_or_op
|
183
|
+
else
|
184
|
+
subject = sseq_or_op.dup
|
185
|
+
subject.members = sseq_or_op.members.dup
|
186
|
+
subject.subject = false
|
187
|
+
has = []
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
return self unless subject
|
192
|
+
|
193
|
+
unless has.empty?
|
194
|
+
subject.members << Pseudo.new(:class, 'has', nil, CommaSequence.new([Sequence.new(has)]))
|
195
|
+
end
|
196
|
+
Sequence.new(pre_subject + [subject])
|
197
|
+
end
|
198
|
+
|
144
199
|
private
|
145
200
|
|
146
201
|
# Conceptually, this expands "parenthesized selectors". That is, if we
|
@@ -159,6 +214,7 @@ module Sass
|
|
159
214
|
prefixes = [[]]
|
160
215
|
|
161
216
|
path.each do |current|
|
217
|
+
next if current.empty?
|
162
218
|
current = current.dup
|
163
219
|
last_current = [current.pop]
|
164
220
|
prefixes = Sass::Util.flatten(prefixes.map do |prefix|
|
@@ -292,7 +348,7 @@ module Sass
|
|
292
348
|
elsif sel2.superselector?(sel1)
|
293
349
|
res.unshift sel1, '~'
|
294
350
|
else
|
295
|
-
merged = sel1.unify(sel2
|
351
|
+
merged = sel1.unify(sel2)
|
296
352
|
res.unshift [
|
297
353
|
[sel1, '~', sel2, '~'],
|
298
354
|
[sel2, '~', sel1, '~'],
|
@@ -309,7 +365,7 @@ module Sass
|
|
309
365
|
if tilde_sel.superselector?(plus_sel)
|
310
366
|
res.unshift plus_sel, '+'
|
311
367
|
else
|
312
|
-
merged = plus_sel.unify(tilde_sel
|
368
|
+
merged = plus_sel.unify(tilde_sel)
|
313
369
|
res.unshift [
|
314
370
|
[tilde_sel, '~', plus_sel, '+'],
|
315
371
|
([merged, '+'] if merged)
|
@@ -322,7 +378,7 @@ module Sass
|
|
322
378
|
res.unshift sel1, op1
|
323
379
|
seq2.push sel2, op2
|
324
380
|
elsif op1 == op2
|
325
|
-
merged = sel1.unify(sel2
|
381
|
+
merged = sel1.unify(sel2)
|
326
382
|
return unless merged
|
327
383
|
res.unshift merged, op1
|
328
384
|
else
|
@@ -412,12 +468,12 @@ module Sass
|
|
412
468
|
seq1.first.is_a?(String) || seq2.first.is_a?(String)
|
413
469
|
# More complex selectors are never superselectors of less complex ones
|
414
470
|
return if seq1.size > seq2.size
|
415
|
-
return seq1.first.superselector?(seq2.last) if seq1.size == 1
|
471
|
+
return seq1.first.superselector?(seq2.last, seq2[0...-1]) if seq1.size == 1
|
416
472
|
|
417
473
|
_, si = Sass::Util.enum_with_index(seq2).find do |e, i|
|
418
474
|
return if i == seq2.size - 1
|
419
475
|
next if e.is_a?(String)
|
420
|
-
seq1.first.superselector?(e)
|
476
|
+
seq1.first.superselector?(e, seq2[0...i])
|
421
477
|
end
|
422
478
|
return unless si
|
423
479
|
|
@@ -475,7 +531,15 @@ module Sass
|
|
475
531
|
# separate sequences should limit the quadratic behavior.
|
476
532
|
seqses.each_with_index do |seqs1, i|
|
477
533
|
result[i] = seqs1.reject do |seq1|
|
478
|
-
|
534
|
+
# The maximum specificity of the sources that caused [seq1] to be
|
535
|
+
# generated. In order for [seq1] to be removed, there must be
|
536
|
+
# another selector that's a superselector of it *and* that has
|
537
|
+
# specificity greater or equal to this.
|
538
|
+
max_spec = _sources(seq1).map do |seq|
|
539
|
+
spec = seq.specificity
|
540
|
+
spec.is_a?(Range) ? spec.max : spec
|
541
|
+
end.max || 0
|
542
|
+
|
479
543
|
result.any? do |seqs2|
|
480
544
|
next if seqs1.equal?(seqs2)
|
481
545
|
# Second Law of Extend: the specificity of a generated selector
|
@@ -483,7 +547,11 @@ module Sass
|
|
483
547
|
# selector.
|
484
548
|
#
|
485
549
|
# See https://github.com/nex3/sass/issues/324.
|
486
|
-
seqs2.any?
|
550
|
+
seqs2.any? do |seq2|
|
551
|
+
spec2 = _specificity(seq2)
|
552
|
+
spec2 = spec2.begin if spec2.is_a?(Range)
|
553
|
+
spec2 >= max_spec && _superselector?(seq2, seq1)
|
554
|
+
end
|
487
555
|
end
|
488
556
|
end
|
489
557
|
end
|