sass 3.4.25 → 3.7.4

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 (237) hide show
  1. checksums.yaml +4 -4
  2. data/CODE_OF_CONDUCT.md +1 -1
  3. data/CONTRIBUTING.md +3 -3
  4. data/README.md +17 -9
  5. data/VERSION +1 -1
  6. data/VERSION_DATE +1 -1
  7. data/VERSION_NAME +1 -1
  8. data/extra/sass-spec-ref.sh +9 -1
  9. data/lib/sass.rb +0 -7
  10. data/lib/sass/cache_stores/filesystem.rb +1 -1
  11. data/lib/sass/css.rb +1 -2
  12. data/lib/sass/engine.rb +39 -29
  13. data/lib/sass/environment.rb +26 -5
  14. data/lib/sass/error.rb +2 -2
  15. data/lib/sass/exec/base.rb +2 -13
  16. data/lib/sass/exec/sass_scss.rb +1 -5
  17. data/lib/sass/features.rb +1 -0
  18. data/lib/sass/importers/filesystem.rb +6 -4
  19. data/lib/sass/logger/base.rb +11 -0
  20. data/lib/sass/plugin/compiler.rb +20 -50
  21. data/lib/sass/plugin/configuration.rb +2 -2
  22. data/lib/sass/railtie.rb +1 -1
  23. data/lib/sass/script/css_parser.rb +4 -1
  24. data/lib/sass/script/functions.rb +308 -81
  25. data/lib/sass/script/lexer.rb +63 -9
  26. data/lib/sass/script/parser.rb +287 -118
  27. data/lib/sass/script/tree/funcall.rb +35 -34
  28. data/lib/sass/script/tree/interpolation.rb +0 -3
  29. data/lib/sass/script/tree/list_literal.rb +23 -8
  30. data/lib/sass/script/tree/map_literal.rb +2 -2
  31. data/lib/sass/script/tree/node.rb +0 -8
  32. data/lib/sass/script/tree/operation.rb +1 -8
  33. data/lib/sass/script/value.rb +2 -0
  34. data/lib/sass/script/value/arg_list.rb +1 -1
  35. data/lib/sass/script/value/base.rb +17 -0
  36. data/lib/sass/script/value/callable.rb +25 -0
  37. data/lib/sass/script/value/color.rb +8 -2
  38. data/lib/sass/script/value/function.rb +19 -0
  39. data/lib/sass/script/value/helpers.rb +37 -11
  40. data/lib/sass/script/value/list.rb +35 -14
  41. data/lib/sass/script/value/map.rb +2 -2
  42. data/lib/sass/script/value/number.rb +3 -2
  43. data/lib/sass/scss/css_parser.rb +6 -1
  44. data/lib/sass/scss/parser.rb +145 -56
  45. data/lib/sass/scss/rx.rb +5 -11
  46. data/lib/sass/scss/static_parser.rb +20 -42
  47. data/lib/sass/selector.rb +4 -0
  48. data/lib/sass/selector/abstract_sequence.rb +7 -6
  49. data/lib/sass/selector/comma_sequence.rb +9 -5
  50. data/lib/sass/selector/pseudo.rb +20 -3
  51. data/lib/sass/selector/sequence.rb +35 -10
  52. data/lib/sass/selector/simple.rb +9 -2
  53. data/lib/sass/selector/simple_sequence.rb +8 -4
  54. data/lib/sass/source/map.rb +2 -6
  55. data/lib/sass/stack.rb +21 -1
  56. data/lib/sass/tree/charset_node.rb +1 -1
  57. data/lib/sass/tree/prop_node.rb +45 -53
  58. data/lib/sass/tree/rule_node.rb +6 -8
  59. data/lib/sass/tree/visitors/check_nesting.rb +1 -1
  60. data/lib/sass/tree/visitors/convert.rb +2 -3
  61. data/lib/sass/tree/visitors/cssize.rb +4 -15
  62. data/lib/sass/tree/visitors/deep_copy.rb +1 -1
  63. data/lib/sass/tree/visitors/extend.rb +2 -8
  64. data/lib/sass/tree/visitors/perform.rb +23 -15
  65. data/lib/sass/tree/visitors/set_options.rb +1 -1
  66. data/lib/sass/tree/visitors/to_css.rb +49 -22
  67. data/lib/sass/util.rb +72 -310
  68. data/lib/sass/util/multibyte_string_scanner.rb +127 -131
  69. data/lib/sass/util/normalized_map.rb +1 -8
  70. data/lib/sass/version.rb +0 -4
  71. metadata +55 -202
  72. data/Rakefile +0 -453
  73. data/lib/sass/script/css_variable_warning.rb +0 -52
  74. data/lib/sass/util/cross_platform_random.rb +0 -19
  75. data/lib/sass/util/ordered_hash.rb +0 -192
  76. data/test/sass-spec.yml +0 -3
  77. data/test/sass/cache_test.rb +0 -131
  78. data/test/sass/callbacks_test.rb +0 -61
  79. data/test/sass/compiler_test.rb +0 -236
  80. data/test/sass/conversion_test.rb +0 -2188
  81. data/test/sass/css2sass_test.rb +0 -526
  82. data/test/sass/css_variable_test.rb +0 -132
  83. data/test/sass/data/hsl-rgb.txt +0 -319
  84. data/test/sass/encoding_test.rb +0 -219
  85. data/test/sass/engine_test.rb +0 -3447
  86. data/test/sass/exec_test.rb +0 -96
  87. data/test/sass/extend_test.rb +0 -1733
  88. data/test/sass/fixtures/test_staleness_check_across_importers.css +0 -1
  89. data/test/sass/fixtures/test_staleness_check_across_importers.scss +0 -1
  90. data/test/sass/functions_test.rb +0 -1977
  91. data/test/sass/importer_test.rb +0 -421
  92. data/test/sass/logger_test.rb +0 -58
  93. data/test/sass/mock_importer.rb +0 -49
  94. data/test/sass/more_results/more1.css +0 -9
  95. data/test/sass/more_results/more1_with_line_comments.css +0 -26
  96. data/test/sass/more_results/more_import.css +0 -29
  97. data/test/sass/more_templates/_more_partial.sass +0 -2
  98. data/test/sass/more_templates/more1.sass +0 -23
  99. data/test/sass/more_templates/more_import.sass +0 -11
  100. data/test/sass/plugin_test.rb +0 -556
  101. data/test/sass/results/alt.css +0 -4
  102. data/test/sass/results/basic.css +0 -9
  103. data/test/sass/results/cached_import_option.css +0 -3
  104. data/test/sass/results/compact.css +0 -5
  105. data/test/sass/results/complex.css +0 -86
  106. data/test/sass/results/compressed.css +0 -1
  107. data/test/sass/results/expanded.css +0 -19
  108. data/test/sass/results/filename_fn.css +0 -3
  109. data/test/sass/results/if.css +0 -3
  110. data/test/sass/results/import.css +0 -31
  111. data/test/sass/results/import_charset.css +0 -5
  112. data/test/sass/results/import_charset_1_8.css +0 -5
  113. data/test/sass/results/import_charset_ibm866.css +0 -5
  114. data/test/sass/results/import_content.css +0 -1
  115. data/test/sass/results/line_numbers.css +0 -49
  116. data/test/sass/results/mixins.css +0 -95
  117. data/test/sass/results/multiline.css +0 -24
  118. data/test/sass/results/nested.css +0 -22
  119. data/test/sass/results/options.css +0 -1
  120. data/test/sass/results/parent_ref.css +0 -13
  121. data/test/sass/results/script.css +0 -16
  122. data/test/sass/results/scss_import.css +0 -31
  123. data/test/sass/results/scss_importee.css +0 -2
  124. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +0 -1
  125. data/test/sass/results/subdir/subdir.css +0 -3
  126. data/test/sass/results/units.css +0 -11
  127. data/test/sass/results/warn.css +0 -0
  128. data/test/sass/results/warn_imported.css +0 -0
  129. data/test/sass/script_conversion_test.rb +0 -357
  130. data/test/sass/script_test.rb +0 -1431
  131. data/test/sass/scss/css_test.rb +0 -1281
  132. data/test/sass/scss/rx_test.rb +0 -160
  133. data/test/sass/scss/scss_test.rb +0 -4205
  134. data/test/sass/scss/test_helper.rb +0 -37
  135. data/test/sass/source_map_test.rb +0 -1055
  136. data/test/sass/superselector_test.rb +0 -210
  137. data/test/sass/templates/_cached_import_option_partial.scss +0 -1
  138. data/test/sass/templates/_double_import_loop2.sass +0 -1
  139. data/test/sass/templates/_filename_fn_import.scss +0 -11
  140. data/test/sass/templates/_imported_charset_ibm866.sass +0 -4
  141. data/test/sass/templates/_imported_charset_utf8.sass +0 -4
  142. data/test/sass/templates/_imported_content.sass +0 -3
  143. data/test/sass/templates/_partial.sass +0 -2
  144. data/test/sass/templates/_same_name_different_partiality.scss +0 -1
  145. data/test/sass/templates/alt.sass +0 -16
  146. data/test/sass/templates/basic.sass +0 -23
  147. data/test/sass/templates/bork1.sass +0 -2
  148. data/test/sass/templates/bork2.sass +0 -2
  149. data/test/sass/templates/bork3.sass +0 -2
  150. data/test/sass/templates/bork4.sass +0 -2
  151. data/test/sass/templates/bork5.sass +0 -3
  152. data/test/sass/templates/cached_import_option.scss +0 -3
  153. data/test/sass/templates/compact.sass +0 -17
  154. data/test/sass/templates/complex.sass +0 -305
  155. data/test/sass/templates/compressed.sass +0 -15
  156. data/test/sass/templates/double_import_loop1.sass +0 -1
  157. data/test/sass/templates/expanded.sass +0 -17
  158. data/test/sass/templates/filename_fn.scss +0 -18
  159. data/test/sass/templates/if.sass +0 -11
  160. data/test/sass/templates/import.sass +0 -12
  161. data/test/sass/templates/import_charset.sass +0 -9
  162. data/test/sass/templates/import_charset_1_8.sass +0 -6
  163. data/test/sass/templates/import_charset_ibm866.sass +0 -11
  164. data/test/sass/templates/import_content.sass +0 -4
  165. data/test/sass/templates/importee.less +0 -2
  166. data/test/sass/templates/importee.sass +0 -19
  167. data/test/sass/templates/line_numbers.sass +0 -13
  168. data/test/sass/templates/mixin_bork.sass +0 -5
  169. data/test/sass/templates/mixins.sass +0 -76
  170. data/test/sass/templates/multiline.sass +0 -20
  171. data/test/sass/templates/nested.sass +0 -25
  172. data/test/sass/templates/nested_bork1.sass +0 -2
  173. data/test/sass/templates/nested_bork2.sass +0 -2
  174. data/test/sass/templates/nested_bork3.sass +0 -2
  175. data/test/sass/templates/nested_bork4.sass +0 -2
  176. data/test/sass/templates/nested_import.sass +0 -2
  177. data/test/sass/templates/nested_mixin_bork.sass +0 -6
  178. data/test/sass/templates/options.sass +0 -2
  179. data/test/sass/templates/parent_ref.sass +0 -25
  180. data/test/sass/templates/same_name_different_ext.sass +0 -2
  181. data/test/sass/templates/same_name_different_ext.scss +0 -1
  182. data/test/sass/templates/same_name_different_partiality.scss +0 -1
  183. data/test/sass/templates/script.sass +0 -101
  184. data/test/sass/templates/scss_import.scss +0 -12
  185. data/test/sass/templates/scss_importee.scss +0 -1
  186. data/test/sass/templates/single_import_loop.sass +0 -1
  187. data/test/sass/templates/subdir/import_up1.scss +0 -1
  188. data/test/sass/templates/subdir/import_up2.scss +0 -1
  189. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +0 -2
  190. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +0 -3
  191. data/test/sass/templates/subdir/subdir.sass +0 -6
  192. data/test/sass/templates/units.sass +0 -11
  193. data/test/sass/templates/warn.sass +0 -3
  194. data/test/sass/templates/warn_imported.sass +0 -4
  195. data/test/sass/test_helper.rb +0 -8
  196. data/test/sass/util/multibyte_string_scanner_test.rb +0 -155
  197. data/test/sass/util/normalized_map_test.rb +0 -51
  198. data/test/sass/util/subset_map_test.rb +0 -91
  199. data/test/sass/util_test.rb +0 -438
  200. data/test/sass/value_helpers_test.rb +0 -179
  201. data/test/test_helper.rb +0 -110
  202. data/vendor/listen/CHANGELOG.md +0 -1
  203. data/vendor/listen/CONTRIBUTING.md +0 -38
  204. data/vendor/listen/Gemfile +0 -20
  205. data/vendor/listen/Guardfile +0 -8
  206. data/vendor/listen/LICENSE +0 -20
  207. data/vendor/listen/README.md +0 -349
  208. data/vendor/listen/Rakefile +0 -5
  209. data/vendor/listen/Vagrantfile +0 -96
  210. data/vendor/listen/lib/listen.rb +0 -54
  211. data/vendor/listen/lib/listen/adapter.rb +0 -327
  212. data/vendor/listen/lib/listen/adapters/bsd.rb +0 -75
  213. data/vendor/listen/lib/listen/adapters/darwin.rb +0 -48
  214. data/vendor/listen/lib/listen/adapters/linux.rb +0 -81
  215. data/vendor/listen/lib/listen/adapters/polling.rb +0 -58
  216. data/vendor/listen/lib/listen/adapters/windows.rb +0 -91
  217. data/vendor/listen/lib/listen/directory_record.rb +0 -406
  218. data/vendor/listen/lib/listen/listener.rb +0 -323
  219. data/vendor/listen/lib/listen/turnstile.rb +0 -32
  220. data/vendor/listen/lib/listen/version.rb +0 -3
  221. data/vendor/listen/listen.gemspec +0 -28
  222. data/vendor/listen/spec/listen/adapter_spec.rb +0 -149
  223. data/vendor/listen/spec/listen/adapters/bsd_spec.rb +0 -36
  224. data/vendor/listen/spec/listen/adapters/darwin_spec.rb +0 -37
  225. data/vendor/listen/spec/listen/adapters/linux_spec.rb +0 -47
  226. data/vendor/listen/spec/listen/adapters/polling_spec.rb +0 -68
  227. data/vendor/listen/spec/listen/adapters/windows_spec.rb +0 -30
  228. data/vendor/listen/spec/listen/directory_record_spec.rb +0 -1250
  229. data/vendor/listen/spec/listen/listener_spec.rb +0 -258
  230. data/vendor/listen/spec/listen/turnstile_spec.rb +0 -56
  231. data/vendor/listen/spec/listen_spec.rb +0 -67
  232. data/vendor/listen/spec/spec_helper.rb +0 -25
  233. data/vendor/listen/spec/support/adapter_helper.rb +0 -666
  234. data/vendor/listen/spec/support/directory_record_helper.rb +0 -57
  235. data/vendor/listen/spec/support/fixtures_helper.rb +0 -29
  236. data/vendor/listen/spec/support/listeners_helper.rb +0 -179
  237. data/vendor/listen/spec/support/platform_helper.rb +0 -15
