sass 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +58 -50
  4. data/Rakefile +1 -4
  5. data/VERSION +1 -1
  6. data/VERSION_DATE +1 -1
  7. data/VERSION_NAME +1 -1
  8. data/bin/sass +1 -1
  9. data/bin/scss +1 -1
  10. data/lib/sass/cache_stores/filesystem.rb +6 -2
  11. data/lib/sass/css.rb +1 -3
  12. data/lib/sass/engine.rb +37 -46
  13. data/lib/sass/environment.rb +13 -17
  14. data/lib/sass/error.rb +6 -9
  15. data/lib/sass/exec/base.rb +187 -0
  16. data/lib/sass/exec/sass_convert.rb +264 -0
  17. data/lib/sass/exec/sass_scss.rb +424 -0
  18. data/lib/sass/exec.rb +5 -771
  19. data/lib/sass/features.rb +7 -0
  20. data/lib/sass/importers/base.rb +7 -2
  21. data/lib/sass/importers/filesystem.rb +9 -25
  22. data/lib/sass/importers.rb +0 -1
  23. data/lib/sass/media.rb +1 -4
  24. data/lib/sass/plugin/compiler.rb +200 -83
  25. data/lib/sass/plugin/staleness_checker.rb +1 -1
  26. data/lib/sass/plugin.rb +3 -3
  27. data/lib/sass/script/css_lexer.rb +1 -1
  28. data/lib/sass/script/functions.rb +622 -268
  29. data/lib/sass/script/lexer.rb +99 -34
  30. data/lib/sass/script/parser.rb +24 -23
  31. data/lib/sass/script/tree/funcall.rb +1 -1
  32. data/lib/sass/script/tree/interpolation.rb +20 -2
  33. data/lib/sass/script/tree/selector.rb +26 -0
  34. data/lib/sass/script/tree/string_interpolation.rb +1 -1
  35. data/lib/sass/script/tree.rb +1 -0
  36. data/lib/sass/script/value/base.rb +7 -5
  37. data/lib/sass/script/value/bool.rb +0 -5
  38. data/lib/sass/script/value/color.rb +39 -21
  39. data/lib/sass/script/value/helpers.rb +107 -0
  40. data/lib/sass/script/value/list.rb +0 -15
  41. data/lib/sass/script/value/null.rb +0 -5
  42. data/lib/sass/script/value/number.rb +62 -14
  43. data/lib/sass/script/value/string.rb +59 -11
  44. data/lib/sass/script/value.rb +0 -1
  45. data/lib/sass/scss/css_parser.rb +8 -2
  46. data/lib/sass/scss/parser.rb +190 -328
  47. data/lib/sass/scss/rx.rb +15 -6
  48. data/lib/sass/scss/static_parser.rb +298 -1
  49. data/lib/sass/selector/abstract_sequence.rb +28 -13
  50. data/lib/sass/selector/comma_sequence.rb +92 -13
  51. data/lib/sass/selector/pseudo.rb +256 -0
  52. data/lib/sass/selector/sequence.rb +94 -24
  53. data/lib/sass/selector/simple.rb +14 -25
  54. data/lib/sass/selector/simple_sequence.rb +97 -33
  55. data/lib/sass/selector.rb +57 -194
  56. data/lib/sass/shared.rb +1 -1
  57. data/lib/sass/source/map.rb +26 -12
  58. data/lib/sass/stack.rb +0 -6
  59. data/lib/sass/supports.rb +2 -3
  60. data/lib/sass/tree/at_root_node.rb +1 -0
  61. data/lib/sass/tree/charset_node.rb +1 -1
  62. data/lib/sass/tree/directive_node.rb +8 -2
  63. data/lib/sass/tree/error_node.rb +18 -0
  64. data/lib/sass/tree/extend_node.rb +1 -1
  65. data/lib/sass/tree/function_node.rb +4 -0
  66. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  67. data/lib/sass/tree/prop_node.rb +1 -1
  68. data/lib/sass/tree/rule_node.rb +12 -7
  69. data/lib/sass/tree/visitors/check_nesting.rb +38 -10
  70. data/lib/sass/tree/visitors/convert.rb +16 -18
  71. data/lib/sass/tree/visitors/cssize.rb +29 -29
  72. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  73. data/lib/sass/tree/visitors/perform.rb +45 -33
  74. data/lib/sass/tree/visitors/set_options.rb +14 -0
  75. data/lib/sass/tree/visitors/to_css.rb +15 -14
  76. data/lib/sass/util/subset_map.rb +1 -1
  77. data/lib/sass/util.rb +222 -99
  78. data/lib/sass/version.rb +5 -5
  79. data/lib/sass.rb +0 -5
  80. data/test/sass/cache_test.rb +62 -20
  81. data/test/sass/callbacks_test.rb +1 -1
  82. data/test/sass/compiler_test.rb +19 -10
  83. data/test/sass/conversion_test.rb +58 -1
  84. data/test/sass/css2sass_test.rb +23 -4
  85. data/test/sass/encoding_test.rb +219 -0
  86. data/test/sass/engine_test.rb +136 -199
  87. data/test/sass/exec_test.rb +2 -2
  88. data/test/sass/extend_test.rb +236 -19
  89. data/test/sass/functions_test.rb +295 -253
  90. data/test/sass/importer_test.rb +31 -21
  91. data/test/sass/logger_test.rb +1 -1
  92. data/test/sass/more_results/more_import.css +1 -1
  93. data/test/sass/plugin_test.rb +14 -13
  94. data/test/sass/results/compact.css +1 -1
  95. data/test/sass/results/complex.css +4 -4
  96. data/test/sass/results/expanded.css +1 -1
  97. data/test/sass/results/import.css +1 -1
  98. data/test/sass/results/import_charset_ibm866.css +2 -2
  99. data/test/sass/results/mixins.css +17 -17
  100. data/test/sass/results/nested.css +1 -1
  101. data/test/sass/results/parent_ref.css +2 -2
  102. data/test/sass/results/script.css +3 -3
  103. data/test/sass/results/scss_import.css +1 -1
  104. data/test/sass/script_conversion_test.rb +10 -7
  105. data/test/sass/script_test.rb +288 -74
  106. data/test/sass/scss/css_test.rb +141 -24
  107. data/test/sass/scss/rx_test.rb +4 -4
  108. data/test/sass/scss/scss_test.rb +457 -18
  109. data/test/sass/source_map_test.rb +115 -25
  110. data/test/sass/superselector_test.rb +191 -0
  111. data/test/sass/templates/scss_import.scss +2 -1
  112. data/test/sass/test_helper.rb +1 -1
  113. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  114. data/test/sass/util/normalized_map_test.rb +1 -1
  115. data/test/sass/util/subset_map_test.rb +2 -2
  116. data/test/sass/util_test.rb +31 -1
  117. data/test/sass/value_helpers_test.rb +5 -7
  118. data/test/test_helper.rb +2 -2
  119. data/vendor/listen/CHANGELOG.md +1 -228
  120. data/vendor/listen/Gemfile +5 -15
  121. data/vendor/listen/README.md +111 -77
  122. data/vendor/listen/Rakefile +0 -42
  123. data/vendor/listen/lib/listen/adapter.rb +195 -82
  124. data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
  125. data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
  126. data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
  127. data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
  128. data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
  129. data/vendor/listen/lib/listen/directory_record.rb +96 -61
  130. data/vendor/listen/lib/listen/listener.rb +135 -37
  131. data/vendor/listen/lib/listen/turnstile.rb +9 -5
  132. data/vendor/listen/lib/listen/version.rb +1 -1
  133. data/vendor/listen/lib/listen.rb +33 -19
  134. data/vendor/listen/listen.gemspec +6 -0
  135. data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
  136. data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
  137. data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
  138. data/vendor/listen/spec/listen/listener_spec.rb +128 -39
  139. data/vendor/listen/spec/listen_spec.rb +15 -21
  140. data/vendor/listen/spec/spec_helper.rb +4 -0
  141. data/vendor/listen/spec/support/adapter_helper.rb +52 -15
  142. data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
  143. data/vendor/listen/spec/support/listeners_helper.rb +30 -7
  144. metadata +25 -22
  145. data/ext/mkrf_conf.rb +0 -27
  146. data/lib/sass/importers/deprecated_path.rb +0 -51
  147. data/lib/sass/script/value/deprecated_false.rb +0 -55
  148. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  149. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  150. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  151. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
