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
@@ -82,6 +82,8 @@ module Sass
82
82
  # with identifier names.
83
83
  IDENT_OP_NAMES = OP_NAMES.select {|k, v| k =~ /^\w+/}
84
84
 
85
+ PARSEABLE_NUMBER = /(?:(\d*\.\d+)|(\d+))(?:[eE]([+-]?\d+))?(#{UNIT})?/
86
+
85
87
  # A hash of regular expressions that are used for tokenizing.
86
88
  REGULAR_EXPRESSIONS = {
87
89
  :whitespace => /\s+/,
@@ -89,9 +91,11 @@ module Sass
89
91
  :single_line_comment => SINGLE_LINE_COMMENT,
90
92
  :variable => /(\$)(#{IDENT})/,
91
93
  :ident => /(#{IDENT})(\()?/,
92
- :number => /(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
93
- :unary_minus_number => /-(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
94
+ :number => PARSEABLE_NUMBER,
95
+ :unary_minus_number => /-#{PARSEABLE_NUMBER}/,
94
96
  :color => HEXCOLOR,
97
+ :id => /##{IDENT}/,
98
+ :selector => /&/,
95
99
  :ident_op => /(#{Regexp.union(*IDENT_OP_NAMES.map do |s|
96
100
  Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")
97
101
  end)})/,
@@ -102,7 +106,7 @@ module Sass
102
106
  private
103
107
 
104
108
  def string_re(open, close)
105
- /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/
109
+ /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m
106
110
  end
107
111
  end
108
112
 
@@ -246,13 +250,19 @@ module Sass
246
250
  end
247
251
 
248
252
  def token
249
- if after_interpolation? && (interp_type = @interpolation_stack.pop)
250
- return string(interp_type, true)
253
+ if after_interpolation? && (interp = @interpolation_stack.pop)
254
+ interp_type, interp_value = interp
255
+ if interp_type == :special_fun
256
+ return special_fun_body(interp_value)
257
+ else
258
+ raise "[BUG]: Unknown interp_type #{interp_type}" unless interp_type == :string
259
+ return string(interp_value, true)
260
+ end
251
261
  end
252
262
 
253
- variable || string(:double, false) || string(:single, false) || number || color ||
254
- string(:uri, false) || raw(UNICODERANGE) || special_fun || special_val || ident_op ||
255
- ident || op
263
+ variable || string(:double, false) || string(:single, false) || number || id || color ||
264
+ selector || string(:uri, false) || raw(UNICODERANGE) || special_fun || special_val ||
265
+ ident_op || ident || op
256
266
  end
257
267
 
258
268
  def variable
@@ -271,18 +281,28 @@ module Sass
271
281
  end
272
282
 
273
283
  def string(re, open)
284
+ line, offset = @line, @offset
274
285
  return unless scan(STRING_REGULAR_EXPRESSIONS[re][open])
286
+ if @scanner[0] =~ /([^\\]|^)\n/
287
+ filename = @options[:filename]
288
+ Sass::Util.sass_warn <<MESSAGE
289
+ DEPRECATION WARNING on line #{line}, column #{offset}#{" of #{filename}" if filename}:
290
+ Unescaped multiline strings are deprecated and will be removed in a future version of Sass.
291
+ To include a newline in a string, use "\\a" or "\\a " as in CSS.
292
+ MESSAGE
293
+ end
294
+
275
295
  if @scanner[2] == '#{' # '
276
296
  @scanner.pos -= 2 # Don't actually consume the #{
277
297
  @offset -= 2
278
- @interpolation_stack << re
298
+ @interpolation_stack << [:string, re]
279
299
  end
280
300
  str =
281
301
  if re == :uri
282
302
  url = "#{'url(' unless open}#{@scanner[1]}#{')' unless @scanner[2] == '#{'}"
283
303
  Script::Value::String.new(url)
284
304
  else
285
- Script::Value::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)
305
+ Script::Value::String.new(Sass::Script::Value::String.value(@scanner[1]), :string)
286
306
  end
287
307
  [:string, str]
288
308
  end
@@ -296,10 +316,12 @@ module Sass
296
316
  # minus logic in the parser instead.
297
317
  if @scanner.peek(1) == '-'
298
318
  return if @scanner.pos == 0
299
- @scanner.pos -= 1
300
- # Don't use @scanner.scan so we don't mess up the match data.
301
- unary_minus_allowed = @scanner.peek(1) =~ /\s/
302
- @scanner.pos += 1
319
+ unary_minus_allowed =
320
+ case @scanner.string[@scanner.pos - 1, 1]
321
+ when /\s/; true
322
+ when '/'; @scanner.pos != 1 && @scanner.string[@scanner.pos - 2, 1] == '*'
323
+ else; false
324
+ end
303
325
 
304
326
  return unless unary_minus_allowed
305
327
  return unless scan(REGULAR_EXPRESSIONS[:unary_minus_number])
@@ -310,33 +332,76 @@ module Sass
310
332
  end
311
333
 
312
334
  value = (@scanner[1] ? @scanner[1].to_f : @scanner[2].to_i) * (minus ? -1 : 1)
313
- script_number = Script::Value::Number.new(value, Array(@scanner[3]))
335
+ value *= 10**@scanner[3].to_i if @scanner[3]
336
+ script_number = Script::Value::Number.new(value, Array(@scanner[4]))
314
337
  [:number, script_number]
315
338
  end
316
339
 
340
+ def id
341
+ # Colors and ids are tough to tell apart, because they overlap but
342
+ # neither is a superset of the other. "#xyz" is an id but not a color,
343
+ # "#000" is a color but not an id, "#abc" is both, and "#0" is neither.
344
+ # We need to handle all these cases correctly.
345
+ #
346
+ # To do so, we first try to parse something as an id. If this works and
347
+ # the id is also a valid color, we return the color. Otherwise, we
348
+ # return the id. If it didn't parse as an id, we then try to parse it as
349
+ # a color. If *this* works, we return the color, and if it doesn't we
350
+ # give up and throw an error.
351
+ #
352
+ # IDs in properties are used in the Basic User Interface Module
353
+ # (http://www.w3.org/TR/css3-ui/).
354
+ return unless scan(REGULAR_EXPRESSIONS[:id])
355
+ if @scanner[0] =~ /^\#[0-9a-fA-F]+$/ && (@scanner[0].length == 4 || @scanner[0].length == 7)
356
+ return [:color, Script::Value::Color.from_hex(@scanner[0])]
357
+ end
358
+ [:ident, @scanner[0]]
359
+ end
360
+
317
361
  def color
318
- s = scan(REGULAR_EXPRESSIONS[:color])
319
- return unless s
320
- raise Sass::SyntaxError.new(<<MESSAGE.rstrip) unless s.size == 4 || s.size == 7
321
- Colors must have either three or six digits: '#{s}'
322
- MESSAGE
323
- script_color = Script::Value::Color.from_hex(s)
362
+ return unless @scanner.match?(REGULAR_EXPRESSIONS[:color])
363
+ return unless @scanner[0].length == 4 || @scanner[0].length == 7
364
+ script_color = Script::Value::Color.from_hex(scan(REGULAR_EXPRESSIONS[:color]))
324
365
  [:color, script_color]
325
366
  end
326
367
 
368
+ def selector
369
+ start_pos = source_position
370
+ return unless scan(REGULAR_EXPRESSIONS[:selector])
371
+ script_selector = Script::Tree::Selector.new
372
+ script_selector.source_range = range(start_pos)
373
+ [:selector, script_selector]
374
+ end
375
+
327
376
  def special_fun
328
- str1 = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
329
- return unless str1
330
- str2, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
331
- c = str2.count("\n")
332
- old_line = @line
333
- old_offset = @offset
334
- @line += c
335
- @offset = c == 0 ? @offset + str2.size : str2[/\n([^\n]*)/, 1].size + 1
336
- [:special_fun,
337
- Sass::Util.merge_adjacent_strings(
338
- [str1] + Sass::Engine.parse_interp(str2, old_line, old_offset, @options)),
339
- str1.size + str2.size]
377
+ prefix = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
378
+ return unless prefix
379
+ special_fun_body(1, prefix)
380
+ end
381
+
382
+ def special_fun_body(parens, prefix = nil)
383
+ str = prefix || ''
384
+ while (scanned = scan(/.*?([()]|\#\{)/m))
385
+ str << scanned
386
+ if scanned[-1] == ?(
387
+ parens += 1
388
+ next
389
+ elsif scanned[-1] == ?)
390
+ parens -= 1
391
+ next unless parens == 0
392
+ else
393
+ raise "[BUG] Unreachable" unless @scanner[1] == '#{' # '
394
+ str.slice!(-2..-1)
395
+ @scanner.pos -= 2 # Don't actually consume the #{
396
+ @offset -= 2
397
+ @interpolation_stack << [:special_fun, parens]
398
+ end
399
+
400
+ return [:special_fun, Sass::Script::Value::String.new(str)]
401
+ end
402
+
403
+ scan(/.*/)
404
+ expected!('")"')
340
405
  end
341
406
 
342
407
  def special_val
@@ -368,7 +433,7 @@ MESSAGE
368
433
  return unless str
369
434
  c = str.count("\n")
370
435
  @line += c
371
- @offset = (c == 0 ? @offset + str.size : str.size - str.rindex("\n") + 1)
436
+ @offset = (c == 0 ? @offset + str.size : str.size - str.rindex("\n"))
372
437
  str
373
438
  end
374
439
 
@@ -36,13 +36,19 @@ module Sass
36
36
  # which signals the end of an interpolated segment,
37
37
  # it returns rather than throwing an error.
38
38
  #
39
+ # @param warn_for_color [Boolean] Whether raw color values passed to
40
+ # interoplation should cause a warning.
39
41
  # @return [Script::Tree::Node] The root node of the parse tree
40
42
  # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
41
- def parse_interpolated
43
+ def parse_interpolated(warn_for_color = false)
44
+ # Start two characters back to compensate for #{
45
+ start_pos = Sass::Source::Position.new(line, offset - 2)
42
46
  expr = assert_expr :expr
43
47
  assert_tok :end_interpolation
48
+ expr = Sass::Script::Tree::Interpolation.new(
49
+ nil, expr, nil, !:wb, !:wa, !:originally_text, warn_for_color)
44
50
  expr.options = @options
45
- expr
51
+ node(expr, start_pos)
46
52
  rescue Sass::SyntaxError => e
47
53
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
48
54
  raise e
@@ -226,8 +232,8 @@ module Sass
226
232
  return other_interp
227
233
  end
228
234
 
229
- start_pos = source_position
230
- e = node(Tree::Operation.new(e, assert_expr(#{sub.inspect}), tok.type), start_pos)
235
+ e = node(Tree::Operation.new(e, assert_expr(#{sub.inspect}), tok.type),
236
+ e.source_range.start_pos)
231
237
  end
232
238
  e
233
239
  end
@@ -340,7 +346,8 @@ RUBY
340
346
  e = first
341
347
  while (interp = try_tok(:begin_interpolation))
342
348
  wb = @lexer.whitespace?(interp)
343
- mid = parse_interpolated
349
+ mid = assert_expr :expr
350
+ assert_tok :end_interpolation
344
351
  wa = @lexer.whitespace?
345
352
  e = node(
346
353
  Script::Tree::Interpolation.new(e, mid, space, wb, wa),
@@ -382,7 +389,7 @@ RUBY
382
389
 
383
390
  name = @lexer.next
384
391
  if (color = Sass::Script::Value::Color::COLOR_NAMES[name.value.downcase])
385
- literal_node(Sass::Script::Value::Color.new(color), name.source_range)
392
+ literal_node(Sass::Script::Value::Color.new(color, name.value), name.source_range)
386
393
  elsif name.value == "true"
387
394
  literal_node(Sass::Script::Value::Bool.new(true), name.source_range)
388
395
  elsif name.value == "false"
@@ -487,22 +494,14 @@ RUBY
487
494
  end
488
495
 
489
496
  def special_fun
490
- start_pos = source_position
491
- tok = try_tok(:special_fun)
492
- return paren unless tok
493
- first = literal_node(Script::Value::String.new(tok.value.first),
494
- start_pos, start_pos.after(tok.value.first))
495
- Sass::Util.enum_slice(tok.value[1..-1], 2).inject(first) do |l, (i, r)|
496
- end_pos = i.source_range.end_pos
497
- end_pos = end_pos.after(r) if r
498
- node(
499
- Script::Tree::Interpolation.new(
500
- l, i,
501
- r && literal_node(Script::Value::String.new(r),
502
- i.source_range.end_pos, end_pos),
503
- false, false),
504
- start_pos, end_pos)
505
- end
497
+ first = try_tok(:special_fun)
498
+ return paren unless first
499
+ str = literal_node(first.value, first.source_range)
500
+ return str unless try_tok(:begin_interpolation)
501
+ mid = parse_interpolated
502
+ last = assert_expr(:special_fun)
503
+ node(Tree::Interpolation.new(str, mid, last, false, false),
504
+ first.source_range.start_pos)
506
505
  end
507
506
 
508
507
  def paren
@@ -530,7 +529,8 @@ RUBY
530
529
  return number unless first
531
530
  str = literal_node(first.value, first.source_range)
532
531
  return str unless try_tok(:begin_interpolation)
533
- mid = parse_interpolated
532
+ mid = assert_expr :expr
533
+ assert_tok :end_interpolation
534
534
  last = assert_expr(:string)
535
535
  node(Tree::StringInterpolation.new(str, mid, last), first.source_range.start_pos)
536
536
  end
@@ -563,6 +563,7 @@ RUBY
563
563
  :mixin_arglist => "mixin argument",
564
564
  :fn_arglist => "function argument",
565
565
  :splat => "...",
566
+ :special_fun => '")"',
566
567
  }
567
568
 
568
569
  def assert_expr(name, expected = nil)
@@ -214,7 +214,7 @@ module Sass::Script::Tree
214
214
  elsif deprecated_argname && keywords.has_key?(deprecated_argname)
215
215
  deprecated_argname = keywords.denormalize(deprecated_argname)
216
216
  Sass::Util.sass_warn("DEPRECATION WARNING: The `$#{deprecated_argname}' argument for " +
217
- "`#{name}()' has been renamed to `$#{argname}'.")
217
+ "`#{@name}()' has been renamed to `$#{argname}'.")
218
218
  keywords.delete(deprecated_argname)
219
219
  else
220
220
  raise Sass::SyntaxError.new("Function #{name} requires an argument named $#{argname}")
@@ -23,6 +23,10 @@ module Sass::Script::Tree
23
23
  # SassScript.
24
24
  attr_reader :originally_text
25
25
 
26
+ # @return [Boolean] Whether a color value passed to the interpolation should
27
+ # generate a warning.
28
+ attr_reader :warn_for_color
29
+
26
30
  # Interpolation in a property is of the form `before #{mid} after`.
27
31
  #
28
32
  # @param before [Node] See {Interpolation#before}
@@ -31,9 +35,10 @@ module Sass::Script::Tree
31
35
  # @param wb [Boolean] See {Interpolation#whitespace_before}
32
36
  # @param wa [Boolean] See {Interpolation#whitespace_after}
33
37
  # @param originally_text [Boolean] See {Interpolation#originally_text}
38
+ # @param warn_for_color [Boolean] See {Interpolation#warn_for_color}
34
39
  # @comment
35
40
  # rubocop:disable ParameterLists
36
- def initialize(before, mid, after, wb, wa, originally_text = false)
41
+ def initialize(before, mid, after, wb, wa, originally_text = false, warn_for_color = false)
37
42
  # rubocop:enable ParameterLists
38
43
  @before = before
39
44
  @mid = mid
@@ -41,6 +46,7 @@ module Sass::Script::Tree
41
46
  @whitespace_before = wb
42
47
  @whitespace_after = wa
43
48
  @originally_text = originally_text
49
+ @warn_for_color = warn_for_color
44
50
  end
45
51
 
46
52
  # @return [String] A human-readable s-expression representation of the interpolation
@@ -90,8 +96,20 @@ module Sass::Script::Tree
90
96
  res = ""
91
97
  res << @before.perform(environment).to_s if @before
92
98
  res << " " if @before && @whitespace_before
99
+
93
100
  val = @mid.perform(environment)
94
- res << (val.is_a?(Sass::Script::Value::String) ? val.value : val.to_s)
101
+ if @warn_for_color && val.is_a?(Sass::Script::Value::Color) && val.name
102
+ alternative = Operation.new(Sass::Script::Value::String.new("", :string), @mid, :plus)
103
+ Sass::Util.sass_warn <<MESSAGE
104
+ WARNING on line #{line}, column #{source_range.start_pos.offset}#{" of #{filename}" if filename}:
105
+ You probably don't mean to use the color value `#{val}' in interpolation here.
106
+ It may end up represented as #{val.inspect}, which will likely produce invalid CSS.
107
+ Always quote color names when using them as strings (for example, "#{val}").
108
+ If you really want to use the color value here, use `#{alternative.to_sass}'.
109
+ MESSAGE
110
+ end
111
+
112
+ res << val.to_s(:quote => :none)
95
113
  res << " " if @after && @whitespace_after
96
114
  res << @after.perform(environment).to_s if @after
97
115
  opts(Sass::Script::Value::String.new(res))
@@ -0,0 +1,26 @@
1
+ module Sass::Script::Tree
2
+ # A SassScript node that will resolve to the current selector.
3
+ class Selector < Node
4
+ def initialize; end
5
+
6
+ def children
7
+ []
8
+ end
9
+
10
+ def to_sass(opts = {})
11
+ '&'
12
+ end
13
+
14
+ def deep_copy
15
+ dup
16
+ end
17
+
18
+ protected
19
+
20
+ def _perform(environment)
21
+ selector = environment.selector
22
+ return opts(Sass::Script::Value::Null.new) unless selector
23
+ opts(selector.to_sass_script)
24
+ end
25
+ end
26
+ end
@@ -81,7 +81,7 @@ module Sass::Script::Tree
81
81
  before = @before.perform(environment)
82
82
  res << before.value
83
83
  mid = @mid.perform(environment)
84
- res << (mid.is_a?(Sass::Script::Value::String) ? mid.value : mid.to_s)
84
+ res << (mid.is_a?(Sass::Script::Value::String) ? mid.value : mid.to_s(:quote => :none))
85
85
  res << @after.perform(environment).value
86
86
  opts(Sass::Script::Value::String.new(res, before.type))
87
87
  end
@@ -13,3 +13,4 @@ require 'sass/script/tree/string_interpolation'
13
13
  require 'sass/script/tree/literal'
14
14
  require 'sass/script/tree/list_literal'
15
15
  require 'sass/script/tree/map_literal'
16
+ require 'sass/script/tree/selector'
@@ -20,7 +20,8 @@ module Sass::Script::Value
20
20
  #
21
21
  # @param value [Object] The object for \{#value}
22
22
  def initialize(value = nil)
23
- @value = value.freeze
23
+ value.freeze unless value.nil? || value == true || value == false
24
+ @value = value
24
25
  end
25
26
 
26
27
  # Sets the options hash for this node,
@@ -95,10 +96,8 @@ MSG
95
96
  # @return [Script::Value::String] A string containing both values
96
97
  # without any separation
97
98
  def plus(other)
98
- if other.is_a?(Sass::Script::Value::String)
99
- return Sass::Script::Value::String.new(to_s + other.value, other.type)
100
- end
101
- Sass::Script::Value::String.new(to_s + other.to_s)
99
+ type = other.is_a?(Sass::Script::Value::String) ? other.type : :identifier
100
+ Sass::Script::Value::String.new(to_s(:quote => :none) + other.to_s(:quote => :none), type)
102
101
  end
103
102
 
104
103
  # The SassScript `-` operation.
@@ -212,6 +211,9 @@ MSG
212
211
  # Returns the string representation of this value
213
212
  # as it would be output to the CSS document.
214
213
  #
214
+ # @options opts :quote [String]
215
+ # The preferred quote style for quoted strings. If `:none`, strings are
216
+ # always emitted unquoted.
215
217
  # @return [String]
216
218
  def to_s(opts = {})
217
219
  Sass::Util.abstract(self)
@@ -20,11 +20,6 @@ module Sass::Script::Value
20
20
  value ? TRUE : FALSE
21
21
  end
22
22
 
23
- def eq(other)
24
- return other.eq(self) if other.is_a?(DeprecatedFalse)
25
- super
26
- end
27
-
28
23
  # The Ruby value of the boolean.
29
24
  #
30
25
  # @return [Boolean]
@@ -151,6 +151,7 @@ module Sass::Script::Value
151
151
  'powderblue' => 0xB0E0E6FF,
152
152
  'purple' => 0x800080FF,
153
153
  'red' => 0xFF0000FF,
154
+ 'rebeccapurple' => 0x663399FF,
154
155
  'rosybrown' => 0xBC8F8FFF,
155
156
  'royalblue' => 0x4169E1FF,
156
157
  'saddlebrown' => 0x8B4513FF,
@@ -187,35 +188,43 @@ module Sass::Script::Value
187
188
  # different ruby implementations and versions vary on the ordering of the result of invert.
188
189
  COLOR_NAMES.update(ALTERNATE_COLOR_NAMES).freeze
189
190
 
191
+ # The user's original representation of the color.
192
+ #
193
+ # @return [String]
194
+ attr_reader :representation
195
+
190
196
  # Constructs an RGB or HSL color object,
191
197
  # optionally with an alpha channel.
192
198
  #
193
- # The RGB values must be between 0 and 255.
194
- # The saturation and lightness values must be between 0 and 100.
195
- # The alpha value must be between 0 and 1.
199
+ # RGB values are clipped within 0 and 255.
200
+ # Saturation and lightness values are clipped within 0 and 100.
201
+ # The alpha value is clipped within 0 and 1.
196
202
  #
197
203
  # @raise [Sass::SyntaxError] if any color value isn't in the specified range
198
204
  #
199
205
  # @overload initialize(attrs)
200
- # The attributes are specified as a hash.
201
- # This hash must contain either `:hue`, `:saturation`, and `:value` keys,
202
- # or `:red`, `:green`, and `:blue` keys.
203
- # It cannot contain both HSL and RGB keys.
204
- # It may also optionally contain an `:alpha` key.
206
+ # The attributes are specified as a hash. This hash must contain either
207
+ # `:hue`, `:saturation`, and `:value` keys, or `:red`, `:green`, and
208
+ # `:blue` keys. It cannot contain both HSL and RGB keys. It may also
209
+ # optionally contain an `:alpha` key, and a `:representation` key
210
+ # indicating the original representation of the color that the user wrote
211
+ # in their stylesheet.
205
212
  #
206
213
  # @param attrs [{Symbol => Numeric}] A hash of color attributes to values
207
214
  # @raise [ArgumentError] if not enough attributes are specified,
208
215
  # or both RGB and HSL attributes are specified
209
216
  #
210
- # @overload initialize(rgba)
217
+ # @overload initialize(rgba, [representation])
211
218
  # The attributes are specified as an array.
212
219
  # This overload only supports RGB or RGBA colors.
213
220
  #
214
221
  # @param rgba [Array<Numeric>] A three- or four-element array
215
222
  # of the red, green, blue, and optionally alpha values (respectively)
216
223
  # of the color
224
+ # @param representation [String] The original representation of the color
225
+ # that the user wrote in their stylesheet.
217
226
  # @raise [ArgumentError] if not enough attributes are specified
218
- def initialize(attrs, allow_both_rgb_and_hsl = false)
227
+ def initialize(attrs, representation = nil, allow_both_rgb_and_hsl = false)
219
228
  super(nil)
220
229
 
221
230
  if attrs.is_a?(Array)
@@ -226,6 +235,7 @@ module Sass::Script::Value
226
235
  red, green, blue = attrs[0...3].map {|c| c.to_i}
227
236
  @attrs = {:red => red, :green => green, :blue => blue}
228
237
  @attrs[:alpha] = attrs[3] ? attrs[3].to_f : 1
238
+ @representation = representation
229
239
  else
230
240
  attrs = attrs.reject {|k, v| v.nil?}
231
241
  hsl = [:hue, :saturation, :lightness] & attrs.keys
@@ -243,21 +253,20 @@ module Sass::Script::Value
243
253
  @attrs = attrs
244
254
  @attrs[:hue] %= 360 if @attrs[:hue]
245
255
  @attrs[:alpha] ||= 1
256
+ @representation = @attrs.delete(:representation)
246
257
  end
247
258
 
248
259
  [:red, :green, :blue].each do |k|
249
260
  next if @attrs[k].nil?
250
- @attrs[k] = @attrs[k].to_i
251
- Sass::Util.check_range("#{k.to_s.capitalize} value", 0..255, @attrs[k])
261
+ @attrs[k] = Sass::Util.restrict(@attrs[k].to_i, 0..255)
252
262
  end
253
263
 
254
264
  [:saturation, :lightness].each do |k|
255
265
  next if @attrs[k].nil?
256
- value = Number.new(@attrs[k], ['%']) # Get correct unit for error messages
257
- @attrs[k] = Sass::Util.check_range("#{k.to_s.capitalize}", 0..100, value, '%')
266
+ @attrs[k] = Sass::Util.restrict(@attrs[k], 0..100)
258
267
  end
259
268
 
260
- @attrs[:alpha] = Sass::Util.check_range("Alpha channel", 0..1, @attrs[:alpha])
269
+ @attrs[:alpha] = Sass::Util.restrict(@attrs[:alpha], 0..1)
261
270
  end
262
271
 
263
272
  # Create a new color from a valid CSS hex string.
@@ -273,7 +282,9 @@ module Sass::Script::Value
273
282
  red = $1.ljust(2, $1).to_i(16)
274
283
  green = $2.ljust(2, $2).to_i(16)
275
284
  blue = $3.ljust(2, $3).to_i(16)
276
- attrs = {:red => red, :green => green, :blue => blue}
285
+
286
+ hex_string = '##{hex_string}' unless hex_string[0] == ?#
287
+ attrs = {:red => red, :green => green, :blue => blue, :representation => hex_string}
277
288
  attrs[:alpha] = alpha if alpha
278
289
  new(attrs)
279
290
  end
@@ -428,7 +439,7 @@ module Sass::Script::Value
428
439
  end
429
440
  attrs[:alpha] ||= alpha
430
441
 
431
- Color.new(attrs, :allow_both_rgb_and_hsl)
442
+ Color.new(attrs, nil, :allow_both_rgb_and_hsl)
432
443
  end
433
444
 
434
445
  # The SassScript `+` operation.
@@ -549,7 +560,8 @@ module Sass::Script::Value
549
560
  # @return [String] The string representation
550
561
  def to_s(opts = {})
551
562
  return smallest if options[:style] == :compressed
552
- return COLOR_NAMES_REVERSE[rgba] if COLOR_NAMES_REVERSE[rgba]
563
+ return representation if representation
564
+ return name if name
553
565
  alpha? ? rgba_str : hex_str
554
566
  end
555
567
  alias_method :to_sass, :to_s
@@ -561,13 +573,19 @@ module Sass::Script::Value
561
573
  alpha? ? rgba_str : hex_str
562
574
  end
563
575
 
576
+ # Returns the color's name, if it has one.
577
+ #
578
+ # @return [String, nil]
579
+ def name
580
+ COLOR_NAMES_REVERSE[rgba]
581
+ end
582
+
564
583
  private
565
584
 
566
585
  def smallest
567
586
  small_explicit_str = alpha? ? rgba_str : hex_str.gsub(/^#(.)\1(.)\2(.)\3$/, '#\1\2\3')
568
- return small_explicit_str unless (color = COLOR_NAMES_REVERSE[rgba]) &&
569
- color.size <= small_explicit_str.size
570
- color
587
+ [representation, COLOR_NAMES_REVERSE[rgba], small_explicit_str].
588
+ compact.min_by {|str| str.size}
571
589
  end
572
590
 
573
591
  def rgba_str