sass 3.3.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 (208) 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 +76 -62
  7. data/Rakefile +104 -24
  8. data/VERSION +1 -1
  9. data/VERSION_DATE +1 -1
  10. data/VERSION_NAME +1 -1
  11. data/bin/sass +1 -1
  12. data/bin/scss +1 -1
  13. data/extra/sass-spec-ref.sh +32 -0
  14. data/extra/update_watch.rb +1 -1
  15. data/lib/sass/cache_stores/filesystem.rb +9 -5
  16. data/lib/sass/cache_stores/memory.rb +4 -5
  17. data/lib/sass/callbacks.rb +2 -2
  18. data/lib/sass/css.rb +12 -13
  19. data/lib/sass/deprecation.rb +55 -0
  20. data/lib/sass/engine.rb +106 -70
  21. data/lib/sass/environment.rb +39 -19
  22. data/lib/sass/error.rb +17 -20
  23. data/lib/sass/exec/base.rb +199 -0
  24. data/lib/sass/exec/sass_convert.rb +283 -0
  25. data/lib/sass/exec/sass_scss.rb +440 -0
  26. data/lib/sass/exec.rb +5 -771
  27. data/lib/sass/features.rb +9 -2
  28. data/lib/sass/importers/base.rb +8 -3
  29. data/lib/sass/importers/filesystem.rb +30 -38
  30. data/lib/sass/logger/base.rb +8 -2
  31. data/lib/sass/logger/delayed.rb +50 -0
  32. data/lib/sass/logger.rb +8 -3
  33. data/lib/sass/media.rb +1 -4
  34. data/lib/sass/plugin/compiler.rb +224 -90
  35. data/lib/sass/plugin/configuration.rb +38 -22
  36. data/lib/sass/plugin/merb.rb +2 -2
  37. data/lib/sass/plugin/rack.rb +3 -3
  38. data/lib/sass/plugin/rails.rb +1 -1
  39. data/lib/sass/plugin/staleness_checker.rb +4 -4
  40. data/lib/sass/plugin.rb +6 -5
  41. data/lib/sass/script/css_lexer.rb +1 -1
  42. data/lib/sass/script/css_parser.rb +2 -3
  43. data/lib/sass/script/css_variable_warning.rb +52 -0
  44. data/lib/sass/script/functions.rb +739 -318
  45. data/lib/sass/script/lexer.rb +134 -54
  46. data/lib/sass/script/parser.rb +252 -56
  47. data/lib/sass/script/tree/funcall.rb +13 -6
  48. data/lib/sass/script/tree/interpolation.rb +127 -4
  49. data/lib/sass/script/tree/list_literal.rb +31 -4
  50. data/lib/sass/script/tree/literal.rb +4 -0
  51. data/lib/sass/script/tree/node.rb +21 -3
  52. data/lib/sass/script/tree/operation.rb +54 -1
  53. data/lib/sass/script/tree/selector.rb +26 -0
  54. data/lib/sass/script/tree/string_interpolation.rb +59 -38
  55. data/lib/sass/script/tree/variable.rb +1 -1
  56. data/lib/sass/script/tree.rb +1 -0
  57. data/lib/sass/script/value/base.rb +17 -14
  58. data/lib/sass/script/value/bool.rb +0 -5
  59. data/lib/sass/script/value/color.rb +78 -42
  60. data/lib/sass/script/value/helpers.rb +119 -2
  61. data/lib/sass/script/value/list.rb +0 -15
  62. data/lib/sass/script/value/map.rb +1 -1
  63. data/lib/sass/script/value/null.rb +0 -5
  64. data/lib/sass/script/value/number.rb +112 -31
  65. data/lib/sass/script/value/string.rb +102 -13
  66. data/lib/sass/script/value.rb +0 -1
  67. data/lib/sass/script.rb +3 -3
  68. data/lib/sass/scss/css_parser.rb +24 -4
  69. data/lib/sass/scss/parser.rb +290 -383
  70. data/lib/sass/scss/rx.rb +17 -9
  71. data/lib/sass/scss/static_parser.rb +306 -4
  72. data/lib/sass/scss.rb +0 -2
  73. data/lib/sass/selector/abstract_sequence.rb +35 -18
  74. data/lib/sass/selector/comma_sequence.rb +114 -19
  75. data/lib/sass/selector/pseudo.rb +266 -0
  76. data/lib/sass/selector/sequence.rb +146 -40
  77. data/lib/sass/selector/simple.rb +22 -33
  78. data/lib/sass/selector/simple_sequence.rb +122 -39
  79. data/lib/sass/selector.rb +57 -197
  80. data/lib/sass/shared.rb +2 -2
  81. data/lib/sass/source/map.rb +31 -14
  82. data/lib/sass/source/position.rb +4 -4
  83. data/lib/sass/stack.rb +2 -8
  84. data/lib/sass/supports.rb +10 -13
  85. data/lib/sass/tree/at_root_node.rb +1 -0
  86. data/lib/sass/tree/charset_node.rb +1 -1
  87. data/lib/sass/tree/comment_node.rb +1 -1
  88. data/lib/sass/tree/css_import_node.rb +9 -1
  89. data/lib/sass/tree/directive_node.rb +8 -2
  90. data/lib/sass/tree/error_node.rb +18 -0
  91. data/lib/sass/tree/extend_node.rb +1 -1
  92. data/lib/sass/tree/function_node.rb +9 -0
  93. data/lib/sass/tree/import_node.rb +6 -5
  94. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  95. data/lib/sass/tree/node.rb +5 -3
  96. data/lib/sass/tree/prop_node.rb +6 -7
  97. data/lib/sass/tree/rule_node.rb +26 -11
  98. data/lib/sass/tree/visitors/check_nesting.rb +56 -32
  99. data/lib/sass/tree/visitors/convert.rb +59 -44
  100. data/lib/sass/tree/visitors/cssize.rb +34 -30
  101. data/lib/sass/tree/visitors/deep_copy.rb +6 -1
  102. data/lib/sass/tree/visitors/extend.rb +15 -13
  103. data/lib/sass/tree/visitors/perform.rb +87 -50
  104. data/lib/sass/tree/visitors/set_options.rb +15 -1
  105. data/lib/sass/tree/visitors/to_css.rb +72 -43
  106. data/lib/sass/util/multibyte_string_scanner.rb +0 -2
  107. data/lib/sass/util/normalized_map.rb +0 -1
  108. data/lib/sass/util/subset_map.rb +2 -3
  109. data/lib/sass/util.rb +334 -154
  110. data/lib/sass/version.rb +7 -7
  111. data/lib/sass.rb +10 -8
  112. data/test/sass/cache_test.rb +62 -20
  113. data/test/sass/callbacks_test.rb +1 -1
  114. data/test/sass/compiler_test.rb +24 -11
  115. data/test/sass/conversion_test.rb +241 -50
  116. data/test/sass/css2sass_test.rb +73 -5
  117. data/test/sass/css_variable_test.rb +132 -0
  118. data/test/sass/encoding_test.rb +219 -0
  119. data/test/sass/engine_test.rb +343 -260
  120. data/test/sass/exec_test.rb +12 -2
  121. data/test/sass/extend_test.rb +333 -44
  122. data/test/sass/functions_test.rb +353 -260
  123. data/test/sass/importer_test.rb +40 -21
  124. data/test/sass/logger_test.rb +1 -1
  125. data/test/sass/more_results/more_import.css +1 -1
  126. data/test/sass/more_templates/more1.sass +10 -10
  127. data/test/sass/more_templates/more_import.sass +2 -2
  128. data/test/sass/plugin_test.rb +24 -21
  129. data/test/sass/results/compact.css +1 -1
  130. data/test/sass/results/complex.css +4 -4
  131. data/test/sass/results/expanded.css +1 -1
  132. data/test/sass/results/import.css +1 -1
  133. data/test/sass/results/import_charset_ibm866.css +2 -2
  134. data/test/sass/results/mixins.css +17 -17
  135. data/test/sass/results/nested.css +1 -1
  136. data/test/sass/results/parent_ref.css +2 -2
  137. data/test/sass/results/script.css +5 -5
  138. data/test/sass/results/scss_import.css +1 -1
  139. data/test/sass/script_conversion_test.rb +71 -39
  140. data/test/sass/script_test.rb +714 -123
  141. data/test/sass/scss/css_test.rb +213 -30
  142. data/test/sass/scss/rx_test.rb +8 -4
  143. data/test/sass/scss/scss_test.rb +766 -22
  144. data/test/sass/source_map_test.rb +263 -95
  145. data/test/sass/superselector_test.rb +210 -0
  146. data/test/sass/templates/_partial.sass +1 -1
  147. data/test/sass/templates/basic.sass +10 -10
  148. data/test/sass/templates/bork1.sass +1 -1
  149. data/test/sass/templates/bork5.sass +1 -1
  150. data/test/sass/templates/compact.sass +10 -10
  151. data/test/sass/templates/complex.sass +187 -187
  152. data/test/sass/templates/compressed.sass +10 -10
  153. data/test/sass/templates/expanded.sass +10 -10
  154. data/test/sass/templates/import.sass +2 -2
  155. data/test/sass/templates/importee.sass +3 -3
  156. data/test/sass/templates/mixins.sass +22 -22
  157. data/test/sass/templates/multiline.sass +4 -4
  158. data/test/sass/templates/nested.sass +13 -13
  159. data/test/sass/templates/parent_ref.sass +12 -12
  160. data/test/sass/templates/script.sass +70 -70
  161. data/test/sass/templates/scss_import.scss +2 -1
  162. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +1 -1
  163. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +2 -2
  164. data/test/sass/templates/subdir/subdir.sass +3 -3
  165. data/test/sass/templates/units.sass +10 -10
  166. data/test/sass/test_helper.rb +1 -1
  167. data/test/sass/util/multibyte_string_scanner_test.rb +11 -3
  168. data/test/sass/util/normalized_map_test.rb +1 -1
  169. data/test/sass/util/subset_map_test.rb +2 -2
  170. data/test/sass/util_test.rb +46 -45
  171. data/test/sass/value_helpers_test.rb +5 -7
  172. data/test/sass-spec.yml +3 -0
  173. data/test/test_helper.rb +7 -6
  174. data/vendor/listen/CHANGELOG.md +1 -228
  175. data/vendor/listen/Gemfile +5 -15
  176. data/vendor/listen/README.md +111 -77
  177. data/vendor/listen/Rakefile +0 -42
  178. data/vendor/listen/lib/listen/adapter.rb +195 -82
  179. data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
  180. data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
  181. data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
  182. data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
  183. data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
  184. data/vendor/listen/lib/listen/directory_record.rb +96 -61
  185. data/vendor/listen/lib/listen/listener.rb +135 -37
  186. data/vendor/listen/lib/listen/turnstile.rb +9 -5
  187. data/vendor/listen/lib/listen/version.rb +1 -1
  188. data/vendor/listen/lib/listen.rb +33 -19
  189. data/vendor/listen/listen.gemspec +6 -0
  190. data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
  191. data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
  192. data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
  193. data/vendor/listen/spec/listen/listener_spec.rb +128 -39
  194. data/vendor/listen/spec/listen_spec.rb +15 -21
  195. data/vendor/listen/spec/spec_helper.rb +4 -0
  196. data/vendor/listen/spec/support/adapter_helper.rb +52 -15
  197. data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
  198. data/vendor/listen/spec/support/listeners_helper.rb +30 -7
  199. metadata +310 -300
  200. data/CONTRIBUTING +0 -3
  201. data/ext/mkrf_conf.rb +0 -27
  202. data/lib/sass/script/value/deprecated_false.rb +0 -55
  203. data/lib/sass/scss/script_lexer.rb +0 -15
  204. data/lib/sass/scss/script_parser.rb +0 -25
  205. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  206. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  207. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  208. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