data/lib/sass/scss/rx.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module Sass
2
3
  module SCSS
3
4
  # A module containing regular expressions used
@@ -63,9 +64,9 @@ module Sass
63
64
  STRING1 = /\"((?:[^\n\r\f\\"]|\\#{NL}|#{ESCAPE})*)\"/
64
65
  STRING2 = /\'((?:[^\n\r\f\\']|\\#{NL}|#{ESCAPE})*)\'/
65
66
 
66
- IDENT = /-?#{NMSTART}#{NMCHAR}*/
67
+ IDENT = /-*#{NMSTART}#{NMCHAR}*/
67
68
  NAME = /#{NMCHAR}+/
68
- NUM = /[0-9]+|[0-9]*\.[0-9]+/
69
+ NUM = //
69
70
  STRING = /#{STRING1}|#{STRING2}/
70
71
  URLCHAR = /[#%&*-~]|#{NONASCII}|#{ESCAPE}/
71
72
  URL = /(#{URLCHAR}*)/
@@ -80,7 +81,7 @@ module Sass
80
81
 
81
82
  S = /[ \t\r\n\f]+/
82
83
 
83
- COMMENT = %r{/\*[^*]*\*+(?:[^/][^*]*\*+)*/}
84
+ COMMENT = %r{/\*([^*]|\*+[^/*])*\**\*/}
84
85
  SINGLE_LINE_COMMENT = %r{//.*(\n[ \t]*//.*)*}
85
86
 
86
87
  CDO = quote("<!--")
@@ -95,7 +96,9 @@ module Sass
95
96
 
96
97
  IMPORTANT = /!#{W}important/i
97
98
 
98
- NUMBER = /#{NUM}(?:#{IDENT}|%)?/
99
+ UNITLESS_NUMBER = /(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?\d+)?/
100
+ NUMBER = /#{UNITLESS_NUMBER}(?:#{IDENT}|%)?/
101
+ PERCENTAGE = /#{UNITLESS_NUMBER}%/
99
102
 
100
103
  URI = /url\(#{W}(?:#{STRING}|#{URL})#{W}\)/i
101
104
  FUNCTION = /#{IDENT}\(/
@@ -118,10 +121,16 @@ module Sass
118
121
  INTERP_START = /#\{/
119
122
  ANY = /:(-[-\w]+-)?any\(/i
120
123
  OPTIONAL = /!#{W}optional/i
124
+ IDENT_START = /-|#{NMSTART}/
125
+
126
+ # A unit is like an IDENT, but disallows a hyphen followed by a digit.
127
+ # This allows "1px-2px" to be interpreted as subtraction rather than "1"
128
+ # with the unit "px-2px". It also allows "%".
129
+ UNIT = /-?#{NMSTART}(?:[a-zA-Z0-9_]|#{NONASCII}|#{ESCAPE}|-(?!\d))*|%/
121
130
 
122
131
  IDENT_HYPHEN_INTERP = /-(#\{)/
123
- STRING1_NOINTERP = /\"((?:[^\n\r\f\\"#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\"/
124
- STRING2_NOINTERP = /\'((?:[^\n\r\f\\'#]|#(?!\{)|\\#{NL}|#{ESCAPE})*)\'/
132
+ STRING1_NOINTERP = /\"((?:[^\n\r\f\\"#]|#(?!\{)|#{ESCAPE})*)\"/
133
+ STRING2_NOINTERP = /\'((?:[^\n\r\f\\'#]|#(?!\{)|#{ESCAPE})*)\'/
125
134
  STRING_NOINTERP = /#{STRING1_NOINTERP}|#{STRING2_NOINTERP}/
126
135
 
127
136
  STATIC_COMPONENT = /#{IDENT}|#{STRING_NOINTERP}|#{HEXCOLOR}|[+-]?#{NUMBER}|\!important/i
@@ -41,6 +41,24 @@ module Sass
41
41
  return type, directives
42
42
  end
43
43
 
44
+ def parse_keyframes_selector
45
+ init_scanner!
46
+ sel = expr!(:keyframes_selector)
47
+ expected("keyframes selector") unless @scanner.eos?
48
+ sel
49
+ end
50
+
51
+ # @see Parser#initialize
52
+ # @param allow_parent_ref [Boolean] Whether to allow the
53
+ # parent-reference selector, `&`, when parsing the document.
54
+ # @comment
55
+ # rubocop:disable ParameterLists
56
+ def initialize(str, filename, importer, line = 1, offset = 1, allow_parent_ref = true)
57
+ # rubocop:enable ParameterLists
58
+ super(str, filename, importer, line, offset)
59
+ @allow_parent_ref = allow_parent_ref
60
+ end
61
+
44
62
  private
45
63
 
46
64
  def moz_document_function
@@ -52,7 +70,7 @@ module Sass
52
70
 
53
71
  def variable; nil; end
54
72
  def script_value; nil; end
55
- def interpolation; nil; end
73
+ def interpolation(warn_for_color = false); nil; end
56
74
  def var_expr; nil; end
57
75
  def interp_string; (s = tok(STRING)) && [s]; end
58
76
  def interp_uri; (s = tok(URI)) && [s]; end
@@ -64,6 +82,285 @@ module Sass
64
82
  super
65
83
  end
66
84
 
85
+ def selector_comma_sequence
86
+ sel = selector
87
+ return unless sel
88
+ selectors = [sel]
89
+ ws = ''
90
+ while tok(/,/)
91
+ ws << str {ss}
92
+ if (sel = selector)
93
+ selectors << sel
94
+ if ws.include?("\n")
95
+ selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members)
96
+ end
97
+ ws = ''
98
+ end
99
+ end
100
+ Selector::CommaSequence.new(selectors)
101
+ end
102
+
103
+ def selector_string
104
+ sel = selector
105
+ return unless sel
106
+ sel.to_s
107
+ end
108
+
109
+ def selector
110
+ start_pos = source_position
111
+ # The combinator here allows the "> E" hack
112
+ val = combinator || simple_selector_sequence
113
+ return unless val
114
+ nl = str {ss}.include?("\n")
115
+ res = []
116
+ res << val
117
+ res << "\n" if nl
118
+
119
+ while (val = combinator || simple_selector_sequence)
120
+ res << val
121
+ res << "\n" if str {ss}.include?("\n")
122
+ end
123
+ seq = Selector::Sequence.new(res.compact)
124
+
125
+ if seq.members.any? {|sseq| sseq.is_a?(Selector::SimpleSequence) && sseq.subject?}
126
+ location = " of #{@filename}" if @filename
127
+ Sass::Util.sass_warn <<MESSAGE
128
+ DEPRECATION WARNING on line #{start_pos.line}, column #{start_pos.offset}#{location}:
129
+ The subject selector operator "!" is deprecated and will be removed in a future release.
130
+ This operator has been replaced by ":has()" in the CSS spec.
131
+ For example: #{seq.subjectless}
132
+ MESSAGE
133
+ end
134
+
135
+ seq
136
+ end
137
+
138
+ def combinator
139
+ tok(PLUS) || tok(GREATER) || tok(TILDE) || reference_combinator
140
+ end
141
+
142
+ def reference_combinator
143
+ return unless tok(/\//)
144
+ res = '/'
145
+ ns, name = expr!(:qualified_name)
146
+ res << ns << '|' if ns
147
+ res << name << tok!(/\//)
148
+ res
149
+ end
150
+
151
+ def simple_selector_sequence
152
+ start_pos = source_position
153
+ e = element_name || id_selector || class_selector || placeholder_selector || attrib ||
154
+ pseudo || parent_selector
155
+ return unless e
156
+ res = [e]
157
+
158
+ # The tok(/\*/) allows the "E*" hack
159
+ while (v = id_selector || class_selector || placeholder_selector ||
160
+ attrib || pseudo || (tok(/\*/) && Selector::Universal.new(nil)))
161
+ res << v
162
+ end
163
+
164
+ pos = @scanner.pos
165
+ line = @line
166
+ if (sel = str? {simple_selector_sequence})
167
+ @scanner.pos = pos
168
+ @line = line
169
+ begin
170
+ # If we see "*E", don't force a throw because this could be the
171
+ # "*prop: val" hack.
172
+ expected('"{"') if res.length == 1 && res[0].is_a?(Selector::Universal)
173
+ throw_error {expected('"{"')}
174
+ rescue Sass::SyntaxError => e
175
+ e.message << "\n\n\"#{sel}\" may only be used at the beginning of a compound selector."
176
+ raise e
177
+ end
178
+ end
179
+
180
+ Selector::SimpleSequence.new(res, tok(/!/), range(start_pos))
181
+ end
182
+
183
+ def parent_selector
184
+ return unless @allow_parent_ref && tok(/&/)
185
+ Selector::Parent.new(tok(NAME))
186
+ end
187
+
188
+ def class_selector
189
+ return unless tok(/\./)
190
+ @expected = "class name"
191
+ Selector::Class.new(tok!(IDENT))
192
+ end
193
+
194
+ def id_selector
195
+ return unless tok(/#(?!\{)/)
196
+ @expected = "id name"
197
+ Selector::Id.new(tok!(NAME))
198
+ end
199
+
200
+ def placeholder_selector
201
+ return unless tok(/%/)
202
+ @expected = "placeholder name"
203
+ Selector::Placeholder.new(tok!(IDENT))
204
+ end
205
+
206
+ def element_name
207
+ ns, name = Sass::Util.destructure(qualified_name(:allow_star_name))
208
+ return unless ns || name
209
+
210
+ if name == '*'
211
+ Selector::Universal.new(ns)
212
+ else
213
+ Selector::Element.new(name, ns)
214
+ end
215
+ end
216
+
217
+ def qualified_name(allow_star_name = false)
218
+ name = tok(IDENT) || tok(/\*/) || (tok?(/\|/) && "")
219
+ return unless name
220
+ return nil, name unless tok(/\|/)
221
+
222
+ return name, tok!(IDENT) unless allow_star_name
223
+ @expected = "identifier or *"
224
+ return name, tok(IDENT) || tok!(/\*/)
225
+ end
226
+
227
+ def attrib
228
+ return unless tok(/\[/)
229
+ ss
230
+ ns, name = attrib_name!
231
+ ss
232
+
233
+ op = tok(/=/) ||
234
+ tok(INCLUDES) ||
235
+ tok(DASHMATCH) ||
236
+ tok(PREFIXMATCH) ||
237
+ tok(SUFFIXMATCH) ||
238
+ tok(SUBSTRINGMATCH)
239
+ if op
240
+ @expected = "identifier or string"
241
+ ss
242
+ val = tok(IDENT) || tok!(STRING)
243
+ ss
244
+ end
245
+ flags = tok(IDENT) || tok(STRING)
246
+ tok!(/\]/)
247
+
248
+ Selector::Attribute.new(name, ns, op, val, flags)
249
+ end
250
+
251
+ def attrib_name!
252
+ if (name_or_ns = tok(IDENT))
253
+ # E, E|E
254
+ if tok(/\|(?!=)/)
255
+ ns = name_or_ns
256
+ name = tok(IDENT)
257
+ else
258
+ name = name_or_ns
259
+ end
260
+ else
261
+ # *|E or |E
262
+ ns = tok(/\*/) || ""
263
+ tok!(/\|/)
264
+ name = tok!(IDENT)
265
+ end
266
+ return ns, name
267
+ end
268
+
269
+ SELECTOR_PSEUDO_CLASSES = %w[not matches current any has host host-context].to_set
270
+
271
+ PREFIXED_SELECTOR_PSEUDO_CLASSES = %w[nth-child nth-last-child].to_set
272
+
273
+ def pseudo
274
+ s = tok(/::?/)
275
+ return unless s
276
+ @expected = "pseudoclass or pseudoelement"
277
+ name = tok!(IDENT)
278
+ if tok(/\(/)
279
+ ss
280
+ deprefixed = deprefix(name)
281
+ if s == ':' && SELECTOR_PSEUDO_CLASSES.include?(deprefixed)
282
+ sel = selector_comma_sequence
283
+ elsif s == ':' && PREFIXED_SELECTOR_PSEUDO_CLASSES.include?(deprefixed)
284
+ arg, sel = prefixed_selector_pseudo
285
+ else
286
+ arg = expr!(:pseudo_args)
287
+ end
288
+
289
+ tok!(/\)/)
290
+ end
291
+ Selector::Pseudo.new(s == ':' ? :class : :element, name, arg, sel)
292
+ end
293
+
294
+ def pseudo_args
295
+ arg = expr!(:pseudo_expr)
296
+ while tok(/,/)
297
+ arg << ',' << str {ss}
298
+ arg.concat expr!(:pseudo_expr)
299
+ end
300
+ arg
301
+ end
302
+
303
+ def pseudo_expr
304
+ res = pseudo_expr_token
305
+ return unless res
306
+ res << str {ss}
307
+ while (e = pseudo_expr_token)
308
+ res << e << str {ss}
309
+ end
310
+ res
311
+ end
312
+
313
+ def pseudo_expr_token
314
+ tok(PLUS) || tok(/[-*]/) || tok(NUMBER) || tok(STRING) || tok(IDENT)
315
+ end
316
+
317
+ def prefixed_selector_pseudo
318
+ prefix = str do
319
+ expr = str {expr!(:a_n_plus_b)}
320
+ ss
321
+ return expr, nil unless tok(/of/)
322
+ ss
323
+ end
324
+ return prefix, expr!(:selector_comma_sequence)
325
+ end
326
+
327
+ def a_n_plus_b
328
+ if (parity = tok(/even|odd/i))
329
+ return parity
330
+ end
331
+
332
+ if tok(/[+-]?[0-9]+/)
333
+ ss
334
+ return true unless tok(/n/)
335
+ else
336
+ return unless tok(/[+-]?n/i)
337
+ end
338
+ ss
339
+
340
+ return true unless tok(/[+-]/)
341
+ ss
342
+ @expected = "number"
343
+ tok!(/[0-9]+/)
344
+ true
345
+ end
346
+
347
+ def keyframes_selector
348
+ ss
349
+ str do
350
+ return unless keyframes_selector_component
351
+ ss
352
+ while tok(/,/)
353
+ ss
354
+ expr!(:keyframes_selector_component)
355
+ ss
356
+ end
357
+ end
358
+ end
359
+
360
+ def keyframes_selector_component
361
+ tok(/from|to/i) || tok(PERCENTAGE)
362
+ end
363
+
67
364
  @sass_script_parser = Class.new(Sass::Script::CssParser)
68
365
  @sass_script_parser.send(:include, ScriptParser)
69
366
  end
@@ -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 `to_a`
7
- # method that returns an array of strings and script nodes.
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
- members.any? {|m| m.is_a?(AbstractSequence) ? m.has_placeholder? : m.is_a?(Placeholder)}
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
- # Converts the selector into a string. This is the standard selector
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
- to_a.map {|e| e.is_a?(Sass::Script::Tree::Node) ? "\#{#{e.to_sass}}" : e}.join
76
+ Sass::Util.abstract(self)
75
77
  end
76
78
 
77
- # Returns the specificity of the selector as an integer. The base is given
78
- # by {Sass::Selector::SPECIFICITY_BASE}.
79
+ # Returns the specificity of the selector.
79
80
  #
80
- # @return [Fixnum]
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
- spec = 0
89
- arr.map {|m| spec += m.is_a?(String) ? 0 : m.specificity}
90
- spec
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,101 @@ 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
- extended = seq.do_extend(extends, parent_directives)
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
+ next if component == "\n"
146
+ Sass::Script::Value::String.new(component.to_s)
147
+ end.compact, :space)
148
+ end, :comma)
149
+ end
150
+
70
151
  # Returns a string representation of the sequence.
71
152
  # This is basically the selector string.
72
153
  #
@@ -75,11 +156,9 @@ module Sass
75
156
  members.map {|m| m.inspect}.join(", ")
76
157
  end
77
158
 
78
- # @see Simple#to_a
79
- def to_a
80
- arr = Sass::Util.intersperse(@members.map {|m| m.to_a}, ", ").flatten
81
- arr.delete("\n")
82
- arr
159
+ # @see AbstractSequence#to_s
160
+ def to_s
161
+ @members.join(", ").gsub(", \n", ",\n")
83
162
  end
84
163
 
85
164
  private