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.
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