sass 3.4.0 → 3.4.25

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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -1
  3. data/CODE_OF_CONDUCT.md +10 -0
  4. data/CONTRIBUTING.md +148 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +26 -20
  7. data/Rakefile +103 -20
  8. data/VERSION +1 -1
  9. data/VERSION_DATE +1 -1
  10. data/extra/sass-spec-ref.sh +32 -0
  11. data/extra/update_watch.rb +1 -1
  12. data/lib/sass/cache_stores/filesystem.rb +7 -7
  13. data/lib/sass/cache_stores/memory.rb +4 -5
  14. data/lib/sass/callbacks.rb +2 -2
  15. data/lib/sass/css.rb +11 -10
  16. data/lib/sass/deprecation.rb +55 -0
  17. data/lib/sass/engine.rb +83 -38
  18. data/lib/sass/environment.rb +26 -2
  19. data/lib/sass/error.rb +12 -12
  20. data/lib/sass/exec/base.rb +15 -3
  21. data/lib/sass/exec/sass_convert.rb +34 -15
  22. data/lib/sass/exec/sass_scss.rb +23 -7
  23. data/lib/sass/features.rb +2 -2
  24. data/lib/sass/importers/base.rb +1 -1
  25. data/lib/sass/importers/deprecated_path.rb +51 -0
  26. data/lib/sass/importers/filesystem.rb +24 -16
  27. data/lib/sass/importers.rb +1 -0
  28. data/lib/sass/logger/base.rb +8 -2
  29. data/lib/sass/logger/delayed.rb +50 -0
  30. data/lib/sass/logger.rb +8 -3
  31. data/lib/sass/plugin/compiler.rb +42 -25
  32. data/lib/sass/plugin/configuration.rb +38 -22
  33. data/lib/sass/plugin/merb.rb +2 -2
  34. data/lib/sass/plugin/rack.rb +3 -3
  35. data/lib/sass/plugin/rails.rb +1 -1
  36. data/lib/sass/plugin/staleness_checker.rb +3 -3
  37. data/lib/sass/plugin.rb +3 -2
  38. data/lib/sass/script/css_parser.rb +2 -3
  39. data/lib/sass/script/css_variable_warning.rb +52 -0
  40. data/lib/sass/script/functions.rb +140 -73
  41. data/lib/sass/script/lexer.rb +37 -22
  42. data/lib/sass/script/parser.rb +235 -40
  43. data/lib/sass/script/tree/funcall.rb +12 -5
  44. data/lib/sass/script/tree/interpolation.rb +109 -4
  45. data/lib/sass/script/tree/list_literal.rb +31 -4
  46. data/lib/sass/script/tree/literal.rb +4 -0
  47. data/lib/sass/script/tree/node.rb +21 -3
  48. data/lib/sass/script/tree/operation.rb +54 -1
  49. data/lib/sass/script/tree/string_interpolation.rb +58 -37
  50. data/lib/sass/script/tree/variable.rb +1 -1
  51. data/lib/sass/script/value/base.rb +10 -9
  52. data/lib/sass/script/value/color.rb +42 -24
  53. data/lib/sass/script/value/helpers.rb +16 -6
  54. data/lib/sass/script/value/map.rb +1 -1
  55. data/lib/sass/script/value/number.rb +52 -19
  56. data/lib/sass/script/value/string.rb +46 -5
  57. data/lib/sass/script.rb +3 -3
  58. data/lib/sass/scss/css_parser.rb +16 -2
  59. data/lib/sass/scss/parser.rb +120 -75
  60. data/lib/sass/scss/rx.rb +9 -10
  61. data/lib/sass/scss/static_parser.rb +19 -14
  62. data/lib/sass/scss.rb +0 -2
  63. data/lib/sass/selector/abstract_sequence.rb +8 -6
  64. data/lib/sass/selector/comma_sequence.rb +25 -9
  65. data/lib/sass/selector/pseudo.rb +45 -35
  66. data/lib/sass/selector/sequence.rb +54 -18
  67. data/lib/sass/selector/simple.rb +11 -11
  68. data/lib/sass/selector/simple_sequence.rb +34 -15
  69. data/lib/sass/selector.rb +7 -10
  70. data/lib/sass/shared.rb +1 -1
  71. data/lib/sass/source/map.rb +7 -4
  72. data/lib/sass/source/position.rb +4 -4
  73. data/lib/sass/stack.rb +2 -2
  74. data/lib/sass/supports.rb +8 -10
  75. data/lib/sass/tree/comment_node.rb +1 -1
  76. data/lib/sass/tree/css_import_node.rb +9 -1
  77. data/lib/sass/tree/function_node.rb +8 -3
  78. data/lib/sass/tree/import_node.rb +6 -5
  79. data/lib/sass/tree/node.rb +5 -3
  80. data/lib/sass/tree/prop_node.rb +5 -6
  81. data/lib/sass/tree/rule_node.rb +14 -4
  82. data/lib/sass/tree/visitors/check_nesting.rb +18 -22
  83. data/lib/sass/tree/visitors/convert.rb +43 -26
  84. data/lib/sass/tree/visitors/cssize.rb +5 -1
  85. data/lib/sass/tree/visitors/deep_copy.rb +1 -1
  86. data/lib/sass/tree/visitors/extend.rb +15 -13
  87. data/lib/sass/tree/visitors/perform.rb +42 -17
  88. data/lib/sass/tree/visitors/set_options.rb +1 -1
  89. data/lib/sass/tree/visitors/to_css.rb +58 -30
  90. data/lib/sass/util/multibyte_string_scanner.rb +0 -2
  91. data/lib/sass/util/normalized_map.rb +0 -1
  92. data/lib/sass/util/subset_map.rb +1 -2
  93. data/lib/sass/util.rb +125 -68
  94. data/lib/sass/version.rb +2 -2
  95. data/lib/sass.rb +10 -3
  96. data/test/sass/compiler_test.rb +6 -2
  97. data/test/sass/conversion_test.rb +187 -53
  98. data/test/sass/css2sass_test.rb +50 -1
  99. data/test/sass/css_variable_test.rb +132 -0
  100. data/test/sass/engine_test.rb +207 -61
  101. data/test/sass/exec_test.rb +10 -0
  102. data/test/sass/extend_test.rb +101 -29
  103. data/test/sass/functions_test.rb +60 -9
  104. data/test/sass/importer_test.rb +9 -0
  105. data/test/sass/more_templates/more1.sass +10 -10
  106. data/test/sass/more_templates/more_import.sass +2 -2
  107. data/test/sass/plugin_test.rb +10 -8
  108. data/test/sass/results/script.css +3 -3
  109. data/test/sass/script_conversion_test.rb +58 -29
  110. data/test/sass/script_test.rb +430 -53
  111. data/test/sass/scss/css_test.rb +73 -7
  112. data/test/sass/scss/rx_test.rb +4 -0
  113. data/test/sass/scss/scss_test.rb +309 -4
  114. data/test/sass/source_map_test.rb +152 -74
  115. data/test/sass/superselector_test.rb +19 -0
  116. data/test/sass/templates/_partial.sass +1 -1
  117. data/test/sass/templates/basic.sass +10 -10
  118. data/test/sass/templates/bork1.sass +1 -1
  119. data/test/sass/templates/bork5.sass +1 -1
  120. data/test/sass/templates/compact.sass +10 -10
  121. data/test/sass/templates/complex.sass +187 -187
  122. data/test/sass/templates/compressed.sass +10 -10
  123. data/test/sass/templates/expanded.sass +10 -10
  124. data/test/sass/templates/import.sass +2 -2
  125. data/test/sass/templates/importee.sass +3 -3
  126. data/test/sass/templates/mixins.sass +22 -22
  127. data/test/sass/templates/multiline.sass +4 -4
  128. data/test/sass/templates/nested.sass +13 -13
  129. data/test/sass/templates/parent_ref.sass +12 -12
  130. data/test/sass/templates/script.sass +70 -70
  131. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +1 -1
  132. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +2 -2
  133. data/test/sass/templates/subdir/subdir.sass +3 -3
  134. data/test/sass/templates/units.sass +10 -10
  135. data/test/sass/util/multibyte_string_scanner_test.rb +10 -2
  136. data/test/sass/util_test.rb +15 -44
  137. data/test/sass-spec.yml +3 -0
  138. data/test/test_helper.rb +5 -4
  139. metadata +302 -295
  140. data/CONTRIBUTING +0 -3
  141. data/lib/sass/scss/script_lexer.rb +0 -15
  142. data/lib/sass/scss/script_parser.rb +0 -25