@@ -1,4 +1,5 @@
1
1
  require 'sass/script/lexer'
2
+ require 'sass/script/css_variable_warning'
2
3
 
3
4
  module Sass
4
5
  module Script
@@ -7,28 +8,34 @@ module Sass
7
8
  class Parser
8
9
  # The line number of the parser's current position.
9
10
  #
10
- # @return [Fixnum]
11
+ # @return [Integer]
11
12
  def line
12
13
  @lexer.line
13
14
  end
14
15
 
15
16
  # The column number of the parser's current position.
16
17
  #
17
- # @return [Fixnum]
18
+ # @return [Integer]
18
19
  def offset
19
20
  @lexer.offset
20
21
  end
21
22
 
22
23
  # @param str [String, StringScanner] The source text to parse
23
- # @param line [Fixnum] The line on which the SassScript appears.
24
+ # @param line [Integer] The line on which the SassScript appears.
24
25
  # Used for error reporting and sourcemap building
25
- # @param offset [Fixnum] The character (not byte) offset where the script starts in the line.
26
+ # @param offset [Integer] The character (not byte) offset where the script starts in the line.
26
27
  # Used for error reporting and sourcemap building
27
- # @param options [{Symbol => Object}] An options hash;
28
- # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
28
+ # @param options [{Symbol => Object}] An options hash; see
29
+ # {file:SASS_REFERENCE.md#Options the Sass options documentation}.
30
+ # This supports an additional `:allow_extra_text` option that controls
31
+ # whether the parser throws an error when extra text is encountered
32
+ # after the parsed construct.
29
33
  def initialize(str, line, offset, options = {})