@@ -51,6 +51,8 @@ module Sass
51
51
  ':' => :colon,
52
52
  '(' => :lparen,
53
53
  ')' => :rparen,
54
+ '[' => :lsquare,
55
+ ']' => :rsquare,
54
56
  ',' => :comma,
55
57
  'and' => :and,
56
58
  'or' => :or,
@@ -188,6 +190,14 @@ module Sass
188
190
  @scanner.string[pos, 1]
189
191
  end
190
192
 
193
+ # Consumes and returns single raw character from the input stream.
194
+ #
195
+ # @return [String]
196
+ def next_char
197
+ unpeek!
198
+ scan(/./)
199
+ end
200
+
191
201
  # Returns the next token without moving the lexer forward.
192
202
  #
193
203
  # @return [Token] The next token
@@ -198,6 +208,7 @@ module Sass
198
208
  # Rewinds the underlying StringScanner
199
209
  # to before the token returned by \{#peek}.
200
210
  def unpeek!
211
+ raise "[BUG] Can't unpeek before a queued token!" if @next_tok
201
212
  return unless @tok
202
213
  @scanner.pos = @tok.pos
203
214
  @line = @tok.source_range.start_pos.line
@@ -241,6 +252,30 @@ module Sass
241
252
  @scanner.string[old_pos...new_pos]