@@ -17,15 +17,16 @@ module Sass::Script::Value
17
17
  # @private
18
18
  #
19
19
  # Convert a ruby integer to a rgba components
20
- # @param color [Fixnum]
21
- # @return [Array<Fixnum>] Array of 4 numbers representing r,g,b and alpha
20
+ # @param color [Integer]
21
+ # @return [Array<Integer>] Array of 4 numbers representing r,g,b and alpha
22
22
  def self.int_to_rgba(color)
23
23
  rgba = (0..3).map {|n| color >> (n << 3) & 0xff}.reverse
24
24
  rgba[-1] = rgba[-1] / 255.0
25
25
  rgba
26
26
  end
27
27
 
28
- ALTERNATE_COLOR_NAMES = Sass::Util.map_vals({
28
+ ALTERNATE_COLOR_NAMES = Sass::Util.map_vals(
29
+ {
29
30
  'aqua' => 0x00FFFFFF,
30
31
  'darkgrey' => 0xA9A9A9FF,
31
32
  'darkslategrey' => 0x2F4F4FFF,
@@ -35,10 +36,11 @@ module Sass::Script::Value
35
36
  'lightgrey' => 0xD3D3D3FF,
36
37
  'lightslategrey' => 0x778899FF,
37
38
  'slategrey' => 0x708090FF,
38
- }, &method(:int_to_rgba))
39
+ }, &method(:int_to_rgba))
39
40
 