30
34
  @options = options
35
+ @allow_extra_text = options.delete(:allow_extra_text)
31
36
  @lexer = lexer_class.new(str, line, offset, options)
37
+ @stop_at = nil
38
+ @css_variable_warning = nil
32
39
  end
33
40
 
34
41
  # Parses a SassScript expression within an interpolated segment (`#{}`).
@@ -36,13 +43,20 @@ module Sass
36
43
  # which signals the end of an interpolated segment,
37
44
  # it returns rather than throwing an error.
38
45
  #
46
+ # @param warn_for_color [Boolean] Whether raw color values passed to
47
+ # interoplation should cause a warning.
39
48
  # @return [Script::Tree::Node] The root node of the parse tree
40
49
  # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
41
- def parse_interpolated
50
+ def parse_interpolated(warn_for_color = false)
51
+ # Start two characters back to compensate for #{
52
+ start_pos = Sass::Source::Position.new(line, offset - 2)
42
53
  expr = assert_expr :expr
43
54
  assert_tok :end_interpolation
55
+ expr = Sass::Script::Tree::Interpolation.new(
56
+ nil, expr, nil, false, false, :warn_for_color => warn_for_color)
57
+ check_for_interpolation expr
44
58
  expr.options = @options
45
- expr
59
+ node(expr, start_pos)
46
60
  rescue Sass::SyntaxError => e