242
253
  end
243
254
 
255
+ # Runs a block, and rewinds the state of the lexer to the beginning of the
256
+ # block if it returns `nil` or `false`.
257
+ def try
258
+ old_pos = @scanner.pos
259
+ old_line = @line
260
+ old_offset = @offset
261
+ old_interpolation_stack = @interpolation_stack.dup
262
+ old_prev = @prev
263
+ old_tok = @tok
264
+ old_next_tok = @next_tok
265
+
266
+ result = yield
267
+ return result if result
268
+
269
+ @scanner.pos = old_pos
270
+ @line = old_line
271
+ @offset = old_offset
272
+ @interpolation_stack = old_interpolation_stack
273
+ @prev = old_prev
274
+ @tok = old_tok
275
+ @next_tok = old_next_tok
276
+ nil
277
+ end
278
+
244
279
  private
245
280
 
246
281
  def read_token
@@ -264,10 +299,14 @@ module Sass
264
299
  end
265
300
 
266
301
  def token
267
- if after_interpolation? && (interp = @interpolation_stack.pop)
268
- interp_type, interp_value = interp
302
+ if after_interpolation?
303
+ interp_type, interp_value = @interpolation_stack.pop
269
304
  if interp_type == :special_fun
270
305
  return special_fun_body(interp_value)