40
41
  # A hash from color names to `[red, green, blue]` value arrays.
41
- COLOR_NAMES = Sass::Util.map_vals({
42
+ COLOR_NAMES = Sass::Util.map_vals(
43
+ {
42
44
  'aliceblue' => 0xF0F8FFFF,
43
45
  'antiquewhite' => 0xFAEBD7FF,
44
46
  'aquamarine' => 0x7FFFD4FF,
@@ -179,7 +181,7 @@ module Sass::Script::Value
179
181
  'whitesmoke' => 0xF5F5F5FF,
180
182
  'yellow' => 0xFFFF00FF,
181
183
  'yellowgreen' => 0x9ACD32FF
182
- }, &method(:int_to_rgba))
184
+ }, &method(:int_to_rgba))
183
185
 
184
186
  # A hash from `[red, green, blue, alpha]` value arrays to color names.
185
187
  COLOR_NAMES_REVERSE = COLOR_NAMES.invert.freeze
@@ -204,7 +206,7 @@ module Sass::Script::Value
204
206
  #
205
207
  # @overload initialize(attrs)
206
208
  # The attributes are specified as a hash. This hash must contain either
207
- # `:hue`, `:saturation`, and `:value` keys, or `:red`, `:green`, and
209
+ # `:hue`, `:saturation`, and `:lightness` keys, or `:red`, `:green`, and
208
210
  # `:blue` keys. It cannot contain both HSL and RGB keys. It may also
209
211
  # optionally contain an `:alpha` key, and a `:representation` key
210
212
  # indicating the original representation of the color that the user wrote
@@ -232,12 +234,12 @@ module Sass::Script::Value
232
234
  raise ArgumentError.new("Color.new(array) expects a three- or four-element array")
233
235
  end
234
236
 
235
- red, green, blue = attrs[0...3].map {|c| c.to_i}
237
+ red, green, blue = attrs[0...3].map {|c| Sass::Util.round(c)}
236
238
  @attrs = {:red => red, :green => green, :blue => blue}
237
239
  @attrs[:alpha] = attrs[3] ? attrs[3].to_f : 1
238
240
  @representation = representation
239
241
  else
240
- attrs = attrs.reject {|k, v| v.nil?}
242
+ attrs = attrs.reject {|_k, v| v.nil?}
241
243
  hsl = [:hue, :saturation, :lightness] & attrs.keys
242
244
  rgb = [:red, :green, :blue] & attrs.keys
243
245
  if !allow_both_rgb_and_hsl && !hsl.empty? && !rgb.empty?
@@ -258,7 +260,7 @@ module Sass::Script::Value
258
260
 
259
261
  [:red, :green, :blue].each do |k|
260
262
  next if @attrs[k].nil?
261
- @attrs[k] = Sass::Util.restrict(@attrs[k].to_i, 0..255)
263
+ @attrs[k] = Sass::Util.restrict(Sass::Util.round(@attrs[k]), 0..255)
262
264
  end
263
265
 
264
266
  [:saturation, :lightness].each do |k|
@@ -283,7 +285,7 @@ module Sass::Script::Value
283
285
  green = $2.ljust(2, $2).to_i(16)
284
286
  blue = $3.ljust(2, $3).to_i(16)
285
287
 
286
- hex_string = '##{hex_string}' unless hex_string[0] == ?#
288
+ hex_string = "##{hex_string}" unless hex_string[0] == ?#
287
289
  attrs = {:red => red, :green => green, :blue => blue, :representation => hex_string}
288
290
  attrs[:alpha] = alpha if alpha
289
291
  new(attrs)
@@ -291,7 +293,7 @@ module Sass::Script::Value
291
293
 