47
61
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
48
62
  raise e
@@ -50,12 +64,23 @@ module Sass
50
64
 
51
65
  # Parses a SassScript expression.
52
66
  #
67
+ # @param css_variable [Boolean] Whether this is the value of a CSS variable.
53
68
  # @return [Script::Tree::Node] The root node of the parse tree
54
69
  # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
55
- def parse
70
+ def parse(css_variable = false)
71
+ if css_variable
72
+ @css_variable_warning = CssVariableWarning.new
73
+ end
74
+
56
75
  expr = assert_expr :expr
57
76
  assert_done
58
77
  expr.options = @options
78
+ check_for_interpolation expr
79
+
80
+ if css_variable
81
+ @css_variable_warning.value = expr
82
+ end
83
+
59
84
  expr
60
85
  rescue Sass::SyntaxError => e
61
86
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
@@ -73,6 +98,7 @@ module Sass
73
98
  expr = assert_expr :expr
74
99
  assert_done
75
100
  expr.options = @options
101
+ check_for_interpolation expr
76
102
  expr
77
103
  rescue Sass::SyntaxError => e
78
104
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
@@ -96,10 +122,26 @@ module Sass
96
122
  end
97
123
  assert_done
98
124
 
99
- args.each {|a| a.options = @options}
100
- keywords.each {|k, v| v.options = @options}
101
- splat.options = @options if splat
102
- kwarg_splat.options = @options if kwarg_splat
125
+ args.each do |a|
126
+ check_for_interpolation a
127
+ a.options = @options
128
+ end
129
+
130
+ keywords.each do |_k, v|
131
+ check_for_interpolation v
132
+ v.options = @options
133
+ end
134
+
135
+ if splat
136
+ check_for_interpolation splat
137
+ splat.options = @options
138
+ end
139
+
140
+ if kwarg_splat
141
+ check_for_interpolation kwarg_splat
142
+ kwarg_splat.options = @options
143
+ end
144
+
103
145
  return args, keywords, splat, kwarg_splat