306
+ elsif interp_type.nil?
307
+ if @scanner.string[@scanner.pos - 1] == '}' && scan(REGULAR_EXPRESSIONS[:ident])
308
+ return [@scanner[2] ? :funcall : :ident, Sass::Util.normalize_ident_escapes(@scanner[1], start: false)]
309
+ end
271
310
  else
272
311
  raise "[BUG]: Unknown interp_type #{interp_type}" unless interp_type == :string
273
312
  return string(interp_value, true)
@@ -285,13 +324,12 @@ module Sass
285
324
 
286
325
  def _variable(rx)
287
326
  return unless scan(rx)
288
-
289
- [:const, @scanner[2]]
327
+ [:const, Sass::Util.normalize_ident_escapes(@scanner[2])]
290
328
  end
291
329
 
292
330
  def ident
293
331
  return unless scan(REGULAR_EXPRESSIONS[:ident])
294
- [@scanner[2] ? :funcall : :ident, @scanner[1]]
332
+ [@scanner[2] ? :funcall : :ident, Sass::Util.normalize_ident_escapes(@scanner[1])]
295
333
  end
296
334
 
297
335
  def string(re, open)
@@ -347,7 +385,9 @@ MESSAGE
347
385
 
348
386
  value = (@scanner[1] ? @scanner[1].to_f : @scanner[2].to_i) * (minus ? -1 : 1)
349
387
  value *= 10**@scanner[3].to_i if @scanner[3]
350
- script_number = Script::Value::Number.new(value, Array(@scanner[4]))
388
+ units = @scanner[4]
389
+ units = Sass::Util::normalize_ident_escapes(units) if units
390
+ script_number = Script::Value::Number.new(value, Array(units))
351
391
  [:number, script_number]
352
392
  end
353
393
 
@@ -366,15 +406,20 @@ MESSAGE
366
406
  # IDs in properties are used in the Basic User Interface Module
367
407
  # (http://www.w3.org/TR/css3-ui/).
368
408
  return unless scan(REGULAR_EXPRESSIONS[:id])
369
- if @scanner[0] =~ /^\#[0-9a-fA-F]+$/ && (@scanner[0].length == 4 || @scanner[0].length == 7)
409
+ if @scanner[0] =~ /^\#[0-9a-fA-F]+$/ &&
410
+ (@scanner[0].length == 4 || @scanner[0].length == 5 ||
411
+ @scanner[0].length == 7 || @scanner[0].length == 9)
370
412
  return [:color, Script::Value::Color.from_hex(@scanner[0])]
371
413
  end
372
- [:ident, @scanner[0]]
414
+ [:ident, Sass::Util.normalize_ident_escapes(@scanner[0])]
373
415
  end
374
416
 
375
417
  def color
376
418
  return unless @scanner.match?(REGULAR_EXPRESSIONS[:color])
377
- return unless @scanner[0].length == 4 || @scanner[0].length == 7
419
+ unless @scanner[0].length == 4 || @scanner[0].length == 5 ||
420
+ @scanner[0].length == 7 || @scanner[0].length == 9
421
+ return
422
+ end
378
423
  script_color = Script::Value::Color.from_hex(scan(REGULAR_EXPRESSIONS[:color]))
379
424
  [:color, script_color]
380
425
  end
@@ -382,6 +427,15 @@ MESSAGE
382
427
  def selector
383
428
  start_pos = source_position
384
429
  return unless scan(REGULAR_EXPRESSIONS[:selector])
430
+
431
+ if @scanner.peek(1) == '&'
432
+ filename = @options[:filename]
433
+ Sass::Util.sass_warn <<MESSAGE
434
+ WARNING on line #{line}, column #{offset}#{" of #{filename}" if filename}:
435
+ In Sass, "&&" means two copies of the parent selector. You probably want to use "and" instead.
436
+ MESSAGE
437
+ end
438
+
385
439
  script_selector = Script::Tree::Selector.new
386
440
  script_selector.source_range = range(start_pos)
387
441
  [:selector, script_selector]
@@ -1,5 +1,4 @@
1
1
  require 'sass/script/lexer'
2
- require 'sass/script/css_variable_warning'
3
2
 
4
3
  module Sass
5
4
  module Script
@@ -35,7 +34,6 @@ module Sass
35
34
  @allow_extra_text = options.delete(:allow_extra_text)
36
35
  @lexer = lexer_class.new(str, line, offset, options)
37
36
  @stop_at = nil
38
- @css_variable_warning = nil
39
37
  end
40
38
 
41
39
  # Parses a SassScript expression within an interpolated segment (`#{}`).
@@ -64,23 +62,13 @@ module Sass
64
62
 
