sass 3.4.0 → 3.4.25

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,5 +1,5 @@
1
1
  require 'sass/script/functions'
2
- require 'sass/util/normalized_map'
2
+ require 'sass/util'
3
3
 
4
4
  module Sass::Script::Tree
5
5
  # A SassScript parse node representing a function call.
@@ -128,12 +128,15 @@ module Sass::Script::Tree
128
128
  splat = Sass::Tree::Visitors::Perform.perform_splat(
129
129
  @splat, keywords, @kwarg_splat, environment)
130
130
  if (fn = environment.function(@name))
131
+ css_variable_warning.warn! if css_variable_warning
131
132
  return without_original(perform_sass_fn(fn, args, splat, environment))
132
133
  end
133
134
 
134
135
  args = construct_ruby_args(ruby_name, args, splat, environment)
135
136
 
136
137
  if Sass::Script::Functions.callable?(ruby_name)
138
+ css_variable_warning.warn! if css_variable_warning
139
+
137
140
  local_environment = Sass::Environment.new(environment.global_env, environment.options)
138
141
  local_environment.caller = Sass::ReadOnlyEnvironment.new(environment, environment.options)
139
142
  result = opts(Sass::Script::Functions::EvaluationContext.new(
@@ -208,7 +211,7 @@ module Sass::Script::Tree
208
211
 
209
212
  argnames = signature.args[args.size..-1] || []
210
213
  deprecated_argnames = (signature.deprecated && signature.deprecated[args.size..-1]) || []
211
- args = args + argnames.zip(deprecated_argnames).map do |(argname, deprecated_argname)|
214
+ args += argnames.zip(deprecated_argnames).map do |(argname, deprecated_argname)|
212
215
  if keywords.has_key?(argname)
213
216
  keywords.delete(argname)
214
217
  elsif deprecated_argname && keywords.has_key?(deprecated_argname)
@@ -241,7 +244,7 @@ module Sass::Script::Tree
241
244
  end
242
245
 
243
246
  def perform_sass_fn(function, args, splat, environment)
244
- Sass::Tree::Visitors::Perform.perform_arguments(function, args, splat) do |env|
247
+ Sass::Tree::Visitors::Perform.perform_arguments(function, args, splat, environment) do |env|
245
248
  env.caller = Sass::Environment.new(environment)
246
249
 
247
250
  val = catch :_sass_return do
@@ -296,8 +299,12 @@ module Sass::Script::Tree
296
299
  message = "wrong number of arguments (#{given} for #{expected})"
297
300
  end
298
301
  end
299
- elsif e.message =~ /^wrong number of arguments \(\d+ for \d+\)/ &&
300
- e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/
302
+ elsif (md = /^wrong number of arguments \(given (\d+), expected (\d+)\)/.match(e.message)) &&
303
+ e.backtrace[0] =~ /:in `#{ruby_name}'$/
304
+ # Handle ruby 2.3 error formatting
305
+ message = "wrong number of arguments (#{md[1]} for #{md[2]})"
306
+ elsif e.message =~ /^wrong number of arguments/ &&
307
+ e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/
301
308
  raise e
302
309
  end
303
310
  raise Sass::SyntaxError.new("#{message} for `#{name}'")
@@ -27,6 +27,17 @@ module Sass::Script::Tree
27
27
  # generate a warning.
28
28
  attr_reader :warn_for_color
29
29
 
30
+ # The type of interpolation deprecation for this node.
31
+ #
32
+ # This can be `:none`, indicating that the node doesn't use deprecated
33
+ # interpolation; `:immediate`, indicating that a deprecation warning should
34
+ # be emitted as soon as possible; or `:potential`, indicating that a
35
+ # deprecation warning should be emitted if the resulting string is used in a
36
+ # way that would distinguish it from a list.
37
+ #
38
+ # @return [Symbol]
39
+ attr_reader :deprecation
40
+
30
41
  # Interpolation in a property is of the form `before #{mid} after`.
31
42
  #
32
43
  # @param before [Node] See {Interpolation#before}
@@ -38,15 +49,16 @@ module Sass::Script::Tree
38
49
  # @param warn_for_color [Boolean] See {Interpolation#warn_for_color}
39
50
  # @comment
40
51
  # rubocop:disable ParameterLists
41
- def initialize(before, mid, after, wb, wa, originally_text = false, warn_for_color = false)
52
+ def initialize(before, mid, after, wb, wa, opts = {})
42
53
  # rubocop:enable ParameterLists
43
54
  @before = before
44
55
  @mid = mid
45
56
  @after = after
46
57
  @whitespace_before = wb
47
58
  @whitespace_after = wa
48
- @originally_text = originally_text
49
- @warn_for_color = warn_for_color
59
+ @originally_text = opts[:originally_text] || false
60
+ @warn_for_color = opts[:warn_for_color] || false
61
+ @deprecation = opts[:deprecation] || :none
50
62
  end
51
63
 
52
64
  # @return [String] A human-readable s-expression representation of the interpolation
@@ -56,6 +68,8 @@ module Sass::Script::Tree
56
68
 
57
69
  # @see Node#to_sass
58
70
  def to_sass(opts = {})
71
+ return to_quoted_equivalent.to_sass if deprecation == :immediate
72
+
59
73
  res = ""
60
74
  res << @before.to_sass(opts) if @before
61
75
  res << ' ' if @before && @whitespace_before
@@ -67,6 +81,19 @@ module Sass::Script::Tree
67
81
  res
68
82
  end
69
83
 
84
+ # Returns an `unquote()` expression that will evaluate to the same value as
85
+ # this interpolation.
86
+ #
87
+ # @return [Sass::Script::Tree::Node]
88
+ def to_quoted_equivalent
89
+ Funcall.new(
90
+ "unquote",
91
+ [to_string_interpolation(self)],
92
+ Sass::Util::NormalizedMap.new,
93
+ nil,
94
+ nil)
95
+ end
96
+
70
97
  # Returns the three components of the interpolation, `before`, `mid`, and `after`.
71
98
  #
72
99
  # @return [Array<Node>]
@@ -87,6 +114,49 @@ module Sass::Script::Tree
87
114
 
88
115
  protected
89
116
 
117
+ # Converts a script node into a corresponding string interpolation
118
+ # expression.
119
+ #
120
+ # @param node_or_interp [Sass::Script::Tree::Node]
121
+ # @return [Sass::Script::Tree::StringInterpolation]
122
+ def to_string_interpolation(node_or_interp)
123
+ unless node_or_interp.is_a?(Interpolation)
124
+ node = node_or_interp
125
+ return string_literal(node.value.to_s) if node.is_a?(Literal)
126
+ if node.is_a?(StringInterpolation)
127
+ return concat(string_literal(node.quote), concat(node, string_literal(node.quote)))
128
+ end
129
+ return StringInterpolation.new(string_literal(""), node, string_literal(""))
130
+ end
131
+
132
+ interp = node_or_interp
133
+ after_string_or_interp =
134
+ if interp.after
135
+ to_string_interpolation(interp.after)
136
+ else
137
+ string_literal("")
138
+ end
139
+ if interp.after && interp.whitespace_after
140
+ after_string_or_interp = concat(string_literal(' '), after_string_or_interp)
141
+ end
142
+
143
+ mid_string_or_interp = to_string_interpolation(interp.mid)
144
+
145
+ before_string_or_interp =
146
+ if interp.before
147
+ to_string_interpolation(interp.before)
148
+ else
149
+ string_literal("")
150
+ end
151
+ if interp.before && interp.whitespace_before
152
+ before_string_or_interp = concat(before_string_or_interp, string_literal(' '))
153
+ end
154
+
155
+ concat(before_string_or_interp, concat(mid_string_or_interp, after_string_or_interp))
156
+ end
157
+
158
+ private
159
+
90
160
  # Evaluates the interpolation.
91
161
  #
92
162
  # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
@@ -112,7 +182,42 @@ MESSAGE
112
182
  res << val.to_s(:quote => :none)
113
183
  res << " " if @after && @whitespace_after
114
184
  res << @after.perform(environment).to_s if @after
115
- opts(Sass::Script::Value::String.new(res))
185
+ str = Sass::Script::Value::String.new(
186
+ res, :identifier,
187
+ (to_quoted_equivalent.to_sass if deprecation == :potential))
188
+ str.source_range = source_range
189
+ opts(str)
190
+ end
191
+
192
+ # Concatenates two string literals or string interpolation expressions.
193
+ #
194
+ # @param string_or_interp1 [Sass::Script::Tree::Literal|Sass::Script::Tree::StringInterpolation]
195
+ # @param string_or_interp2 [Sass::Script::Tree::Literal|Sass::Script::Tree::StringInterpolation]
196
+ # @return [Sass::Script::Tree::StringInterpolation]
197
+ def concat(string_or_interp1, string_or_interp2)
198
+ if string_or_interp1.is_a?(Literal) && string_or_interp2.is_a?(Literal)
199
+ return string_literal(string_or_interp1.value.value + string_or_interp2.value.value)
200
+ end
201
+
202
+ if string_or_interp1.is_a?(Literal)
203
+ string = string_or_interp1
204
+ interp = string_or_interp2
205
+ before = string_literal(string.value.value + interp.before.value.value)
206
+ return StringInterpolation.new(before, interp.mid, interp.after)
207
+ end
208
+
209
+ StringInterpolation.new(
210
+ string_or_interp1.before,
211
+ string_or_interp1.mid,
212
+ concat(string_or_interp1.after, string_or_interp2))
213
+ end
214
+
215
+ # Returns a string literal with the given contents.
216
+ #
217
+ # @param string [String]
218
+ # @return string [Sass::Script::Tree::Literal]
219
+ def string_literal(string)
220
+ Literal.new(Sass::Script::Value::String.new(string, :string))
116
221
  end
117
222
  end
118
223
  end
@@ -28,11 +28,8 @@ module Sass::Script::Tree
28
28
  # @see Value#to_sass
29
29
  def to_sass(opts = {})
30
30
  return "()" if elements.empty?
31
- precedence = Sass::Script::Parser.precedence_of(separator)
32
31
  members = elements.map do |v|
33
- if v.is_a?(ListLiteral) && Sass::Script::Parser.precedence_of(v.separator) <= precedence ||
34
- separator == :space && v.is_a?(UnaryOperation) &&
35
- (v.operator == :minus || v.operator == :plus)
32
+ if element_needs_parens?(v)
36
33
  "(#{v.to_sass(opts)})"
37
34
  else
38
35
  v.to_sass(opts)
@@ -55,6 +52,10 @@ module Sass::Script::Tree
55
52
  "(#{elements.map {|e| e.inspect}.join(separator == :space ? ' ' : ', ')})"
56
53
  end
57
54
 
55
+ def force_division!
56
+ # Do nothing. Lists prevent division propagation.
57
+ end
58
+
58
59
  protected
59
60
 
60
61
  def _perform(environment)
@@ -68,6 +69,32 @@ module Sass::Script::Tree
68
69
 
69
70
  private
70
71
 
72
+ # Returns whether an element in the list should be wrapped in parentheses
73
+ # when serialized to Sass.
74
+ def element_needs_parens?(element)
75
+ if element.is_a?(ListLiteral)
76
+ return Sass::Script::Parser.precedence_of(element.separator) <=
77
+ Sass::Script::Parser.precedence_of(separator)
78
+ end
79
+
80
+ return false unless separator == :space
81
+
82
+ if element.is_a?(UnaryOperation)
83
+ return element.operator == :minus || element.operator == :plus
84
+ end
85
+
86
+ return false unless element.is_a?(Operation)
87
+ return true unless element.operator == :div
88
+ !(is_literal_number?(element.operand1) && is_literal_number?(element.operand2))
89
+ end
90
+
91
+ # Returns whether a value is a number literal that shouldn't be divided.
92
+ def is_literal_number?(value)
93
+ value.is_a?(Literal) &&
94
+ value.value.is_a?((Sass::Script::Value::Number)) &&
95
+ !value.value.original.nil?
96
+ end
97
+
71
98
  def sep_str(opts = options)
72
99
  return ' ' if separator == :space
73
100
  return ',' if opts && opts[:style] == :compressed
@@ -35,6 +35,10 @@ module Sass::Script::Tree
35
35
  value.inspect
36
36
  end
37
37
 
38
+ def force_division!
39
+ value.original = nil if value.is_a?(Sass::Script::Value::Number)
40
+ end
41
+
38
42
  protected
39
43
 
40
44
  def _perform(environment)
@@ -10,7 +10,7 @@ module Sass::Script::Tree
10
10
 
11
11
  # The line of the document on which this node appeared.
12
12
  #
13
- # @return [Fixnum]
13
+ # @return [Integer]
14
14
  attr_accessor :line
15
15
 
16
16
  # The source range in the document on which this node appeared.
@@ -23,9 +23,17 @@ module Sass::Script::Tree
23
23
  # @return [String]
24
24
  attr_accessor :filename
25
25
 
26
+ # The warning that this node should emit if it executes in a way that's not
27
+ # safe for a CSS variable value.
28
+ #
29
+ # This is `nil` if this is not in a CSS variable value.
30
+ #
31
+ # @return [Sass::Script::CssVariableWarning]
32
+ attr_accessor :css_variable_warning
33
+
26
34
  # Sets the options hash for this node,
27
35
  # as well as for all child nodes.
28
- # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
36
+ # See {file:SASS_REFERENCE.md#Options the Sass options documentation}.
29
37
  #
30
38
  # @param options [{Symbol => Object}] The options
31
39
  def options=(options)
@@ -62,6 +70,10 @@ module Sass::Script::Tree
62
70
 
63
71
  # Returns the text of this SassScript expression.
64
72
  #
73
+ # @options opts :quote [String]
74
+ # The preferred quote style for quoted strings. If `:none`, strings are
75
+ # always emitted unquoted.
76
+ #
65
77
  # @return [String]
66
78
  def to_sass(opts = {})
67
79
  Sass::Util.abstract(self)
@@ -75,12 +87,18 @@ module Sass::Script::Tree
75
87
  Sass::Util.abstract(self)
76
88
  end
77
89
 
90
+ # Forces any division operations with number literals in this expression to
91
+ # do real division, rather than returning strings.
92
+ def force_division!
93
+ children.each {|c| c.force_division!}
94
+ end
95
+
78
96
  protected
79
97
 
80
98
  # Converts underscores to dashes if the :dasherize option is set.
81
99
  def dasherize(s, opts)
82
100
  if opts[:dasherize]
83
- s.gsub(/_/, '-')
101
+ s.tr('_', '-')
84
102
  else
85
103
  s
86
104
  end
@@ -2,6 +2,9 @@ module Sass::Script::Tree
2
2
  # A SassScript parse node representing a binary operation,
3
3
  # such as `$a + $b` or `"foo" + 1`.
4
4
  class Operation < Node
5
+ @@color_arithmetic_deprecation = Sass::Deprecation.new
6
+ @@unitless_equals_deprecation = Sass::Deprecation.new
7
+
5
8
  attr_reader :operand1
6
9
  attr_reader :operand2
7
10
  attr_reader :operator
@@ -78,16 +81,66 @@ module Sass::Script::Tree
78
81
  "Invalid null operation: \"#{value1.inspect} #{@operator} #{value2.inspect}\".")
79
82
  end
80
83
 
84
+ if css_variable_warning && @operator == :div &&
85
+ !(value1.is_a?(Sass::Script::Value::Number) && value1.original &&
86
+ value2.is_a?(Sass::Script::Value::Number) && value2.original) &&
87
+ !(value1.is_a?(Sass::Script::Value::String) && value2.is_a?(Sass::Script::Value::String))
88
+ css_variable_warning.warn!
89
+ end
90
+
81
91
  begin
82
- opts(value1.send(@operator, value2))
92
+ result = opts(value1.send(@operator, value2))
83
93
  rescue NoMethodError => e
84
94
  raise e unless e.name.to_s == @operator.to_s
85
95
  raise Sass::SyntaxError.new("Undefined operation: \"#{value1} #{@operator} #{value2}\".")
86
96
  end
97
+
98
+ warn_for_color_arithmetic(value1, value2)
99
+ warn_for_unitless_equals(value1, value2, result)
100
+
101
+ result
87
102
  end
88
103
 
89
104
  private
90
105
 
106
+ def warn_for_color_arithmetic(value1, value2)
107
+ return unless @operator == :plus || @operator == :times || @operator == :minus ||
108
+ @operator == :div || @operator == :mod
109
+
110
+ if value1.is_a?(Sass::Script::Value::Number)
111
+ return unless value2.is_a?(Sass::Script::Value::Color)
112
+ elsif value1.is_a?(Sass::Script::Value::Color)
113
+ return unless value2.is_a?(Sass::Script::Value::Color) || value2.is_a?(Sass::Script::Value::Number)
114
+ else
115
+ return
116
+ end
117
+
118
+ @@color_arithmetic_deprecation.warn(filename, line, <<WARNING)
119
+ The operation `#{value1} #{@operator} #{value2}` is deprecated and will be an error in future versions.
120
+ Consider using Sass's color functions instead.
121
+ http://sass-lang.com/documentation/Sass/Script/Functions.html#other_color_functions
122
+ WARNING
123
+ end
124
+
125
+ def warn_for_unitless_equals(value1, value2, result)
126
+ return unless @operator == :eq || @operator == :neq
127
+ return unless value1.is_a?(Sass::Script::Value::Number)
128
+ return unless value2.is_a?(Sass::Script::Value::Number)
129
+ return unless value1.unitless? != value2.unitless?
130
+ return unless result == (if @operator == :eq
131
+ Sass::Script::Value::Bool::TRUE
132
+ else
133
+ Sass::Script::Value::Bool::FALSE
134
+ end)
135
+
136
+ operation = "#{value1.to_sass} #{@operator == :eq ? '==' : '!='} #{value2.to_sass}"
137
+ future_value = @operator == :neq
138
+ @@unitless_equals_deprecation.warn(filename, line, <<WARNING)
139
+ The result of `#{operation}` will be `#{future_value}` in future releases of Sass.
140
+ Unitless numbers will no longer be equal to the same numbers with units.
141
+ WARNING
142
+ end
143
+
91
144
  def operand_to_sass(op, side, opts)
92
145
  return "(#{op.to_sass(opts)})" if op.is_a?(Sass::Script::Tree::ListLiteral)
93
146
  return op.to_sass(opts) unless op.is_a?(Operation)
@@ -3,12 +3,39 @@ module Sass::Script::Tree
3
3
  #
4
4
  # @see Interpolation
5
5
  class StringInterpolation < Node
6
+ # @return [Literal] The string literal before this interpolation.
7
+ attr_reader :before
8
+
9
+ # @return [Node] The SassScript within the interpolation
10
+ attr_reader :mid
11
+
12
+ # @return [StringInterpolation, Literal]
13
+ # The string literal or string interpolation before this interpolation.
14
+ attr_reader :after
15
+
16
+ # Whether this is a CSS string or a CSS identifier. The difference is that
17
+ # strings are written with double-quotes, while identifiers aren't.
18
+ #
19
+ # String interpolations are only ever identifiers if they're quote-like
20
+ # functions such as `url()`.
21
+ #
22
+ # @return [Symbol] `:string` or `:identifier`
23
+ def type
24
+ @before.value.type
25
+ end
26
+
27
+ # Returns the quote character that should be used to wrap a Sass
28
+ # representation of this interpolation.
29
+ def quote
30
+ quote_for(self) || '"'
31
+ end
32
+
6
33
  # Interpolation in a string is of the form `"before #{mid} after"`,
7
34
  # where `before` and `after` may include more interpolation.
8
35
  #
9
- # @param before [Node] The string before the interpolation
10
- # @param mid [Node] The SassScript within the interpolation
11
- # @param after [Node] The string after the interpolation
36
+ # @param before [StringInterpolation, Literal] See {StringInterpolation#before}
37
+ # @param mid [Node] See {StringInterpolation#mid}
38
+ # @param after [Literal] See {StringInterpolation#after}
12
39
  def initialize(before, mid, after)
13
40
  @before = before
14
41
  @mid = mid
@@ -22,32 +49,15 @@ module Sass::Script::Tree
22
49
 
23
50
  # @see Node#to_sass
24
51
  def to_sass(opts = {})
25
- # We can get rid of all of this when we remove the deprecated :equals context
26
- # XXX CE: It's gone now but I'm not sure what can be removed now.
27
- before_unquote, before_quote_char, before_str = parse_str(@before.to_sass(opts))
28
- after_unquote, after_quote_char, after_str = parse_str(@after.to_sass(opts))
29
- unquote = before_unquote || after_unquote ||
30
- (before_quote_char && !after_quote_char && !after_str.empty?) ||
31
- (!before_quote_char && after_quote_char && !before_str.empty?)
32
- quote_char =
33
- if before_quote_char && after_quote_char && before_quote_char != after_quote_char
34
- before_str.gsub!("\\'", "'")
35
- before_str.gsub!('"', "\\\"")
36
- after_str.gsub!("\\'", "'")
37
- after_str.gsub!('"', "\\\"")
38
- '"'
39
- else
40
- before_quote_char || after_quote_char
41
- end
52
+ quote = type == :string ? opts[:quote] || quote_for(self) || '"' : :none
53
+ opts = opts.merge(:quote => quote)
42
54
 
43
55
  res = ""
44
- res << 'unquote(' if unquote
45
- res << quote_char if quote_char
46
- res << before_str
47
- res << '#{' << @mid.to_sass(opts) << '}'
48
- res << after_str
49
- res << quote_char if quote_char
50
- res << ')' if unquote
56
+ res << quote if quote != :none
57
+ res << _to_sass(before, opts)
58
+ res << '#{' << @mid.to_sass(opts.merge(:quote => nil)) << '}'
59
+ res << _to_sass(after, opts)
60
+ res << quote if quote != :none
51
61
  res
52
62
  end
53
63
 
@@ -88,17 +98,28 @@ module Sass::Script::Tree
88
98
 
89
99
  private
90
100
 
91
- def parse_str(str)
92
- case str
93
- when /^unquote\((["'])(.*)\1\)$/
94
- return true, $1, $2
95
- when '""'
96
- return false, nil, ""
97
- when /^(["'])(.*)\1$/
98
- return false, $1, $2
99
- else
100
- return false, nil, str
101
+ def _to_sass(string_or_interp, opts)
102
+ result = string_or_interp.to_sass(opts)
103
+ opts[:quote] == :none ? result : result.slice(1...-1)
104
+ end
105
+
106
+ def quote_for(string_or_interp)
107
+ if string_or_interp.is_a?(Sass::Script::Tree::Literal)
108
+ return nil if string_or_interp.value.value.empty?
109
+ return '"' if string_or_interp.value.value.include?("'")
110
+ return "'" if string_or_interp.value.value.include?('"')
111
+ return nil
101
112
  end
113
+
114
+ # Double-quotes take precedence over single quotes.
115
+ before_quote = quote_for(string_or_interp.before)
116
+ return '"' if before_quote == '"'
117
+ after_quote = quote_for(string_or_interp.after)
118
+ return '"' if after_quote == '"'
119
+
120
+ # Returns "'" if either or both insist on single quotes, and nil
121
+ # otherwise.
122
+ before_quote || after_quote
102
123
  end
103
124
  end
104
125
  end
@@ -14,7 +14,7 @@ module Sass::Script::Tree
14
14
  # @param name [String] See \{#name}
15
15
  def initialize(name)
16
16
  @name = name
17
- @underscored_name = name.gsub(/-/, "_")
17
+ @underscored_name = name.tr("-", "_")
18
18
  super()
19
19
  end
20
20
 
@@ -22,11 +22,12 @@ module Sass::Script::Value
22
22
  def initialize(value = nil)
23
23
  value.freeze unless value.nil? || value == true || value == false
24
24
  @value = value
25
+ @options = nil
25
26
  end
26
27
 
27
28
  # Sets the options hash for this node,
28
29
  # as well as for all child nodes.
29
- # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
30
+ # See {file:SASS_REFERENCE.md#Options the Sass options documentation}.
30
31
  #
31
32
  # @param options [{Symbol => Object}] The options
32
33
  attr_writer :options
@@ -87,7 +88,7 @@ MSG
87
88
  # @return [Script::Value::String] A string containing both values
88
89
  # separated by `"="`
89
90
  def single_eq(other)
90
- Sass::Script::Value::String.new("#{to_s}=#{other.to_s}")
91
+ Sass::Script::Value::String.new("#{self}=#{other}")
91
92
  end
92
93
 
93
94
  # The SassScript `+` operation.
@@ -106,7 +107,7 @@ MSG
106
107
  # @return [Script::Value::String] A string containing both values
107
108
  # separated by `"-"`
108
109
  def minus(other)
109
- Sass::Script::Value::String.new("#{to_s}-#{other.to_s}")
110
+ Sass::Script::Value::String.new("#{self}-#{other}")
110
111
  end
111
112
 
112
113
  # The SassScript `/` operation.
@@ -115,7 +116,7 @@ MSG
115
116
  # @return [Script::Value::String] A string containing both values
116
117
  # separated by `"/"`
117
118
  def div(other)
118
- Sass::Script::Value::String.new("#{to_s}/#{other.to_s}")
119
+ Sass::Script::Value::String.new("#{self}/#{other}")
119
120
  end
120
121
 
121
122
  # The SassScript unary `+` operation (e.g. `+$a`).
@@ -124,7 +125,7 @@ MSG
124
125
  # @return [Script::Value::String] A string containing the value
125
126
  # preceded by `"+"`
126
127
  def unary_plus
127
- Sass::Script::Value::String.new("+#{to_s}")
128
+ Sass::Script::Value::String.new("+#{self}")
128
129
  end
129
130
 
130
131
  # The SassScript unary `-` operation (e.g. `-$a`).
@@ -133,7 +134,7 @@ MSG
133
134
  # @return [Script::Value::String] A string containing the value
134
135
  # preceded by `"-"`
135
136
  def unary_minus
136
- Sass::Script::Value::String.new("-#{to_s}")
137
+ Sass::Script::Value::String.new("-#{self}")
137
138
  end
138
139
 
139
140
  # The SassScript unary `/` operation (e.g. `/$a`).
@@ -142,13 +143,13 @@ MSG
142
143
  # @return [Script::Value::String] A string containing the value
143
144
  # preceded by `"/"`
144
145
  def unary_div
145
- Sass::Script::Value::String.new("/#{to_s}")
146
+ Sass::Script::Value::String.new("/#{self}")
146
147
  end
147
148
 
148
149
  # Returns the hash code of this value. Two objects' hash codes should be
149
150
  # equal if the objects are equal.
150
151
  #
151
- # @return [Fixnum] The hash code.
152
+ # @return [Integer for Ruby 2.4.0+, Fixnum for earlier Ruby versions] The hash code.
152
153
  def hash
153
154
  value.hash
154
155
  end
@@ -175,7 +176,7 @@ MSG
175
176
  eq(other).to_bool
176
177
  end
177
178
 
178
- # @return [Fixnum] The integer value of this value
179
+ # @return [Integer] The integer value of this value
179
180
  # @raise [Sass::SyntaxError] if this value isn't an integer
180
181
  def to_i
181
182
  raise Sass::SyntaxError.new("#{inspect} is not an integer.")