104
146
  rescue Sass::SyntaxError => e
105
147
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
@@ -116,10 +158,20 @@ module Sass
116
158
  assert_done
117
159
 
118
160
  args.each do |k, v|
161
+ check_for_interpolation k
119
162
  k.options = @options
120
- v.options = @options if v
163
+
164
+ if v
165
+ check_for_interpolation v
166
+ v.options = @options
167
+ end
121
168
  end
122
- splat.options = @options if splat
169
+
170
+ if splat
171
+ check_for_interpolation splat
172
+ splat.options = @options
173
+ end
174
+
123
175
  return args, splat
124
176
  rescue Sass::SyntaxError => e
125
177
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
@@ -136,10 +188,20 @@ module Sass
136
188
  assert_done
137
189
 
138
190
  args.each do |k, v|
191
+ check_for_interpolation k
139
192
  k.options = @options
140
- v.options = @options if v
193
+
194
+ if v
195
+ check_for_interpolation v
196
+ v.options = @options
197
+ end
141
198
  end
142
- splat.options = @options if splat
199
+
200
+ if splat
201
+ check_for_interpolation splat
202
+ splat.options = @options
203
+ end
204
+
143
205
  return args, splat
144
206
  rescue Sass::SyntaxError => e
145
207
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
@@ -159,6 +221,7 @@ module Sass
159
221
  end
160
222
 
161
223
  expr = assert_expr :funcall
224
+ check_for_interpolation expr
162
225
  expr.options = @options
163
226
  @lexer.unpeek!
164
227
  expr
@@ -169,12 +232,12 @@ module Sass
169
232
 
170
233
  # Parses a SassScript expression.
171
234
  #
172
- # @overload parse(str, line, offset, filename = nil)
173
235
  # @return [Script::Tree::Node] The root node of the parse tree
174
236
  # @see Parser#initialize
175
237
  # @see Parser#parse
176
- def self.parse(*args)
177
- new(*args).parse
238
+ def self.parse(value, line, offset, options = {})
239
+ css_variable = options.delete :css_variable
240
+ new(value, line, offset, options).parse(css_variable)
178
241
  end
179
242
 