65
63
  # Parses a SassScript expression.
66
64
  #
67
- # @param css_variable [Boolean] Whether this is the value of a CSS variable.
68
65
  # @return [Script::Tree::Node] The root node of the parse tree
69
66
  # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
70
- def parse(css_variable = false)
71
- if css_variable
72
- @css_variable_warning = CssVariableWarning.new
73
- end
74
-
67
+ def parse
75
68
  expr = assert_expr :expr
76
69
  assert_done
77
70
  expr.options = @options
78
71
  check_for_interpolation expr
79
-
80
- if css_variable
81
- @css_variable_warning.value = expr
82
- end
83
-
84
72
  expr
85
73
  rescue Sass::SyntaxError => e
86
74
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
@@ -90,7 +78,7 @@ module Sass
90
78
  # Parses a SassScript expression,
91
79
  # ending it when it encounters one of the given identifier tokens.
92
80
  #
93
- # @param tokens [#include?(String)] A set of strings that delimit the expression.
81
+ # @param tokens [#include?(String | Symbol)] A set of strings or symbols that delimit the expression.
94
82
  # @return [Script::Tree::Node] The root node of the parse tree
95
83
  # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
96
84
  def parse_until(tokens)
@@ -127,7 +115,7 @@ module Sass
127
115
  a.options = @options
128
116
  end
129
117
 
130
- keywords.each do |_k, v|
118
+ keywords.each do |_, v|
131
119
  check_for_interpolation v
132
120
  v.options = @options
133
121
  end
@@ -232,12 +220,12 @@ module Sass
232
220
 
233
221
  # Parses a SassScript expression.
234
222
  #
223
+ # @overload parse(str, line, offset, filename = nil)
235
224
  # @return [Script::Tree::Node] The root node of the parse tree
236
225
  # @see Parser#initialize
237
226
  # @see Parser#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)
227
+ def self.parse(*args)
228
+ new(*args).parse
241
229
  end
242
230
 
243
231
  PRECEDENCE = [
@@ -250,8 +238,6 @@ module Sass
250
238
 
251
239
  ASSOCIATIVE = [:plus, :times]
252
240
 
253
- VALID_CSS_OPS = [:comma, :single_eq, :space, :div]
254
-
255
241
  class << self
256
242
  # Returns an integer representing the precedence
257
243
  # of the given operator.
@@ -284,17 +270,17 @@ module Sass
284
270
  interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect})
285
271
  return interp if interp
286
272
  return unless e = #{sub}
287
- while tok = try_toks(#{ops.map {|o| o.inspect}.join(', ')})
273
+
274
+ while tok = peek_toks(#{ops.map {|o| o.inspect}.join(', ')})
275
+ return e if @stop_at && @stop_at.include?(tok.type)
276
+ @lexer.next
277
+
288
278
  if interp = try_op_before_interp(tok, e)
289
279
  other_interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}, interp)
290
280
  return interp unless other_interp
291
281
  return other_interp
292
282
  end
293
283
 