292
294
  # The red component of the color.
293
295
  #
294
- # @return [Fixnum]
296
+ # @return [Integer]
295
297
  def red
296
298
  hsl_to_rgb!
297
299
  @attrs[:red]
@@ -299,7 +301,7 @@ module Sass::Script::Value
299
301
 
300
302
  # The green component of the color.
301
303
  #
302
- # @return [Fixnum]
304
+ # @return [Integer]
303
305
  def green
304
306
  hsl_to_rgb!
305
307
  @attrs[:green]
@@ -307,7 +309,7 @@ module Sass::Script::Value
307
309
 
308
310
  # The blue component of the color.
309
311
  #
310
- # @return [Fixnum]
312
+ # @return [Integer]
311
313
  def blue
312
314
  hsl_to_rgb!
313
315
  @attrs[:blue]
@@ -340,7 +342,7 @@ module Sass::Script::Value
340
342
  # The alpha channel (opacity) of the color.
341
343
  # This is 1 unless otherwise defined.
342
344
  #
343
- # @return [Fixnum]
345
+ # @return [Integer]
344
346
  def alpha
345
347
  @attrs[:alpha].to_f
346
348
  end
@@ -355,7 +357,7 @@ module Sass::Script::Value
355
357
 
356
358
  # Returns the red, green, and blue components of the color.
357
359
  #
358
- # @return [Array<Fixnum>] A frozen three-element array of the red, green, and blue
360
+ # @return [Array<Integer>] A frozen three-element array of the red, green, and blue
359
361
  # values (respectively) of the color
360
362
  def rgb
361
363
  [red, green, blue].freeze
@@ -363,7 +365,7 @@ module Sass::Script::Value
363
365
 
364
366
  # Returns the red, green, blue, and alpha components of the color.
365
367
  #
366
- # @return [Array<Fixnum>] A frozen four-element array of the red, green,
368
+ # @return [Array<Integer>] A frozen four-element array of the red, green,
367
369
  # blue, and alpha values (respectively) of the color
368
370
  def rgba
369
371
  [red, green, blue, alpha].freeze
@@ -371,7 +373,7 @@ module Sass::Script::Value
371
373
 
372
374
  # Returns the hue, saturation, and lightness components of the color.
373
375
  #
374
- # @return [Array<Fixnum>] A frozen three-element array of the
376
+ # @return [Array<Integer>] A frozen three-element array of the
375
377
  # hue, saturation, and lightness values (respectively) of the color
376
378
  def hsl
377
379
  [hue, saturation, lightness].freeze
@@ -379,10 +381,10 @@ module Sass::Script::Value
379
381
 
380
382
  # Returns the hue, saturation, lightness, and alpha components of the color.
381
383
  #
382
- # @return [Array<Fixnum>] A frozen four-element array of the hue,
384
+ # @return [Array<Integer>] A frozen four-element array of the hue,
383
385
  # saturation, lightness, and alpha values (respectively) of the color
384
386
  def hsla
385
- [hue, saturation, lightness].freeze
387
+ [hue, saturation, lightness, alpha].freeze
386
388
  end
387
389
 
388
390
  # The SassScript `==` operation.
@@ -421,7 +423,7 @@ module Sass::Script::Value
421
423
  # @return [Color] The new Color object
422
424
  # @raise [ArgumentError] if both RGB and HSL keys are specified
423
425
  def with(attrs)
424
- attrs = attrs.reject {|k, v| v.nil?}
426
+ attrs = attrs.reject {|_k, v| v.nil?}
425
427
  hsl = !([:hue, :saturation, :lightness] & attrs.keys).empty?
426
428
  rgb = !([:red, :green, :blue] & attrs.keys).empty?
427
429
  if hsl && rgb
@@ -598,16 +600,32 @@ module Sass::Script::Value
598
600
  "##{red}#{green}#{blue}"
599
601
  end
600
602
 
603
+ def operation_name(operation)
604
+ case operation
605
+ when :+
606
+ "add"
607
+ when :-
608
+ "subtract"
609
+ when :*
610
+ "multiply"
611
+ when :/
612
+ "divide"
613
+ when :%
614
+ "modulo"
615
+ end
616
+ end
617
+
601
618
  def piecewise(other, operation)
602
619
  other_num = other.is_a? Number
603
620
  if other_num && !other.unitless?
604
621
  raise Sass::SyntaxError.new(
605
- "Cannot add a number with units (#{other}) to a color (#{self}).")
622
+ "Cannot #{operation_name(operation)} a number with units (#{other}) to a color (#{self})."
623
+ )
606
624
  end