180
243
  PRECEDENCE = [
@@ -187,6 +250,8 @@ module Sass
187
250
 
188
251
  ASSOCIATIVE = [:plus, :times]
189
252
 
253
+ VALID_CSS_OPS = [:comma, :single_eq, :space, :div]
254
+
190
255
  class << self
191
256
  # Returns an integer representing the precedence
192
257
  # of the given operator.
@@ -226,8 +291,12 @@ module Sass
226
291
  return other_interp
227
292
  end
228
293
 
229
- start_pos = source_position
230
- e = node(Tree::Operation.new(e, assert_expr(#{sub.inspect}), tok.type), start_pos)
294
+ if @css_variable_warning && !VALID_CSS_OPS.include?(tok.type)
295
+ @css_variable_warning.warn!
296
+ end
297
+
298
+ e = node(Tree::Operation.new(e, assert_expr(#{sub.inspect}), tok.type),
299
+ e.source_range.start_pos)
231
300
  end
232
301
  e
233
302
  end
@@ -241,6 +310,8 @@ RUBY
241
310
  interp = try_op_before_interp(tok)
242
311
  return interp if interp
243
312
  start_pos = source_position
313
+
314
+ @css_variable_warning.warn! if @css_variable_warning
244
315
  node(Tree::UnaryOperation.new(assert_expr(:unary_#{op}), :#{op}), start_pos)
245
316
  end
246
317
  RUBY
@@ -267,6 +338,7 @@ RUBY
267
338
  return list e, start_pos unless @lexer.peek && @lexer.peek.type == :colon
268
339
 
269
340
  pair = map_pair(e)
341
+ @css_variable_warning.warn! if @css_variable_warning
270
342
  map = node(Sass::Script::Tree::MapLiteral.new([pair]), start_pos)
271
343
  while try_tok(:comma)
272
344
  pair = map_pair
@@ -302,19 +374,32 @@ RUBY
302
374
  end
303
375
  return list unless (e = interpolation)
304
376
  list.elements << e
377
+ list.source_range.end_pos = list.elements.last.source_range.end_pos
305
378
  end
306
379
  list
307
380
  end
308
381
 
309
382
  production :equals, :interpolation, :single_eq
310
383
 
311
- def try_op_before_interp(op, prev = nil)
384
+ def try_op_before_interp(op, prev = nil, after_interp = false)
312
385
  return unless @lexer.peek && @lexer.peek.type == :begin_interpolation
386
+ unary = !prev && !after_interp
313
387
  wb = @lexer.whitespace?(op)
314
388
  str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]),
315
389
  op.source_range)
390
+
391
+ deprecation =
392
+ case op.type
393
+ when :comma; :potential
394
+ when :div, :single_eq; :none
395
+ when :plus; unary ? :none : :immediate
396
+ when :minus; @lexer.whitespace?(@lexer.peek) ? :immediate : :none
397
+ else; :immediate
398
+ end
399
+
316
400
  interp = node(
317
- Script::Tree::Interpolation.new(prev, str, nil, wb, !:wa, :originally_text),
401
+ Script::Tree::Interpolation.new(
402
+ prev, str, nil, wb, false, :originally_text => true, :deprecation => deprecation),
318
403
  (prev || str).source_range.start_pos)
319
404
  interpolation(interp)
320
405
  end
@@ -323,15 +408,25 @@ RUBY
323
408
  return unless @lexer.after_interpolation?
324
409
  op = try_toks(*ops)
325
410
  return unless op
326
- interp = try_op_before_interp(op, prev)
411
+ interp = try_op_before_interp(op, prev, :after_interp)
327
412
  return interp if interp
328
413
 
329
414
  wa = @lexer.whitespace?
330
415
  str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]),
331
416
  op.source_range)
332
417
  str.line = @lexer.line
418
+
419
+ deprecation =
420
+ case op.type
421
+ when :comma; :potential
422
+ when :div, :single_eq; :none
423
+ when :minus; @lexer.whitespace?(op) ? :immediate : :none
424
+ else; :immediate
425
+ end
333
426
  interp = node(
334
- Script::Tree::Interpolation.new(prev, str, assert_expr(name), !:wb, wa, :originally_text),
427
+ Script::Tree::Interpolation.new(
428
+ prev, str, assert_expr(name), false, wa,
429
+ :originally_text => true, :deprecation => deprecation),
335
430
  (prev || str).source_range.start_pos)
336
431
  interp
337
432
  end
@@ -340,15 +435,60 @@ RUBY
340
435
  e = first
341
436
  while (interp = try_tok(:begin_interpolation))
342
437
  wb = @lexer.whitespace?(interp)
343
- mid = parse_interpolated
438
+ char_before = @lexer.char(interp.pos - 1)
439
+ mid = without_css_variable_warning {assert_expr :expr}
440
+ assert_tok :end_interpolation
344
441
  wa = @lexer.whitespace?
442
+ char_after = @lexer.char
443
+
444
+ after = space
445
+ before_deprecation = e.is_a?(Script::Tree::Interpolation) ? e.deprecation : :none
446
+ after_deprecation = after.is_a?(Script::Tree::Interpolation) ? after.deprecation : :none
447
+
448
+ deprecation =
449
+ if before_deprecation == :immediate || after_deprecation == :immediate ||
450
+ # Warn for #{foo}$var and #{foo}(1) but not #{$foo}1.
451
+ (after && !wa && char_after =~ /[$(]/) ||
452
+ # Warn for $var#{foo} and (a)#{foo} but not a#{foo}.
453
+ (e && !wb && is_unsafe_before?(e, char_before))
454
+ :immediate
455
+ else
456
+ :potential
457
+ end
458
+
345
459
  e = node(
346
- Script::Tree::Interpolation.new(e, mid, space, wb, wa),
347
- (e || mid).source_range.start_pos)
460
+ Script::Tree::Interpolation.new(e, mid, after, wb, wa, :deprecation => deprecation),
461
+ (e || interp).source_range.start_pos)
348
462
  end
349
463
  e
350
464
  end
351
465
 
466
+ # Returns whether `expr` is unsafe to include before an interpolation.
467
+ #
468
+ # @param expr [Node] The expression to check.
469
+ # @param char_before [String] The character immediately before the
470
+ # interpolation being checked (and presumably the last character of
471
+ # `expr`).
472
+ # @return [Boolean]
473
+ def is_unsafe_before?(expr, char_before)
474
+ return char_before == ')' if is_safe_value?(expr)
475
+
476
+ # Otherwise, it's only safe if it was another interpolation.
477
+ !expr.is_a?(Script::Tree::Interpolation)
478
+ end
479
+
480
+ # Returns whether `expr` is safe as the value immediately before an
481
+ # interpolation.
482
+ #
483
+ # It's safe as long as the previous expression is an identifier or number,
484
+ # or a list whose last element is also safe.
485
+ def is_safe_value?(expr)
486
+ return is_safe_value?(expr.elements.last) if expr.is_a?(Script::Tree::ListLiteral)
487
+ return false unless expr.is_a?(Script::Tree::Literal)
488
+ expr.value.is_a?(Script::Value::Number) ||
489
+ (expr.value.is_a?(Script::Value::String) && expr.value.type == :identifier)
490
+ end
491
+
352
492
  def space
353
493
  start_pos = source_position
354
494
  e = or_expr
@@ -382,7 +522,7 @@ RUBY
382
522
 
383
523
  name = @lexer.next
384
524
  if (color = Sass::Script::Value::Color::COLOR_NAMES[name.value.downcase])
385
- literal_node(Sass::Script::Value::Color.new(color), name.source_range)
525
+ literal_node(Sass::Script::Value::Color.new(color, name.value), name.source_range)
386
526
  elsif name.value == "true"
387
527
  literal_node(Sass::Script::Value::Bool.new(true), name.source_range)
388
528
  elsif name.value == "false"
@@ -487,41 +627,36 @@ RUBY
487
627
  end
488
628
 
489
629
  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
630
+ first = try_tok(:special_fun)
631
+ return paren unless first
632
+ str = literal_node(first.value, first.source_range)
633
+ return str unless try_tok(:string_interpolation)
634
+ mid = without_css_variable_warning {assert_expr :expr}
635
+ assert_tok :end_interpolation
636
+ last = assert_expr(:special_fun)
637
+ node(
638
+ Tree::Interpolation.new(str, mid, last, false, false),
639
+ first.source_range.start_pos)
506
640
  end
507
641
 
508
642
  def paren
509
643
  return variable unless try_tok(:lparen)
510
- was_in_parens = @in_parens
511
- @in_parens = true
512
644
  start_pos = source_position
513
645
  e = map
646
+ e.force_division! if e
514
647
  end_pos = source_position
515
648
  assert_tok(:rparen)
516
- return e || node(Sass::Script::Tree::ListLiteral.new([], nil), start_pos, end_pos)
517
- ensure
518
- @in_parens = was_in_parens
649
+
650
+ @css_variable_warning.warn! if @css_variable_warning
651
+ e || node(Sass::Script::Tree::ListLiteral.new([], nil), start_pos, end_pos)
519
652
  end
520
653
 
521
654
  def variable
522
655
  start_pos = source_position
523
656
  c = try_tok(:const)
524
657
  return string unless c
658
+
659
+ @css_variable_warning.warn! if @css_variable_warning
525
660
  node(Tree::Variable.new(*c.value), start_pos)
526
661
  end
527
662
 
@@ -529,8 +664,9 @@ RUBY
529
664
  first = try_tok(:string)
530
665
  return number unless first
531
666
  str = literal_node(first.value, first.source_range)
532
- return str unless try_tok(:begin_interpolation)
533
- mid = parse_interpolated
667
+ return str unless try_tok(:string_interpolation)
668
+ mid = without_css_variable_warning {assert_expr :expr}
669
+ assert_tok :end_interpolation
534
670
  last = assert_expr(:string)
535
671
  node(Tree::StringInterpolation.new(str, mid, last), first.source_range.start_pos)
536
672
  end
@@ -539,13 +675,15 @@ RUBY
539
675
  tok = try_tok(:number)
540
676
  return selector unless tok
541
677
  num = tok.value
542
- num.original = num.to_s unless @in_parens
678
+ num.options = @options
679
+ num.original = num.to_s
543
680
  literal_node(num, tok.source_range.start_pos)
544
681
  end
545
682
 
546
683
  def selector
547
684
  tok = try_tok(:selector)
548
685
  return literal unless tok
686
+ @css_variable_warning.warn! if @css_variable_warning
549
687
  node(tok.value, tok.source_range.start_pos)
550
688
  end
551
689
 
@@ -563,6 +701,7 @@ RUBY
563
701
  :mixin_arglist => "mixin argument",
564
702
  :fn_arglist => "function argument",
565
703
  :splat => "...",
704
+ :special_fun => '")"',
566
705
  }
567
706
 
568
707
  def assert_expr(name, expected = nil)
@@ -596,8 +735,14 @@ RUBY
596
735
  end
597
736
 
598
737
  def assert_done
599
- return if @lexer.done?
600
- @lexer.expected!(EXPR_NAMES[:default])
738
+ if @allow_extra_text
739
+ # If extra text is allowed, just rewind the lexer so that the
740
+ # StringScanner is pointing to the end of the parsed text.
741
+ @lexer.unpeek!
742
+ else
743
+ return if @lexer.done?
744
+ @lexer.expected!(EXPR_NAMES[:default])
745
+ end
601
746
  end
602
747
 
603
748
  # @overload node(value, source_range)
@@ -626,11 +771,62 @@ RUBY
626
771
  range(source_range_or_start_pos, end_pos)
627
772
  end
628
773
 
774
+ node.css_variable_warning = @css_variable_warning
629
775
  node.line = source_range.start_pos.line
630
776
  node.source_range = source_range
631
777
  node.filename = @options[:filename]
632
778
  node
633
779
  end
780
+
781
+ # Runs the given block without CSS variable warnings enabled.
782
+ #
783
+ # CSS warnings don't apply within interpolation, so this is used to
784
+ # disable them.
785
+ #
786
+ # @yield []
787
+ def without_css_variable_warning
788
+ old_css_variable_warning = @css_variable_warning
789
+ @css_variable_warning = nil
790
+ yield
791
+ ensure
792
+ @css_variable_warning = old_css_variable_warning
793
+ end
794
+
795
+ # Checks a script node for any immediately-deprecated interpolations, and
796
+ # emits warnings for them.
797
+ #
798
+ # @param node [Sass::Script::Tree::Node]
799
+ def check_for_interpolation(node)
800
+ nodes = [node]
801
+ until nodes.empty?
802
+ node = nodes.pop
803
+ unless node.is_a?(Sass::Script::Tree::Interpolation) &&
804
+ node.deprecation == :immediate
805
+ nodes.concat node.children
806
+ next
807
+ end
808
+
809
+ interpolation_deprecation(node)
810
+ end
811
+ end
812
+
813
+ # Emits a deprecation warning for an interpolation node.
814
+ #
815
+ # @param node [Sass::Script::Tree::Node]
816
+ def interpolation_deprecation(interpolation)
817
+ return if @options[:_convert]
818
+ location = "on line #{interpolation.line}"
819
+ location << " of #{interpolation.filename}" if interpolation.filename
820
+ Sass::Util.sass_warn <<WARNING
821
+ DEPRECATION WARNING #{location}:
822
+ \#{} interpolation near operators will be simplified in a future version of Sass.
823
+ To preserve the current behavior, use quotes:
824
+
825
+ #{interpolation.to_quoted_equivalent.to_sass}
826
+
827
+ You can use the sass-convert command to automatically fix most cases.
828
+ WARNING
829
+ end
634
830
  end
635
831
  end
636
832
  end
@@ -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,13 +211,13 @@ 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)
215
218
  deprecated_argname = keywords.denormalize(deprecated_argname)
216
219
  Sass::Util.sass_warn("DEPRECATION WARNING: The `$#{deprecated_argname}' argument for " +
217
- "`#{name}()' has been renamed to `$#{argname}'.")
220
+ "`#{@name}()' has been renamed to `$#{argname}'.")
218
221
  keywords.delete(deprecated_argname)
219
222
  else
220
223
  raise Sass::SyntaxError.new("Function #{name} requires an argument named $#{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}'")