sass 3.3.0 → 3.4.0
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 +58 -50
- data/Rakefile +1 -4
- 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/cache_stores/filesystem.rb +6 -2
- data/lib/sass/css.rb +1 -3
- data/lib/sass/engine.rb +37 -46
- data/lib/sass/environment.rb +13 -17
- data/lib/sass/error.rb +6 -9
- data/lib/sass/exec/base.rb +187 -0
- data/lib/sass/exec/sass_convert.rb +264 -0
- data/lib/sass/exec/sass_scss.rb +424 -0
- data/lib/sass/exec.rb +5 -771
- data/lib/sass/features.rb +7 -0
- data/lib/sass/importers/base.rb +7 -2
- data/lib/sass/importers/filesystem.rb +9 -25
- data/lib/sass/importers.rb +0 -1
- data/lib/sass/media.rb +1 -4
- data/lib/sass/plugin/compiler.rb +200 -83
- data/lib/sass/plugin/staleness_checker.rb +1 -1
- data/lib/sass/plugin.rb +3 -3
- data/lib/sass/script/css_lexer.rb +1 -1
- data/lib/sass/script/functions.rb +622 -268
- data/lib/sass/script/lexer.rb +99 -34
- data/lib/sass/script/parser.rb +24 -23
- data/lib/sass/script/tree/funcall.rb +1 -1
- data/lib/sass/script/tree/interpolation.rb +20 -2
- data/lib/sass/script/tree/selector.rb +26 -0
- data/lib/sass/script/tree/string_interpolation.rb +1 -1
- data/lib/sass/script/tree.rb +1 -0
- data/lib/sass/script/value/base.rb +7 -5
- data/lib/sass/script/value/bool.rb +0 -5
- data/lib/sass/script/value/color.rb +39 -21
- 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 +62 -14
- data/lib/sass/script/value/string.rb +59 -11
- data/lib/sass/script/value.rb +0 -1
- data/lib/sass/scss/css_parser.rb +8 -2
- data/lib/sass/scss/parser.rb +190 -328
- data/lib/sass/scss/rx.rb +15 -6
- data/lib/sass/scss/static_parser.rb +298 -1
- data/lib/sass/selector/abstract_sequence.rb +28 -13
- data/lib/sass/selector/comma_sequence.rb +92 -13
- data/lib/sass/selector/pseudo.rb +256 -0
- data/lib/sass/selector/sequence.rb +94 -24
- data/lib/sass/selector/simple.rb +14 -25
- data/lib/sass/selector/simple_sequence.rb +97 -33
- data/lib/sass/selector.rb +57 -194
- data/lib/sass/shared.rb +1 -1
- data/lib/sass/source/map.rb +26 -12
- data/lib/sass/stack.rb +0 -6
- data/lib/sass/supports.rb +2 -3
- data/lib/sass/tree/at_root_node.rb +1 -0
- data/lib/sass/tree/charset_node.rb +1 -1
- data/lib/sass/tree/directive_node.rb +8 -2
- data/lib/sass/tree/error_node.rb +18 -0
- data/lib/sass/tree/extend_node.rb +1 -1
- data/lib/sass/tree/function_node.rb +4 -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 +12 -7
- data/lib/sass/tree/visitors/check_nesting.rb +38 -10
- data/lib/sass/tree/visitors/convert.rb +16 -18
- data/lib/sass/tree/visitors/cssize.rb +29 -29
- data/lib/sass/tree/visitors/deep_copy.rb +5 -0
- data/lib/sass/tree/visitors/perform.rb +45 -33
- data/lib/sass/tree/visitors/set_options.rb +14 -0
- data/lib/sass/tree/visitors/to_css.rb +15 -14
- data/lib/sass/util/subset_map.rb +1 -1
- data/lib/sass/util.rb +222 -99
- data/lib/sass/version.rb +5 -5
- data/lib/sass.rb +0 -5
- data/test/sass/cache_test.rb +62 -20
- data/test/sass/callbacks_test.rb +1 -1
- data/test/sass/compiler_test.rb +19 -10
- data/test/sass/conversion_test.rb +58 -1
- data/test/sass/css2sass_test.rb +23 -4
- data/test/sass/encoding_test.rb +219 -0
- data/test/sass/engine_test.rb +136 -199
- data/test/sass/exec_test.rb +2 -2
- data/test/sass/extend_test.rb +236 -19
- data/test/sass/functions_test.rb +295 -253
- 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 +14 -13
- 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 +10 -7
- data/test/sass/script_test.rb +288 -74
- data/test/sass/scss/css_test.rb +141 -24
- data/test/sass/scss/rx_test.rb +4 -4
- data/test/sass/scss/scss_test.rb +457 -18
- data/test/sass/source_map_test.rb +115 -25
- 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 +31 -1
- data/test/sass/value_helpers_test.rb +5 -7
- data/test/test_helper.rb +2 -2
- data/vendor/listen/CHANGELOG.md +1 -228
- data/vendor/listen/Gemfile +5 -15
- data/vendor/listen/README.md +111 -77
- data/vendor/listen/Rakefile +0 -42
- data/vendor/listen/lib/listen/adapter.rb +195 -82
- data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
- data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
- data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
- data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
- data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
- data/vendor/listen/lib/listen/directory_record.rb +96 -61
- data/vendor/listen/lib/listen/listener.rb +135 -37
- data/vendor/listen/lib/listen/turnstile.rb +9 -5
- data/vendor/listen/lib/listen/version.rb +1 -1
- data/vendor/listen/lib/listen.rb +33 -19
- data/vendor/listen/listen.gemspec +6 -0
- data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
- data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
- data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
- data/vendor/listen/spec/listen/listener_spec.rb +128 -39
- data/vendor/listen/spec/listen_spec.rb +15 -21
- data/vendor/listen/spec/spec_helper.rb +4 -0
- data/vendor/listen/spec/support/adapter_helper.rb +52 -15
- data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
- data/vendor/listen/spec/support/listeners_helper.rb +30 -7
- metadata +25 -22
- data/ext/mkrf_conf.rb +0 -27
- data/lib/sass/importers/deprecated_path.rb +0 -51
- data/lib/sass/script/value/deprecated_false.rb +0 -55
- data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
- data/vendor/listen/lib/listen/multi_listener.rb +0 -143
- data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
- data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
@@ -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,48 +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
|
-
# @overload def do_extend(extends, parent_directives)
|
82
81
|
# @param extends [Sass::Util::SubsetMap{Selector::Simple =>
|
83
82
|
# Sass::Tree::Visitors::Cssize::Extend}]
|
84
83
|
# The extensions to perform on this selector
|
85
84
|
# @param parent_directives [Array<Sass::Tree::DirectiveNode>]
|
86
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
|
-
Sass::Util.intersperse(ary, " ").flatten.compact
|
148
|
+
# @see AbstractSequence#to_s
|
149
|
+
def to_s
|
150
|
+
@members.join(" ").gsub(/ ?\n ?/, "\n")
|
123
151
|
end
|
124
152
|
|
125
153
|
# Returns a string representation of the sequence.
|
@@ -139,6 +167,35 @@ module Sass
|
|
139
167
|
members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m}
|
140
168
|
end
|
141
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
|
+
|
142
199
|
private
|
143
200
|
|
144
201
|
# Conceptually, this expands "parenthesized selectors". That is, if we
|
@@ -157,6 +214,7 @@ module Sass
|
|
157
214
|
prefixes = [[]]
|
158
215
|
|
159
216
|
path.each do |current|
|
217
|
+
next if current.empty?
|
160
218
|
current = current.dup
|
161
219
|
last_current = [current.pop]
|
162
220
|
prefixes = Sass::Util.flatten(prefixes.map do |prefix|
|
@@ -290,7 +348,7 @@ module Sass
|
|
290
348
|
elsif sel2.superselector?(sel1)
|
291
349
|
res.unshift sel1, '~'
|
292
350
|
else
|
293
|
-
merged = sel1.unify(sel2
|
351
|
+
merged = sel1.unify(sel2)
|
294
352
|
res.unshift [
|
295
353
|
[sel1, '~', sel2, '~'],
|
296
354
|
[sel2, '~', sel1, '~'],
|
@@ -307,7 +365,7 @@ module Sass
|
|
307
365
|
if tilde_sel.superselector?(plus_sel)
|
308
366
|
res.unshift plus_sel, '+'
|
309
367
|
else
|
310
|
-
merged = plus_sel.unify(tilde_sel
|
368
|
+
merged = plus_sel.unify(tilde_sel)
|
311
369
|
res.unshift [
|
312
370
|
[tilde_sel, '~', plus_sel, '+'],
|
313
371
|
([merged, '+'] if merged)
|
@@ -320,7 +378,7 @@ module Sass
|
|
320
378
|
res.unshift sel1, op1
|
321
379
|
seq2.push sel2, op2
|
322
380
|
elsif op1 == op2
|
323
|
-
merged = sel1.unify(sel2
|
381
|
+
merged = sel1.unify(sel2)
|
324
382
|
return unless merged
|
325
383
|
res.unshift merged, op1
|
326
384
|
else
|
@@ -410,12 +468,12 @@ module Sass
|
|
410
468
|
seq1.first.is_a?(String) || seq2.first.is_a?(String)
|
411
469
|
# More complex selectors are never superselectors of less complex ones
|
412
470
|
return if seq1.size > seq2.size
|
413
|
-
return seq1.first.superselector?(seq2.last) if seq1.size == 1
|
471
|
+
return seq1.first.superselector?(seq2.last, seq2[0...-1]) if seq1.size == 1
|
414
472
|
|
415
473
|
_, si = Sass::Util.enum_with_index(seq2).find do |e, i|
|
416
474
|
return if i == seq2.size - 1
|
417
475
|
next if e.is_a?(String)
|
418
|
-
seq1.first.superselector?(e)
|
476
|
+
seq1.first.superselector?(e, seq2[0...i])
|
419
477
|
end
|
420
478
|
return unless si
|
421
479
|
|
@@ -473,7 +531,15 @@ module Sass
|
|
473
531
|
# separate sequences should limit the quadratic behavior.
|
474
532
|
seqses.each_with_index do |seqs1, i|
|
475
533
|
result[i] = seqs1.reject do |seq1|
|
476
|
-
|
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
|
+
|
477
543
|
result.any? do |seqs2|
|
478
544
|
next if seqs1.equal?(seqs2)
|
479
545
|
# Second Law of Extend: the specificity of a generated selector
|
@@ -481,7 +547,11 @@ module Sass
|
|
481
547
|
# selector.
|
482
548
|
#
|
483
549
|
# See https://github.com/nex3/sass/issues/324.
|
484
|
-
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
|
485
555
|
end
|
486
556
|
end
|
487
557
|
end
|
data/lib/sass/selector/simple.rb
CHANGED
@@ -14,28 +14,18 @@ module Sass
|
|
14
14
|
# @return [String, nil]
|
15
15
|
attr_accessor :filename
|
16
16
|
|
17
|
-
#
|
18
|
-
# potentially {Sass::Script::Tree::Node}s (if there's interpolation in the
|
19
|
-
# selector). When the interpolation is resolved and the strings are joined
|
20
|
-
# together, this will be the string representation of this node.
|
21
|
-
#
|
22
|
-
# @return [Array<String, Sass::Script::Tree::Node>]
|
23
|
-
def to_a
|
24
|
-
Sass::Util.abstract(self)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Returns a string representation of the node.
|
28
|
-
# This is basically the selector string.
|
17
|
+
# @see #to_s
|
29
18
|
#
|
30
19
|
# @return [String]
|
31
20
|
def inspect
|
32
|
-
|
21
|
+
to_s
|
33
22
|
end
|
34
23
|
|
35
|
-
#
|
24
|
+
# Returns the selector string.
|
25
|
+
#
|
36
26
|
# @return [String]
|
37
27
|
def to_s
|
38
|
-
|
28
|
+
Sass::Util.abstract(self)
|
39
29
|
end
|
40
30
|
|
41
31
|
# Returns a hash code for this selector object.
|
@@ -58,7 +48,7 @@ module Sass
|
|
58
48
|
# @param other [Object] The object to test equality against
|
59
49
|
# @return [Boolean] Whether or not this is equal to `other`
|
60
50
|
def eql?(other)
|
61
|
-
other.class == self.class && other.hash == hash && other.equality_key
|
51
|
+
other.class == self.class && other.hash == hash && other.equality_key == equality_key
|
62
52
|
end
|
63
53
|
alias_method :==, :eql?
|
64
54
|
|
@@ -83,10 +73,10 @@ module Sass
|
|
83
73
|
return sels if sels.any? {|sel2| eql?(sel2)}
|
84
74
|
sels_with_ix = Sass::Util.enum_with_index(sels)
|
85
75
|
_, i =
|
86
|
-
if is_a?(Pseudo)
|
76
|
+
if is_a?(Pseudo)
|
87
77
|
sels_with_ix.find {|sel, _| sel.is_a?(Pseudo) && (sels.last.type == :element)}
|
88
78
|
else
|
89
|
-
sels_with_ix.find {|sel, _| sel.is_a?(Pseudo)
|
79
|
+
sels_with_ix.find {|sel, _| sel.is_a?(Pseudo)}
|
90
80
|
end
|
91
81
|
return sels + [self] unless i
|
92
82
|
sels[0...i] + [self] + sels[i..-1]
|
@@ -96,12 +86,11 @@ module Sass
|
|
96
86
|
|
97
87
|
# Returns the key used for testing whether selectors are equal.
|
98
88
|
#
|
99
|
-
# This is
|
100
|
-
# selectors constructed in different ways are considered equivalent.
|
89
|
+
# This is a cached version of \{#to\_s}.
|
101
90
|
#
|
102
|
-
# @return [
|
91
|
+
# @return [String]
|
103
92
|
def equality_key
|
104
|
-
@equality_key ||=
|
93
|
+
@equality_key ||= to_s
|
105
94
|
end
|
106
95
|
|
107
96
|
# Unifies two namespaces,
|
@@ -118,9 +107,9 @@ module Sass
|
|
118
107
|
# could be found at all.
|
119
108
|
# If the second value is `false`, the first should be ignored.
|
120
109
|
def unify_namespaces(ns1, ns2)
|
121
|
-
return nil, false unless ns1 == ns2 || ns1.nil? || ns1 ==
|
122
|
-
return ns2, true if ns1 ==
|
123
|
-
return ns1, true if ns2 ==
|
110
|
+
return nil, false unless ns1 == ns2 || ns1.nil? || ns1 == '*' || ns2.nil? || ns2 == '*'
|
111
|
+
return ns2, true if ns1 == '*'
|
112
|
+
return ns1, true if ns2 == '*'
|
124
113
|
[ns1 || ns2, true]
|
125
114
|
end
|
126
115
|
end
|