607
625
 
608
626
  result = []
609
627
  (0...3).each do |i|
610
- res = rgb[i].send(operation, other_num ? other.value : other.rgb[i])
628
+ res = rgb[i].to_f.send(operation, other_num ? other.value : other.rgb[i])
611
629
  result[i] = [[res, 255].min, 0].max
612
630
  end
613
631
 
@@ -632,7 +650,7 @@ module Sass::Script::Value
632
650
  hue_to_rgb(m1, m2, h + 1.0 / 3),
633
651
  hue_to_rgb(m1, m2, h),
634
652
  hue_to_rgb(m1, m2, h - 1.0 / 3)
635
- ].map {|c| (c * 0xff).round}
653
+ ].map {|c| Sass::Util.round(c * 0xff)}
636
654
  end
637
655
 
638
656
  def hue_to_rgb(m1, m2, h)
@@ -1,6 +1,8 @@
1
1
  module Sass::Script::Value
2
2
  # Provides helper functions for creating sass values from within ruby methods.
3
3
  # @since `3.3.0`
4
+ # @comment
5
+ # rubocop:disable ModuleLength
4
6
  module Helpers
5
7
  # Construct a Sass Boolean.
6
8
  #
@@ -139,7 +141,7 @@ module Sass::Script::Value
139
141
  Sass::SCSS::StaticParser.new(str, nil, nil, 1, 1, allow_parent_ref).parse_selector
140
142
  rescue Sass::SyntaxError => e
141
143
  err = "#{value.inspect} is not a valid selector: #{e}"
142
- err = "$#{name.to_s.gsub('_', '-')}: #{err}" if name
144
+ err = "$#{name.to_s.tr('_', '-')}: #{err}" if name
143
145
  raise ArgumentError.new(err)
144
146
  end
145
147
  end
@@ -163,7 +165,7 @@ module Sass::Script::Value
163
165
  return seq if selector.members.length == 1
164
166
 
165
167
  err = "#{value.inspect} is not a complex selector"
166
- err = "$#{name.to_s.gsub('_', '-')}: #{err}" if name
168
+ err = "$#{name.to_s.tr('_', '-')}: #{err}" if name
167
169
  raise ArgumentError.new(err)
168
170
  end
169
171
 
@@ -190,10 +192,18 @@ module Sass::Script::Value
190
192
  end
191
193
 
192
194
  err = "#{value.inspect} is not a compound selector"
193
- err = "$#{name.to_s.gsub('_', '-')}: #{err}" if name
195
+ err = "$#{name.to_s.tr('_', '-')}: #{err}" if name
194
196
  raise ArgumentError.new(err)
195
197
  end
196
198
 