294
- if @css_variable_warning && !VALID_CSS_OPS.include?(tok.type)
295
- @css_variable_warning.warn!
296
- end
297
-
298
284
  e = node(Tree::Operation.new(e, assert_expr(#{sub.inspect}), tok.type),
299
285
  e.source_range.start_pos)
300
286
  end
@@ -310,8 +296,6 @@ RUBY
310
296
  interp = try_op_before_interp(tok)
311
297
  return interp if interp
312
298
  start_pos = source_position
313
-
314
- @css_variable_warning.warn! if @css_variable_warning
315
299
  node(Tree::UnaryOperation.new(assert_expr(:unary_#{op}), :#{op}), start_pos)
316
300
  end
317
301
  RUBY
@@ -338,12 +322,12 @@ RUBY
338
322
  return list e, start_pos unless @lexer.peek && @lexer.peek.type == :colon
339
323
 
340
324
  pair = map_pair(e)
341
- @css_variable_warning.warn! if @css_variable_warning
342
325
  map = node(Sass::Script::Tree::MapLiteral.new([pair]), start_pos)
343
326
  while try_tok(:comma)
344
327
  pair = map_pair
345
328
  return map unless pair
346
329
  map.pairs << pair
330
+ map.source_range.end_pos = map.pairs.last.last.source_range.end_pos
347
331
  end
348
332
  map
349
333
  end
@@ -364,7 +348,7 @@ RUBY
364
348
  def list(first, start_pos)
365
349
  return first unless @lexer.peek && @lexer.peek.type == :comma
366
350
 
367
- list = node(Sass::Script::Tree::ListLiteral.new([first], :comma), start_pos)
351
+ list = node(Sass::Script::Tree::ListLiteral.new([first], separator: :comma), start_pos)
368
352
  while (tok = try_tok(:comma))
369
353
  element_before_interp = list.elements.length == 1 ? list.elements.first : list
370
354
  if (interp = try_op_before_interp(tok, element_before_interp))
@@ -401,13 +385,16 @@ RUBY
401
385
  Script::Tree::Interpolation.new(
402
386
  prev, str, nil, wb, false, :originally_text => true, :deprecation => deprecation),
403
387
  (prev || str).source_range.start_pos)
404
- interpolation(interp)
388
+ interpolation(first: interp)
405
389
  end
406
390
 
407
391
  def try_ops_after_interp(ops, name, prev = nil)
408
392
  return unless @lexer.after_interpolation?
409
- op = try_toks(*ops)
393
+ op = peek_toks(*ops)
410
394
  return unless op
395
+ return if @stop_at && @stop_at.include?(op.type)
396
+ @lexer.next
397
+
411
398
  interp = try_op_before_interp(op, prev, :after_interp)
412
399
  return interp if interp
413
400
 
@@ -431,17 +418,17 @@ RUBY
431
418
  interp
432
419
  end
433
420
 
434
- def interpolation(first = space)
435
- e = first
421
+ def interpolation(first: nil, inner: :space)
422
+ e = first || send(inner)
436
423
  while (interp = try_tok(:begin_interpolation))
437
424
  wb = @lexer.whitespace?(interp)
438
425
  char_before = @lexer.char(interp.pos - 1)
439
- mid = without_css_variable_warning {assert_expr :expr}
426
+ mid = without_stop_at {assert_expr :expr}
440
427
  assert_tok :end_interpolation
441
428
  wa = @lexer.whitespace?
442
429
  char_after = @lexer.char
443
430
 
444
- after = space
431
+ after = send(inner)
445
432
  before_deprecation = e.is_a?(Script::Tree::Interpolation) ? e.deprecation : :none
446
433
  after_deprecation = after.is_a?(Script::Tree::Interpolation) ? after.deprecation : :none
447
434
 
@@ -500,7 +487,7 @@ RUBY
500
487
  if arr.size == 1
501
488
  arr.first
502
489
  else
503
- node(Sass::Script::Tree::ListLiteral.new(arr, :space), start_pos)
490
+ node(Sass::Script::Tree::ListLiteral.new(arr, separator: :space), start_pos)
504
491
  end
505
492
  end
506
493
 
@@ -517,7 +504,7 @@ RUBY
517
504
  unary :not, :ident
518
505
 
519
506
  def ident
520
- return funcall unless @lexer.peek && @lexer.peek.type == :ident
507
+ return css_min_max unless @lexer.peek && @lexer.peek.type == :ident
521
508
  return if @stop_at && @stop_at.include?(@lexer.peek.value)
522
509
 
523
510
  name = @lexer.next
@@ -534,6 +521,122 @@ RUBY
534
521
  end
535
522
  end
536
523
 
524
+ def css_min_max
525
+ @lexer.try do
526
+ next unless tok = try_tok(:funcall)
527
+ next unless %w[min max].include?(tok.value.downcase)
528
+ next unless contents = min_max_contents
529
+ node(array_to_interpolation(["#{tok.value}(", *contents]),
530
+ tok.source_range.start_pos, source_position)
531
+ end || funcall
532
+ end
533
+
534
+ def min_max_contents(allow_comma: true)
535
+ result = []
536
+ loop do
537
+ if tok = try_tok(:number)
538
+ result << tok.value.to_s
539
+ elsif value = min_max_interpolation
540
+ result << value
541
+ elsif value = min_max_calc
542
+ result << value.value
543
+ elsif value = min_max_function ||
544
+ min_max_parens ||
545
+ nested_min_max
546
+ result.concat value
547
+ else
548
+ return
549
+ end
550
+
551
+ if try_tok(:rparen)
552
+ result << ")"
553
+ return result
554
+ elsif tok = try_tok(:plus) || try_tok(:minus) || try_tok(:times) || try_tok(:div)
555
+ result << " #{Lexer::OPERATORS_REVERSE[tok.type]} "
556
+ elsif allow_comma && try_tok(:comma)
557
+ result << ", "
558
+ else
559
+ return
560
+ end
561
+ end
562
+ end
563
+
564
+ def min_max_interpolation
565
+ without_stop_at do
566
+ tok = try_tok(:begin_interpolation)
567
+ return unless tok
568
+ expr = without_stop_at {assert_expr :expr}
569
+ assert_tok :end_interpolation
570
+ expr
571
+ end
572
+ end
573
+
574
+ def min_max_function
575
+ return unless tok = peek_tok(:funcall)
576
+ return unless %w[calc env var].include?(tok.value.downcase)
577
+ @lexer.next
578
+ result = [tok.value, '(', *declaration_value, ')']
579
+ assert_tok :rparen
580
+ result
581
+ end
582
+
583
+ def min_max_calc
584
+ return unless tok = peek_tok(:special_fun)
585
+ return unless tok.value.value.downcase.start_with?("calc(")
586
+ @lexer.next.value
587
+ end
588
+
589
+ def min_max_parens
590
+ return unless try_tok :lparen
591
+ return unless contents = min_max_contents(allow_comma: false)
592
+ ['(', *contents]
593
+ end
594
+
595
+ def nested_min_max
596
+ return unless tok = peek_tok(:funcall)
597
+ return unless %w[min max].include?(tok.value.downcase)
598
+ @lexer.next
599
+ return unless contents = min_max_contents
600
+ [tok.value, '(', *contents]
601
+ end
602
+
603
+ def declaration_value
604
+ result = []
605
+ brackets = []
606
+ loop do
607
+ result << @lexer.str do
608
+ until @lexer.done? ||
609
+ peek_toks(:begin_interpolation,
610
+ :end_interpolation,
611
+ :lcurly,
612
+ :lparen,
613
+ :lsquare,
614
+ :rparen,
615
+ :rsquare)
616
+ @lexer.next || @lexer.next_char
617
+ end
618
+ end
619
+
620
+ if try_tok(:begin_interpolation)
621
+ result << assert_expr(:expr)
622
+ assert_tok :end_interpolation
623
+ elsif tok = try_toks(:lcurly, :lparen, :lsquare)
624
+ brackets << case tok.type
625
+ when :lcurly; :end_interpolation
626
+ when :lparen; :rparen
627
+ when :lsquare; :rsquare
628
+ end
629
+ result << Lexer::OPERATORS_REVERSE[tok.type]
630
+ elsif brackets.empty?
631
+ return result
632
+ else
633
+ bracket = brackets.pop
634
+ assert_tok bracket
635
+ result << Lexer::OPERATORS_REVERSE[bracket]
636
+ end
637
+ end
638
+ end
639
+
537
640
  def funcall
538
641
  tok = try_tok(:funcall)
539
642
  return raw unless tok
@@ -549,29 +652,31 @@ RUBY
549
652
  else
550
653
  return [], nil unless try_tok(:lparen)
551
654
  end
552
- return [], nil if try_tok(:rparen)
553
655
 
554
- res = []
555
- splat = nil
556
- must_have_default = false
557
- loop do
558
- c = assert_tok(:const)
559
- var = node(Script::Tree::Variable.new(c.value), c.source_range)
560
- if try_tok(:colon)
561
- val = assert_expr(:space)
562
- must_have_default = true
563
- elsif try_tok(:splat)
564
- splat = var
565
- break
566
- elsif must_have_default
567
- raise SyntaxError.new(
568
- "Required argument #{var.inspect} must come before any optional arguments.")
656
+ without_stop_at do
657
+ res = []
658
+ splat = nil
659
+ must_have_default = false
660
+ loop do
661
+ break if peek_tok(:rparen)
662
+ c = assert_tok(:const)
663
+ var = node(Script::Tree::Variable.new(c.value), c.source_range)
664
+ if try_tok(:colon)
665
+ val = assert_expr(:space)
666
+ must_have_default = true
667
+ elsif try_tok(:splat)
668
+ splat = var
669
+ break
670
+ elsif must_have_default
671
+ raise SyntaxError.new(
672
+ "Required argument #{var.inspect} must come before any optional arguments.")
673
+ end
674
+ res << [var, val]
675
+ break unless try_tok(:comma)
569
676
  end
570
- res << [var, val]
571
- break unless try_tok(:comma)
677
+ assert_tok(:rparen)
678
+ return res, splat
572
679
  end
573
- assert_tok(:rparen)
574
- return res, splat
575
680
  end
576
681
 
577
682
  def fn_arglist
@@ -583,40 +688,37 @@ RUBY
583
688
  end
584
689
 
585
690
  def arglist(subexpr, description)
586
- args = []
587
- keywords = Sass::Util::NormalizedMap.new
588
- e = send(subexpr)
589
-
590
- return [args, keywords] unless e
591
-
592
- splat = nil
593
- loop do
594
- if @lexer.peek && @lexer.peek.type == :colon
595
- name = e
596
- @lexer.expected!("comma") unless name.is_a?(Tree::Variable)
597
- assert_tok(:colon)
598
- value = assert_expr(subexpr, description)
599
-
600
- if keywords[name.name]
601
- raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
602
- end
691
+ without_stop_at do
692
+ args = []
693
+ keywords = Sass::Util::NormalizedMap.new
694
+ splat = nil
695
+ while (e = send(subexpr))
696
+ if @lexer.peek && @lexer.peek.type == :colon
697
+ name = e
698
+ @lexer.expected!("comma") unless name.is_a?(Tree::Variable)
699
+ assert_tok(:colon)
700
+ value = assert_expr(subexpr, description)
701
+
702
+ if keywords[name.name]
703
+ raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
704
+ end
603
705
 
604
- keywords[name.name] = value
605
- else
606
- if try_tok(:splat)
607
- return args, keywords, splat, e if splat
608
- splat, e = e, nil
609
- elsif splat
610
- raise SyntaxError.new("Only keyword arguments may follow variable arguments (...).")
611
- elsif !keywords.empty?
612
- raise SyntaxError.new("Positional arguments must come before keyword arguments.")
706
+ keywords[name.name] = value
707
+ else
708
+ if try_tok(:splat)
709
+ return args, keywords, splat, e if splat
710
+ splat, e = e, nil
711
+ elsif splat
712
+ raise SyntaxError.new("Only keyword arguments may follow variable arguments (...).")
713
+ elsif !keywords.empty?
714
+ raise SyntaxError.new("Positional arguments must come before keyword arguments.")
715
+ end
716
+ args << e if e
613
717
  end
614
718
 
615
- args << e if e
719
+ return args, keywords, splat unless try_tok(:comma)
616
720
  end
617
-
618
- return args, keywords, splat unless try_tok(:comma)
619
- e = assert_expr(subexpr, description)
721
+ return args, keywords
620
722
  end
621
723
  end
622
724
 
@@ -628,10 +730,10 @@ RUBY
628
730
 
629
731
  def special_fun
630
732
  first = try_tok(:special_fun)
631
- return paren unless first
733
+ return square_list unless first
632
734
  str = literal_node(first.value, first.source_range)
633
735
  return str unless try_tok(:string_interpolation)
634
- mid = without_css_variable_warning {assert_expr :expr}
736
+ mid = without_stop_at {assert_expr :expr}
635
737
  assert_tok :end_interpolation
636
738
  last = assert_expr(:special_fun)
637
739
  node(
@@ -639,24 +741,68 @@ RUBY
639
741
  first.source_range.start_pos)
640
742
  end
641
743
 
642
- def paren
643
- return variable unless try_tok(:lparen)
744
+ def square_list
644
745
  start_pos = source_position
645
- e = map
646
- e.force_division! if e
647
- end_pos = source_position
648
- assert_tok(:rparen)
746
+ return paren unless try_tok(:lsquare)
747
+
748
+ without_stop_at do
749
+ space_start_pos = source_position
750
+ e = interpolation(inner: :or_expr)
751
+ separator = nil
752
+ if e
753
+ elements = [e]
754
+ while (e = interpolation(inner: :or_expr))
755
+ elements << e
756
+ end
757
+
758
+ # If there's a comma after a space-separated list, it's actually a
759
+ # space-separated list nested in a comma-separated list.
760
+ if try_tok(:comma)
761
+ e = if elements.length == 1
762
+ elements.first
763
+ else
764
+ node(
765
+ Sass::Script::Tree::ListLiteral.new(elements, separator: :space),
766
+ space_start_pos)
767
+ end
768
+ elements = [e]
769
+
770
+ while (e = space)
771
+ elements << e
772
+ break unless try_tok(:comma)
773
+ end
774
+ separator = :comma
775
+ else
776
+ separator = :space if elements.length > 1
777
+ end
778
+ else
779
+ elements = []
780
+ end
781
+
782
+ assert_tok(:rsquare)
783
+ end_pos = source_position
649
784
 
650
- @css_variable_warning.warn! if @css_variable_warning
651
- e || node(Sass::Script::Tree::ListLiteral.new([], nil), start_pos, end_pos)
785
+ node(Sass::Script::Tree::ListLiteral.new(elements, separator: separator, bracketed: true),
786
+ start_pos, end_pos)
787
+ end
788
+ end
789
+
790
+ def paren
791
+ return variable unless try_tok(:lparen)
792
+ without_stop_at do
793
+ start_pos = source_position
794
+ e = map
795
+ e.force_division! if e
796
+ end_pos = source_position
797
+ assert_tok(:rparen)
798
+ e || node(Sass::Script::Tree::ListLiteral.new([]), start_pos, end_pos)
799
+ end
652
800
  end
653
801
 
654
802
  def variable
655
803
  start_pos = source_position
656
804
  c = try_tok(:const)
657
805
  return string unless c
658
-
659
- @css_variable_warning.warn! if @css_variable_warning
660
806
  node(Tree::Variable.new(*c.value), start_pos)
661
807
  end
662
808
 
@@ -665,9 +811,9 @@ RUBY
665
811
  return number unless first
666
812
  str = literal_node(first.value, first.source_range)
667
813
  return str unless try_tok(:string_interpolation)
668
- mid = without_css_variable_warning {assert_expr :expr}
814
+ mid = assert_expr :expr
669
815
  assert_tok :end_interpolation
670
- last = assert_expr(:string)
816
+ last = without_stop_at {assert_expr(:string)}
671
817
  node(Tree::StringInterpolation.new(str, mid, last), first.source_range.start_pos)
672
818
  end
673
819
 
@@ -683,7 +829,6 @@ RUBY
683
829
  def selector
684
830
  tok = try_tok(:selector)
685
831
  return literal unless tok
686
- @css_variable_warning.warn! if @css_variable_warning
687
832
  node(tok.value, tok.source_range.start_pos)
688
833
  end
689
834
 
@@ -723,15 +868,23 @@ RUBY
723
868
  @lexer.expected!(names.map {|tok| Lexer::TOKEN_NAMES[tok] || tok}.join(" or "))
724
869
  end
725
870
 
726
- def try_tok(name)
871
+ def peek_tok(name)
727
872
  # Avoids an array allocation caused by argument globbing in the try_toks method.
728
873
  peeked = @lexer.peek
729
- peeked && name == peeked.type && @lexer.next
874
+ peeked && name == peeked.type && peeked
730
875
  end
731
876
 
732
- def try_toks(*names)
877
+ def peek_toks(*names)
733
878
  peeked = @lexer.peek
734
- peeked && names.include?(peeked.type) && @lexer.next
879
+ peeked && names.include?(peeked.type) && peeked
880
+ end
881
+
882
+ def try_tok(name)
883
+ peek_tok(name) && @lexer.next
884
+ end
885
+
886
+ def try_toks(*names)
887
+ peek_toks(*names) && @lexer.next
735
888
  end
736
889
 
737
890
  def assert_done
@@ -745,6 +898,14 @@ RUBY
745
898
  end
746
899
  end
747
900
 
901
+ def without_stop_at
902
+ old_stop_at = @stop_at
903
+ @stop_at = nil
904
+ yield
905
+ ensure
906
+ @stop_at = old_stop_at
907
+ end
908
+
748
909
  # @overload node(value, source_range)
749
910
  # @param value [Sass::Script::Value::Base]
750
911
  # @param source_range [Sass::Source::Range]
@@ -771,25 +932,33 @@ RUBY
771
932
  range(source_range_or_start_pos, end_pos)
772
933
  end
773
934
 
774
- node.css_variable_warning = @css_variable_warning
775
935
  node.line = source_range.start_pos.line
776
936
  node.source_range = source_range
777
937
  node.filename = @options[:filename]
778
938
  node
779
939
  end
780
940
 
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.
941
+ # Converts an array of strings and expressions to a string interoplation
942
+ # object.
785
943
  #
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
944
+ # @param array [Array<Script::Tree:Node | String>]
945
+ # @return [Script::Tree::StringInterpolation]
946
+ def array_to_interpolation(array)
947
+ Sass::Util.merge_adjacent_strings(array).reverse.inject(nil) do |after, value|
948
+ if value.is_a?(::String)
949
+ literal = Sass::Script::Tree::Literal.new(
950
+ Sass::Script::Value::String.new(value))
951
+ next literal unless after
952
+ Sass::Script::Tree::StringInterpolation.new(literal, after.mid, after.after)
953
+ else
954
+ Sass::Script::Tree::StringInterpolation.new(
955
+ Sass::Script::Tree::Literal.new(
956
+ Sass::Script::Value::String.new('')),
957
+ value,
958
+ after || Sass::Script::Tree::Literal.new(
959
+ Sass::Script::Value::String.new('')))
960
+ end
961
+ end
793
962
  end
794
963
 
795
964
  # Checks a script node for any immediately-deprecated interpolations, and