199
+ # Returns true when the literal is a string containing a calc()
200
+ #
201
+ # @param literal [Sass::Script::Value::Base] The value to check
202
+ # @return boolean
203
+ def calc?(literal)
204
+ literal.is_a?(Sass::Script::Value::String) && literal.value =~ /calc\(/
205
+ end
206
+
197
207
  private
198
208
 
199
209
  # Converts a user-provided selector into string form or throws an
@@ -205,7 +215,7 @@ module Sass::Script::Value
205
215
 
206
216
  err = "#{value.inspect} is not a valid selector: it must be a string,\n" +
207
217
  "a list of strings, or a list of lists of strings"
208
- err = "$#{name.to_s.gsub('_', '-')}: #{err}" if name
218
+ err = "$#{name.to_s.tr('_', '-')}: #{err}" if name
209
219
  raise ArgumentError.new(err)
210
220
  end
211
221
 
@@ -242,14 +252,14 @@ module Sass::Script::Value
242
252
  def parse_unit_string(unit_string)
243
253
  denominator_units = numerator_units = Sass::Script::Value::Number::NO_UNITS
244
254
  return numerator_units, denominator_units unless unit_string && unit_string.length > 0
245
- num_over_denominator = unit_string.split(/ *\/ */)
255
+ num_over_denominator = unit_string.split(%r{ */ *})
246
256
  unless (1..2).include?(num_over_denominator.size)
247
257
  raise ArgumentError.new("Malformed unit string: #{unit_string}")
248
258
  end
249
259
  numerator_units = num_over_denominator[0].split(/ *\* */)
250
260
  denominator_units = (num_over_denominator[1] || "").split(/ *\* */)
251
261
  [[numerator_units, "numerator"], [denominator_units, "denominator"]].each do |units, name|
252
- if unit_string =~ /\// && units.size == 0
262
+ if unit_string =~ %r{/} && units.size == 0
253
263
  raise ArgumentError.new("Malformed unit string: #{unit_string}")
254
264
  end
255
265
  if units.any? {|unit| unit !~ VALID_UNIT}
@@ -56,7 +56,7 @@ module Sass::Script::Value
56
56
  return "()" if value.empty?
57
57
 
58
58
  to_sass = lambda do |value|
59
- if value.is_a?(Map) || (value.is_a?(List) && value.separator == :comma)
59
+ if value.is_a?(List) && value.separator == :comma
60
60
  "(#{value.to_sass(opts)})"
61
61
  else
62
62
  value.to_sass(opts)
@@ -34,25 +34,35 @@ module Sass::Script::Value
34
34
  attr_accessor :original
35
35
 
36
36
  def self.precision
37
- @precision ||= 5
37
+ Thread.current[:sass_numeric_precision] || Thread.main[:sass_numeric_precision] || 5
38
38
  end
39
39
 
40
40
  # Sets the number of digits of precision
41
41
  # For example, if this is `3`,
42
42
  # `3.1415926` will be printed as `3.142`.
43
+ # The numeric precision is stored as a thread local for thread safety reasons.
44
+ # To set for all threads, be sure to set the precision on the main thread.
43
45
  def self.precision=(digits)
44
- @precision = digits.round
45
- @precision_factor = 10.0**@precision
46
+ Thread.current[:sass_numeric_precision] = digits.round
47
+ Thread.current[:sass_numeric_precision_factor] = nil
48
+ Thread.current[:sass_numeric_epsilon] = nil
46
49
  end
47
50
 
48
51
  # the precision factor used in numeric output
49
52
  # it is derived from the `precision` method.
50
53
  def self.precision_factor
51
- @precision_factor ||= 10.0**precision
54
+ Thread.current[:sass_numeric_precision_factor] ||= 10.0**precision
55
+ end
56
+
57
+ # Used in checking equality of floating point numbers. Any
58
+ # numbers within an `epsilon` of each other are considered functionally equal.
59
+ # The value for epsilon is one tenth of the current numeric precision.
60
+ def self.epsilon
61
+ Thread.current[:sass_numeric_epsilon] ||= 1 / (precision_factor * 10)
52
62
  end
53
63
 
54
64
  # Used so we don't allocate two new arrays for each new number.
55
- NO_UNITS = []
65
+ NO_UNITS = []
56
66
 
57
67
  # @param value [Numeric] The value of the number
58
68
  # @param numerator_units [::String, Array<::String>] See \{#numerator\_units}
@@ -63,6 +73,7 @@ module Sass::Script::Value
63
73
  super(value)
64
74
  @numerator_units = numerator_units
65
75
  @denominator_units = denominator_units
76
+ @options = nil
66
77
  normalize!
67
78
  end
68
79
 
@@ -200,7 +211,7 @@ module Sass::Script::Value
200
211
  rescue Sass::UnitConversionError
201
212
  return Bool::FALSE
202
213
  end
203
- Bool.new(this.value == other.value)
214
+ Bool.new(basically_equal?(this.value, other.value))
204
215
  end
205
216
 
206
217
  def hash
@@ -211,7 +222,7 @@ module Sass::Script::Value
211
222
  # Hash-equality must be transitive, so it just compares the exact value,
212
223
  # numerator units, and denominator units.
213
224
  def eql?(other)
214
- value == other.value && numerator_units == other.numerator_units &&
225
+ basically_equal?(value, other.value) && numerator_units == other.numerator_units &&
215
226
  denominator_units == other.denominator_units
216
227
  end
217
228
 
@@ -281,20 +292,30 @@ module Sass::Script::Value
281
292
  # and confusing.
282
293
  str = ("%0.#{self.class.precision}f" % value).gsub(/0*$/, '') if str.include?('e')
283
294
 
295
+ # Sometimes numeric formatting will result in a decimal number with a trailing zero (x.0)
296
+ if str =~ /(.*)\.0$/
297
+ str = $1
298
+ end
299
+
300
+ # We omit a leading zero before the decimal point in compressed mode.
301
+ if @options && options[:style] == :compressed
302
+ str.sub!(/^(-)?0\./, '\1.')
303
+ end
304
+
284
305
  unitless? ? str : "#{str}#{unit_str}"
285
306
  end
286
307
  alias_method :to_sass, :inspect
287
308
 
288
- # @return [Fixnum] The integer value of the number
309
+ # @return [Integer] The integer value of the number
289
310
  # @raise [Sass::SyntaxError] if the number isn't an integer
290
311
  def to_i
291
312
  super unless int?
292
- value
313
+ value.to_i
293
314
  end
294
315
 
295
316
  # @return [Boolean] Whether or not this number is an integer.
296
317
  def int?
297
- value % 1 == 0.0
318
+ basically_equal?(value % 1, 0.0)
298
319
  end
299
320
 
300
321
  # @return [Boolean] Whether or not this number has no units.
@@ -375,12 +396,24 @@ module Sass::Script::Value
375
396
 
376
397
  private
377
398
 
399
+ # @private
400
+ # @see Sass::Script::Number.basically_equal?
401
+ def basically_equal?(num1, num2)
402
+ self.class.basically_equal?(num1, num2)
403
+ end
404
+
405
+ # Checks whether two numbers are within an epsilon of each other.
406
+ # @return [Boolean]
407
+ def self.basically_equal?(num1, num2)
408
+ (num1 - num2).abs < epsilon
409
+ end
410
+
378
411
  # @private
379
412
  def self.round(num)
380
413
  if num.is_a?(Float) && (num.infinite? || num.nan?)
381
414
  num
382
- elsif num % 1 == 0.0
383
- num.to_i
415
+ elsif basically_equal?(num % 1, 0.0)
416
+ num.round
384
417
  else
385
418
  ((num * precision_factor).round / precision_factor).to_f
386
419
  end
@@ -439,11 +472,10 @@ module Sass::Script::Value
439
472
  sans_common_units(@numerator_units, @denominator_units)
440
473
 
441
474
  @denominator_units.each_with_index do |d, i|
442
- if convertable?(d) && (u = @numerator_units.find(&method(:convertable?)))
443
- @value /= conversion_factor(d, u)
444
- @denominator_units.delete_at(i)
445
- @numerator_units.delete_at(@numerator_units.index(u))
446
- end
475
+ next unless convertable?(d) && (u = @numerator_units.find(&method(:convertable?)))
476
+ @value /= conversion_factor(d, u)
477
+ @denominator_units.delete_at(i)
478
+ @numerator_units.delete_at(@numerator_units.index(u))
447
479
  end
448
480
  end
449
481
 
@@ -458,6 +490,7 @@ module Sass::Script::Value
458
490
  'cm' => Rational(1, 2.54),
459
491
  'pc' => Rational(1, 6),
460
492
  'mm' => Rational(1, 25.4),
493
+ 'q' => Rational(1, 101.6),
461
494
  'pt' => Rational(1, 72),
462
495
  'px' => Rational(1, 96)
463
496
  },
@@ -477,8 +510,8 @@ module Sass::Script::Value
477
510
  },
478
511
  {
479
512
  'dpi' => Rational(1),
480
- 'dpcm' => Rational(1, 2.54),
481
- 'dppx' => Rational(1, 96)
513
+ 'dpcm' => Rational(254, 100),
514
+ 'dppx' => Rational(96)
482
515
  }
483
516
  ]
484
517
 
@@ -2,6 +2,8 @@
2
2
  module Sass::Script::Value
3
3
  # A SassScript object representing a CSS string *or* a CSS identifier.
4
4
  class String < Base
5
+ @@interpolation_deprecation = Sass::Deprecation.new
6
+
5
7
  # The Ruby value of the string.
6
8
  #
7
9
  # @return [String]
@@ -28,9 +30,18 @@ module Sass::Script::Value
28
30
  end
29
31
  end
30
32
 
31
- def self.quote(contents, quote = nil)
33
+ # Returns the quoted string representation of `contents`.
34
+ #
35
+ # @options opts :quote [String]
36
+ # The preferred quote style for quoted strings. If `:none`, strings are
37
+ # always emitted unquoted. If `nil`, quoting is determined automatically.
38
+ # @options opts :sass [String]
39
+ # Whether to quote strings for Sass source, as opposed to CSS. Defaults to `false`.
40
+ def self.quote(contents, opts = {})
41
+ quote = opts[:quote]
42
+
32
43
  # Short-circuit if there are no characters that need quoting.
33
- unless contents =~ /[\n\\"']/
44
+ unless contents =~ /[\n\\"']|\#\{/
34
45
  quote ||= '"'
35
46
  return "#{quote}#{contents}#{quote}"
36
47
  end
@@ -50,6 +61,9 @@ module Sass::Script::Value
50
61
  # Replace single backslashes with multiples.
51
62
  contents = contents.gsub("\\", "\\\\\\\\")
52
63
 
64
+ # Escape interpolation.
65
+ contents = contents.gsub('#{', "\\\#{") if opts[:sass]
66
+
53
67
  if quote == '"'
54
68
  contents = contents.gsub('"', "\\\"")
55
69
  else
@@ -64,9 +78,13 @@ module Sass::Script::Value
64
78
  #
65
79
  # @param value [String] See \{#value}
66
80
  # @param type [Symbol] See \{#type}
67
- def initialize(value, type = :identifier)
81
+ # @param deprecated_interp_equivalent [String?]
82
+ # If this was created via a potentially-deprecated string interpolation,
83
+ # this is the replacement expression that should be suggested to the user.
84
+ def initialize(value, type = :identifier, deprecated_interp_equivalent = nil)
68
85
  super(value)
69
86
  @type = type
87
+ @deprecated_interp_equivalent = deprecated_interp_equivalent
70
88
  end
71
89
 
72
90
  # @see Value#plus
@@ -82,12 +100,35 @@ module Sass::Script::Value
82
100
  # @see Value#to_s
83
101
  def to_s(opts = {})
84
102
  return @value.gsub(/\n\s*/, ' ') if opts[:quote] == :none || @type == :identifier
85
- Sass::Script::Value::String.quote(value, opts[:quote])
103
+ String.quote(value, opts)
86
104
  end
87
105
 
88
106
  # @see Value#to_sass
89
107
  def to_sass(opts = {})
90
- to_s
108
+ to_s(opts.merge(:sass => true))
109
+ end
110
+
111
+ def separator
112
+ check_deprecated_interp
113
+ super
114
+ end
115
+
116
+ def to_a
117
+ check_deprecated_interp
118
+ super
119
+ end
120
+
121
+ # Prints a warning if this string was created using potentially-deprecated
122
+ # interpolation.
123
+ def check_deprecated_interp
124
+ return unless @deprecated_interp_equivalent
125
+
126
+ @@interpolation_deprecation.warn(source_range.file, source_range.start_pos.line, <<WARNING)
127
+ \#{} interpolation near operators will be simplified in a future version of Sass.
128
+ To preserve the current behavior, use quotes:
129
+
130
+ #{@deprecated_interp_equivalent}
131
+ WARNING
91
132
  end
92
133
 
93
134
  def inspect
data/lib/sass/script.rb CHANGED
@@ -16,12 +16,12 @@ module Sass
16
16
  # Parses a string of SassScript
17
17
  #
18
18
  # @param value [String] The SassScript
19
- # @param line [Fixnum] The number of the line on which the SassScript appeared.
19
+ # @param line [Integer] The number of the line on which the SassScript appeared.
20
20
  # Used for error reporting
21
- # @param offset [Fixnum] The number of characters in on `line` that the SassScript started.
21
+ # @param offset [Integer] The number of characters in on `line` that the SassScript started.
22
22
  # Used for error reporting
23
23
  # @param options [{Symbol => Object}] An options hash;
24
- # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
24
+ # see {file:SASS_REFERENCE.md#Options the Sass options documentation}
25
25
  # @return [Script::Tree::Node] The root node of the parse tree
26
26
  def self.parse(value, line, offset, options = {})
27
27
  Parser.parse(value, line, offset, options)
@@ -14,6 +14,13 @@ module Sass
14
14
  def interpolation(warn_for_color = false); nil; end
15
15
  def use_css_import?; true; end
16
16
 
17
+ def block_contents(node, context)
18
+ if node.is_a?(Sass::Tree::DirectiveNode) && node.normalized_name == '@keyframes'
19
+ context = :keyframes
20
+ end
21
+ super(node, context)
22
+ end
23
+
17
24
  def block_child(context)
18
25
  case context
19
26
  when :ruleset
@@ -22,6 +29,8 @@ module Sass
22
29
  directive || ruleset
23
30
  when :directive
24
31
  directive || declaration_or_ruleset
32
+ when :keyframes
33
+ keyframes_ruleset
25
34
  end
26
35
  end
27
36
 
@@ -35,8 +44,13 @@ module Sass
35
44
  block(node(Sass::Tree::RuleNode.new(selector, range(start_pos)), start_pos), :ruleset)
36
45
  end
37
46
 
38
- @sass_script_parser = Class.new(Sass::Script::CssParser)
39
- @sass_script_parser.send(:include, ScriptParser)
47
+ def keyframes_ruleset
48
+ start_pos = source_position
49
+ return unless (selector = keyframes_selector)
50
+ block(node(Sass::Tree::KeyframeRuleNode.new(selector.strip), start_pos), :ruleset)
51
+ end
52
+
53
+ @sass_script_parser = Sass::Script::CssParser
40
54
  end
41
55
  end
42
56
  end