sass 3.6.0 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. checksums.yaml +5 -5
  2. data/CONTRIBUTING.md +1 -1
  3. data/README.md +10 -0
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/lib/sass/script/css_parser.rb +4 -1
  7. data/lib/sass/script/functions.rb +15 -25
  8. data/lib/sass/script/lexer.rb +45 -7
  9. data/lib/sass/script/parser.rb +259 -93
  10. data/lib/sass/scss/parser.rb +43 -16
  11. data/lib/sass/scss/static_parser.rb +15 -15
  12. data/lib/sass/util.rb +31 -0
  13. metadata +8 -156
  14. data/Rakefile +0 -364
  15. data/test/sass-spec.yml +0 -3
  16. data/test/sass/cache_test.rb +0 -130
  17. data/test/sass/callbacks_test.rb +0 -60
  18. data/test/sass/compiler_test.rb +0 -225
  19. data/test/sass/conversion_test.rb +0 -2138
  20. data/test/sass/css2sass_test.rb +0 -523
  21. data/test/sass/css_variable_test.rb +0 -237
  22. data/test/sass/data/hsl-rgb.txt +0 -319
  23. data/test/sass/encoding_test.rb +0 -188
  24. data/test/sass/engine_test.rb +0 -3499
  25. data/test/sass/exec_test.rb +0 -95
  26. data/test/sass/extend_test.rb +0 -1679
  27. data/test/sass/fixtures/test_staleness_check_across_importers.css +0 -1
  28. data/test/sass/fixtures/test_staleness_check_across_importers.scss +0 -1
  29. data/test/sass/functions_test.rb +0 -2021
  30. data/test/sass/importer_test.rb +0 -420
  31. data/test/sass/logger_test.rb +0 -57
  32. data/test/sass/mock_importer.rb +0 -49
  33. data/test/sass/more_results/more1.css +0 -9
  34. data/test/sass/more_results/more1_with_line_comments.css +0 -26
  35. data/test/sass/more_results/more_import.css +0 -29
  36. data/test/sass/more_templates/_more_partial.sass +0 -2
  37. data/test/sass/more_templates/more1.sass +0 -23
  38. data/test/sass/more_templates/more_import.sass +0 -11
  39. data/test/sass/plugin_test.rb +0 -552
  40. data/test/sass/results/alt.css +0 -4
  41. data/test/sass/results/basic.css +0 -9
  42. data/test/sass/results/cached_import_option.css +0 -3
  43. data/test/sass/results/compact.css +0 -5
  44. data/test/sass/results/complex.css +0 -86
  45. data/test/sass/results/compressed.css +0 -1
  46. data/test/sass/results/expanded.css +0 -19
  47. data/test/sass/results/filename_fn.css +0 -3
  48. data/test/sass/results/if.css +0 -3
  49. data/test/sass/results/import.css +0 -31
  50. data/test/sass/results/import_charset.css +0 -5
  51. data/test/sass/results/import_charset_ibm866.css +0 -5
  52. data/test/sass/results/import_content.css +0 -1
  53. data/test/sass/results/line_numbers.css +0 -49
  54. data/test/sass/results/mixins.css +0 -95
  55. data/test/sass/results/multiline.css +0 -24
  56. data/test/sass/results/nested.css +0 -22
  57. data/test/sass/results/options.css +0 -1
  58. data/test/sass/results/parent_ref.css +0 -13
  59. data/test/sass/results/script.css +0 -16
  60. data/test/sass/results/scss_import.css +0 -31
  61. data/test/sass/results/scss_importee.css +0 -2
  62. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +0 -1
  63. data/test/sass/results/subdir/subdir.css +0 -3
  64. data/test/sass/results/units.css +0 -11
  65. data/test/sass/results/warn.css +0 -0
  66. data/test/sass/results/warn_imported.css +0 -0
  67. data/test/sass/script_conversion_test.rb +0 -365
  68. data/test/sass/script_test.rb +0 -1427
  69. data/test/sass/scss/css_test.rb +0 -1266
  70. data/test/sass/scss/rx_test.rb +0 -159
  71. data/test/sass/scss/scss_test.rb +0 -4238
  72. data/test/sass/scss/test_helper.rb +0 -37
  73. data/test/sass/source_map_test.rb +0 -1063
  74. data/test/sass/superselector_test.rb +0 -209
  75. data/test/sass/templates/_cached_import_option_partial.scss +0 -1
  76. data/test/sass/templates/_double_import_loop2.sass +0 -1
  77. data/test/sass/templates/_filename_fn_import.scss +0 -11
  78. data/test/sass/templates/_imported_charset_ibm866.sass +0 -4
  79. data/test/sass/templates/_imported_charset_utf8.sass +0 -4
  80. data/test/sass/templates/_imported_content.sass +0 -3
  81. data/test/sass/templates/_partial.sass +0 -2
  82. data/test/sass/templates/_same_name_different_partiality.scss +0 -1
  83. data/test/sass/templates/alt.sass +0 -16
  84. data/test/sass/templates/basic.sass +0 -23
  85. data/test/sass/templates/bork1.sass +0 -2
  86. data/test/sass/templates/bork2.sass +0 -2
  87. data/test/sass/templates/bork3.sass +0 -2
  88. data/test/sass/templates/bork4.sass +0 -2
  89. data/test/sass/templates/bork5.sass +0 -3
  90. data/test/sass/templates/cached_import_option.scss +0 -3
  91. data/test/sass/templates/compact.sass +0 -17
  92. data/test/sass/templates/complex.sass +0 -305
  93. data/test/sass/templates/compressed.sass +0 -15
  94. data/test/sass/templates/double_import_loop1.sass +0 -1
  95. data/test/sass/templates/expanded.sass +0 -17
  96. data/test/sass/templates/filename_fn.scss +0 -18
  97. data/test/sass/templates/if.sass +0 -11
  98. data/test/sass/templates/import.sass +0 -12
  99. data/test/sass/templates/import_charset.sass +0 -9
  100. data/test/sass/templates/import_charset_ibm866.sass +0 -11
  101. data/test/sass/templates/import_content.sass +0 -4
  102. data/test/sass/templates/importee.less +0 -2
  103. data/test/sass/templates/importee.sass +0 -19
  104. data/test/sass/templates/line_numbers.sass +0 -13
  105. data/test/sass/templates/mixin_bork.sass +0 -5
  106. data/test/sass/templates/mixins.sass +0 -76
  107. data/test/sass/templates/multiline.sass +0 -20
  108. data/test/sass/templates/nested.sass +0 -25
  109. data/test/sass/templates/nested_bork1.sass +0 -2
  110. data/test/sass/templates/nested_bork2.sass +0 -2
  111. data/test/sass/templates/nested_bork3.sass +0 -2
  112. data/test/sass/templates/nested_bork4.sass +0 -2
  113. data/test/sass/templates/nested_import.sass +0 -2
  114. data/test/sass/templates/nested_mixin_bork.sass +0 -6
  115. data/test/sass/templates/options.sass +0 -2
  116. data/test/sass/templates/parent_ref.sass +0 -25
  117. data/test/sass/templates/same_name_different_ext.sass +0 -2
  118. data/test/sass/templates/same_name_different_ext.scss +0 -1
  119. data/test/sass/templates/same_name_different_partiality.scss +0 -1
  120. data/test/sass/templates/script.sass +0 -101
  121. data/test/sass/templates/scss_import.scss +0 -12
  122. data/test/sass/templates/scss_importee.scss +0 -1
  123. data/test/sass/templates/single_import_loop.sass +0 -1
  124. data/test/sass/templates/subdir/import_up1.scss +0 -1
  125. data/test/sass/templates/subdir/import_up2.scss +0 -1
  126. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +0 -2
  127. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +0 -3
  128. data/test/sass/templates/subdir/subdir.sass +0 -6
  129. data/test/sass/templates/units.sass +0 -11
  130. data/test/sass/templates/warn.sass +0 -3
  131. data/test/sass/templates/warn_imported.sass +0 -4
  132. data/test/sass/test_helper.rb +0 -8
  133. data/test/sass/util/multibyte_string_scanner_test.rb +0 -152
  134. data/test/sass/util/normalized_map_test.rb +0 -50
  135. data/test/sass/util/subset_map_test.rb +0 -90
  136. data/test/sass/util_test.rb +0 -393
  137. data/test/sass/value_helpers_test.rb +0 -178
  138. data/test/test_helper.rb +0 -149
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 82509c4a444a2c000d6f55f2d47502dcb94b70e903da358f22e00869cd292d35
4
- data.tar.gz: 79d675357ab4eefa7cb6960d489de1ee2f2c73e901b8142865db5cdf54821357
2
+ SHA1:
3
+ metadata.gz: e9abf6343867b108ab17ccfa5170666b2a45f904
4
+ data.tar.gz: b5744bc72de099acd9ead2a0df936c122b80eab7
5
5
  SHA512:
6
- metadata.gz: 701b7fdcc0f1a03af08ef639680040b307f2e5e3210cd785434ec8296ccfb5a10dc1258906914fdacf73414a3c8aaeaa5179d545faa524b6e450cc39c0980069
7
- data.tar.gz: 8db9b3fa129857550a352af2973cd9795874645ef8fc869a6c1c13643e01fbd98cacd79e11af9a88004f630847ec0b3ea6db8ddeb7baec847068c2ec47f160fc
6
+ metadata.gz: 2a4f75cb603cc8c249e13a5d20d35b84302a98834e3a768a38c3fefffdc8b3ab52217c118855ea087ce12ec24a5327302e5516e02112c122456299ad7aacf446
7
+ data.tar.gz: a56c6e88b050deba0d2d2b6c74c784e5c71337e6b6a1f0ce5432aa9d75395fe27654b6ff9e9d5f3fa91abc5d02a800337c67710f6bb06143f514a024f24e995e
@@ -18,7 +18,7 @@ is regularly merged into the one below: `stable` into `next`, `next` into
18
18
  `master`.
19
19
 
20
20
  * The `stable` branch is the default—it's what GitHub shows if you go to
21
- [sass/sass](https://github.com/sass/sass), and it's the default place for pull
21
+ [sass/ruby-sass](https://github.com/sass/ruby-sass), and it's the default place for pull
22
22
  requests to go. This branch is where we work on the next patch release. Bug
23
23
  fixes and documentation improvements belong here, but not new features.
24
24
 
data/README.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## Ruby Sass is Deprecated!
2
+
3
+ Ruby Sass is in a sunset period where only critical bugs and CSS compatibility
4
+ issues will be fixed. It will be completely unmaintained as of 26 March 2019.
5
+ See [the Sass website][] for details, and consider switching to the [`sassc`
6
+ gem][]
7
+
8
+ [the Sass website]: https://sass-lang.com/ruby-sass
9
+ [`sassc` gem]: https://rubygems.org/gems/sassc
10
+
1
11
  # Sass [![Travis Build Status](https://travis-ci.org/sass/ruby-sass.svg?branch=next)](https://travis-ci.org/sass/ruby-sass) [![Gem Version](https://badge.fury.io/rb/sass.svg)](http://badge.fury.io/rb/sass) [![Inline docs](http://inch-ci.org/github/sass/sass.svg)](http://inch-ci.org/github/sass/sass)
2
12
 
3
13
  **Sass makes CSS fun again**. Sass is an extension of CSS,
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.6.0
1
+ 3.7.0
@@ -1 +1 @@
1
- 19 September 2018 17:19:09 UTC
1
+ 06 November 2018 23:08:31 UTC
@@ -24,7 +24,10 @@ module Sass
24
24
  end
25
25
 
26
26
  # Short-circuit all the SassScript-only productions
27
- alias_method :interpolation, :space
27
+ def interpolation(first: nil, inner: :space)
28
+ first || send(inner)
29
+ end
30
+
28
31
  alias_method :or_expr, :div
29
32
  alias_method :unary_div, :ident
30
33
  alias_method :paren, :string
@@ -664,15 +664,11 @@ module Sass::Script
664
664
  assert_type green, :Number, :green
665
665
  assert_type blue, :Number, :blue
666
666
 
667
- color_attrs = [red, green, blue].map do |c|
668
- if c.is_unit?("%")
669
- c.value * 255 / 100.0
670
- elsif c.unitless?
671
- c.value
672
- else
673
- raise ArgumentError.new("Expected #{c} to be unitless or have a unit of % but got #{c}")
674
- end
675
- end
667
+ color_attrs = [
668
+ percentage_or_unitless(red, 255, "red"),
669
+ percentage_or_unitless(green, 255, "green"),
670
+ percentage_or_unitless(blue, 255, "blue")
671
+ ]
676
672
 
677
673
  # Don't store the string representation for function-created colors, both
678
674
  # because it's not very useful and because some functions aren't supported
@@ -737,8 +733,7 @@ module Sass::Script
737
733
  unquoted_string("rgba(#{color.red}, #{color.green}, #{color.blue}, #{alpha})")
738
734
  else
739
735
  assert_type alpha, :Number, :alpha
740
- check_alpha_unit alpha, 'rgba'
741
- color.with(:alpha => alpha.value)
736
+ color.with(:alpha => percentage_or_unitless(alpha, 1, "alpha"))
742
737
  end
743
738
  when 3
744
739
  if var?(args[0]) || var?(args[1]) || var?(args[2])
@@ -840,7 +835,6 @@ module Sass::Script
840
835
  assert_type saturation, :Number, :saturation
841
836
  assert_type lightness, :Number, :lightness
842
837
  assert_type alpha, :Number, :alpha
843
- check_alpha_unit alpha, 'hsla'
844
838
 
845
839
  h = hue.value
846
840
  s = saturation.value
@@ -850,7 +844,8 @@ module Sass::Script
850
844
  # because it's not very useful and because some functions aren't supported
851
845
  # on older browsers.
852
846
  Sass::Script::Value::Color.new(
853
- :hue => h, :saturation => s, :lightness => l, :alpha => alpha.value)
847
+ :hue => h, :saturation => s, :lightness => l,
848
+ :alpha => percentage_or_unitless(alpha, 1, "alpha"))
854
849
  end
855
850
  declare :hsla, [:hue, :saturation, :lightness, :alpha]
856
851
  declare :hsla, [:hue, :saturation, :lightness]
@@ -2911,19 +2906,14 @@ WARNING
2911
2906
  color.with(attr => color.send(attr).send(op, amount.value))
2912
2907
  end
2913
2908
 
2914
- def check_alpha_unit(alpha, function)
2915
- return if alpha.unitless?
2916
-
2917
- if alpha.is_unit?("%")
2918
- Sass::Util.sass_warn(<<WARNING)
2919
- DEPRECATION WARNING: Passing a percentage as the alpha value to #{function}() will be
2920
- interpreted differently in future versions of Sass. For now, use #{alpha.value} instead.
2921
- WARNING
2909
+ def percentage_or_unitless(number, max, name)
2910
+ if number.unitless?
2911
+ value = number.value
2912
+ elsif number.is_unit?("%")
2913
+ value = max * number.value / 100.0;
2922
2914
  else
2923
- Sass::Util.sass_warn(<<WARNING)
2924
- DEPRECATION WARNING: Passing a number with units as the alpha value to #{function}() is
2925
- deprecated and will be an error in future versions of Sass. Use #{alpha.value} instead.
2926
- WARNING
2915
+ raise ArgumentError.new(
2916
+ "$#{name}: Expected #{number} to have no units or \"%\"");
2927
2917
  end
2928
2918
  end
2929
2919
  end
@@ -190,6 +190,14 @@ module Sass
190
190
  @scanner.string[pos, 1]
191
191
  end
192
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
+
193
201
  # Returns the next token without moving the lexer forward.
194
202
  #
195
203
  # @return [Token] The next token
@@ -200,6 +208,7 @@ module Sass
200
208
  # Rewinds the underlying StringScanner
201
209
  # to before the token returned by \{#peek}.
202
210
  def unpeek!
211
+ raise "[BUG] Can't unpeek before a queued token!" if @next_tok
203
212
  return unless @tok
204
213
  @scanner.pos = @tok.pos
205
214
  @line = @tok.source_range.start_pos.line
@@ -243,6 +252,30 @@ module Sass
243
252
  @scanner.string[old_pos...new_pos]
244
253
  end
245
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
+
246
279
  private
247
280
 
248
281
  def read_token
@@ -266,10 +299,14 @@ module Sass
266
299
  end
267
300
 
268
301
  def token
269
- if after_interpolation? && (interp = @interpolation_stack.pop)
270
- interp_type, interp_value = interp
302
+ if after_interpolation?
303
+ interp_type, interp_value = @interpolation_stack.pop
271
304
  if interp_type == :special_fun
272
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
273
310
  else
274
311
  raise "[BUG]: Unknown interp_type #{interp_type}" unless interp_type == :string
275
312
  return string(interp_value, true)
@@ -287,13 +324,12 @@ module Sass
287
324
 
288
325
  def _variable(rx)
289
326
  return unless scan(rx)
290
-
291
- [:const, @scanner[2]]
327
+ [:const, Sass::Util.normalize_ident_escapes(@scanner[2])]
292
328
  end
293
329
 
294
330
  def ident
295
331
  return unless scan(REGULAR_EXPRESSIONS[:ident])
296
- [@scanner[2] ? :funcall : :ident, @scanner[1]]
332
+ [@scanner[2] ? :funcall : :ident, Sass::Util.normalize_ident_escapes(@scanner[1])]
297
333
  end
298
334
 
299
335
  def string(re, open)
@@ -349,7 +385,9 @@ MESSAGE
349
385
 
350
386
  value = (@scanner[1] ? @scanner[1].to_f : @scanner[2].to_i) * (minus ? -1 : 1)
351
387
  value *= 10**@scanner[3].to_i if @scanner[3]
352
- 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))
353
391
  [:number, script_number]
354
392
  end
355
393
 
@@ -373,7 +411,7 @@ MESSAGE
373
411
  @scanner[0].length == 7 || @scanner[0].length == 9)
374
412
  return [:color, Script::Value::Color.from_hex(@scanner[0])]
375
413
  end
376
- [:ident, @scanner[0]]
414
+ [:ident, Sass::Util.normalize_ident_escapes(@scanner[0])]
377
415
  end
378
416
 
379
417
  def color
@@ -78,7 +78,7 @@ module Sass
78
78
  # Parses a SassScript expression,
79
79
  # ending it when it encounters one of the given identifier tokens.
80
80
  #
81
- # @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.
82
82
  # @return [Script::Tree::Node] The root node of the parse tree
83
83
  # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
84
84
  def parse_until(tokens)
@@ -270,7 +270,11 @@ module Sass
270
270
  interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect})
271
271
  return interp if interp
272
272
  return unless e = #{sub}
273
- 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
+
274
278
  if interp = try_op_before_interp(tok, e)
275
279
  other_interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}, interp)
276
280
  return interp unless other_interp
@@ -386,8 +390,11 @@ RUBY
386
390
 
387
391
  def try_ops_after_interp(ops, name, prev = nil)
388
392
  return unless @lexer.after_interpolation?
389
- op = try_toks(*ops)
393
+ op = peek_toks(*ops)
390
394
  return unless op
395
+ return if @stop_at && @stop_at.include?(op.type)
396
+ @lexer.next
397
+
391
398
  interp = try_op_before_interp(op, prev, :after_interp)
392
399
  return interp if interp
393
400
 
@@ -416,7 +423,7 @@ RUBY
416
423
  while (interp = try_tok(:begin_interpolation))
417
424
  wb = @lexer.whitespace?(interp)
418
425
  char_before = @lexer.char(interp.pos - 1)
419
- mid = assert_expr :expr
426
+ mid = without_stop_at {assert_expr :expr}
420
427
  assert_tok :end_interpolation
421
428
  wa = @lexer.whitespace?
422
429
  char_after = @lexer.char
@@ -497,7 +504,7 @@ RUBY
497
504
  unary :not, :ident
498
505
 
499
506
  def ident
500
- return funcall unless @lexer.peek && @lexer.peek.type == :ident
507
+ return css_min_max unless @lexer.peek && @lexer.peek.type == :ident
501
508
  return if @stop_at && @stop_at.include?(@lexer.peek.value)
502
509
 
503
510
  name = @lexer.next
@@ -514,6 +521,122 @@ RUBY
514
521
  end
515
522
  end
516
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
+
517
640
  def funcall
518
641
  tok = try_tok(:funcall)
519
642
  return raw unless tok
@@ -530,28 +653,30 @@ RUBY
530
653
  return [], nil unless try_tok(:lparen)
531
654
  end
532
655
 
533
- res = []
534
- splat = nil
535
- must_have_default = false
536
- loop do
537
- break if peek_tok(:rparen)
538
- c = assert_tok(:const)
539
- var = node(Script::Tree::Variable.new(c.value), c.source_range)
540
- if try_tok(:colon)
541
- val = assert_expr(:space)
542
- must_have_default = true
543
- elsif try_tok(:splat)
544
- splat = var
545
- break
546
- elsif must_have_default
547
- raise SyntaxError.new(
548
- "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)
549
676
  end
550
- res << [var, val]
551
- break unless try_tok(:comma)
677
+ assert_tok(:rparen)
678
+ return res, splat
552
679
  end
553
- assert_tok(:rparen)
554
- return res, splat
555
680
  end
556
681
 
557
682
  def fn_arglist
@@ -563,36 +688,38 @@ RUBY
563
688
  end
564
689
 
565
690
  def arglist(subexpr, description)
566
- args = []
567
- keywords = Sass::Util::NormalizedMap.new
568
- splat = nil
569
- while (e = send(subexpr))
570
- if @lexer.peek && @lexer.peek.type == :colon
571
- name = e
572
- @lexer.expected!("comma") unless name.is_a?(Tree::Variable)
573
- assert_tok(:colon)
574
- value = assert_expr(subexpr, description)
575
-
576
- if keywords[name.name]
577
- raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once")
578
- 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
579
705
 
580
- keywords[name.name] = value
581
- else
582
- if try_tok(:splat)
583
- return args, keywords, splat, e if splat
584
- splat, e = e, nil
585
- elsif splat
586
- raise SyntaxError.new("Only keyword arguments may follow variable arguments (...).")
587
- elsif !keywords.empty?
588
- 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
589
717
  end
590
- args << e if e
591
- end
592
718
 
593
- return args, keywords, splat unless try_tok(:comma)
719
+ return args, keywords, splat unless try_tok(:comma)
720
+ end
721
+ return args, keywords
594
722
  end
595
- return args, keywords
596
723
  end
597
724
 
598
725
  def raw
@@ -606,7 +733,7 @@ RUBY
606
733
  return square_list unless first
607
734
  str = literal_node(first.value, first.source_range)
608
735
  return str unless try_tok(:string_interpolation)
609
- mid = assert_expr :expr
736
+ mid = without_stop_at {assert_expr :expr}
610
737
  assert_tok :end_interpolation
611
738
  last = assert_expr(:special_fun)
612
739
  node(
@@ -618,54 +745,58 @@ RUBY
618
745
  start_pos = source_position
619
746
  return paren unless try_tok(:lsquare)
620
747
 
621
- space_start_pos = source_position
622
- e = interpolation(inner: :or_expr)
623
- separator = nil
624
- if e
625
- elements = [e]
626
- while (e = interpolation(inner: :or_expr))
627
- elements << e
628
- end
629
-
630
- # If there's a comma after a space-separated list, it's actually a
631
- # space-separated list nested in a comma-separated list.
632
- if try_tok(:comma)
633
- e = if elements.length == 1
634
- elements.first
635
- else
636
- node(
637
- Sass::Script::Tree::ListLiteral.new(elements, separator: :space),
638
- space_start_pos)
639
- end
748
+ without_stop_at do
749
+ space_start_pos = source_position
750
+ e = interpolation(inner: :or_expr)
751
+ separator = nil
752
+ if e
640
753
  elements = [e]
641
-
642
- while (e = space)
754
+ while (e = interpolation(inner: :or_expr))
643
755
  elements << e
644
- break unless try_tok(:comma)
645
756
  end
646
- separator = :comma
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
647
778
  else
648
- separator = :space if elements.length > 1
779
+ elements = []
649
780
  end
650
- else
651
- elements = []
652
- end
653
781
 
654
- assert_tok(:rsquare)
655
- end_pos = source_position
782
+ assert_tok(:rsquare)
783
+ end_pos = source_position
656
784
 
657
- node(Sass::Script::Tree::ListLiteral.new(elements, separator: separator, bracketed: true),
658
- start_pos, end_pos)
785
+ node(Sass::Script::Tree::ListLiteral.new(elements, separator: separator, bracketed: true),
786
+ start_pos, end_pos)
787
+ end
659
788
  end
660
789
 
661
790
  def paren
662
791
  return variable unless try_tok(:lparen)
663
- start_pos = source_position
664
- e = map
665
- e.force_division! if e
666
- end_pos = source_position
667
- assert_tok(:rparen)
668
- e || node(Sass::Script::Tree::ListLiteral.new([]), start_pos, end_pos)
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
669
800
  end
670
801
 
671
802
  def variable
@@ -682,7 +813,7 @@ RUBY
682
813
  return str unless try_tok(:string_interpolation)
683
814
  mid = assert_expr :expr
684
815
  assert_tok :end_interpolation
685
- last = assert_expr(:string)
816
+ last = without_stop_at {assert_expr(:string)}
686
817
  node(Tree::StringInterpolation.new(str, mid, last), first.source_range.start_pos)
687
818
  end
688
819
 
@@ -740,7 +871,12 @@ RUBY
740
871
  def peek_tok(name)
741
872
  # Avoids an array allocation caused by argument globbing in the try_toks method.
742
873
  peeked = @lexer.peek
743
- peeked && name == peeked.type
874
+ peeked && name == peeked.type && peeked
875
+ end
876
+
877
+ def peek_toks(*names)
878
+ peeked = @lexer.peek
879
+ peeked && names.include?(peeked.type) && peeked
744
880
  end
745
881
 
746
882
  def try_tok(name)
@@ -748,8 +884,7 @@ RUBY
748
884
  end
749
885
 
750
886
  def try_toks(*names)
751
- peeked = @lexer.peek
752
- peeked && names.include?(peeked.type) && @lexer.next
887
+ peek_toks(*names) && @lexer.next
753
888
  end
754
889
 
755
890
  def assert_done
@@ -763,6 +898,14 @@ RUBY
763
898
  end
764
899
  end
765
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
+
766
909
  # @overload node(value, source_range)
767
910
  # @param value [Sass::Script::Value::Base]
768
911
  # @param source_range [Sass::Source::Range]
@@ -795,6 +938,29 @@ RUBY
795
938
  node
796
939
  end
797
940
 
941
+ # Converts an array of strings and expressions to a string interoplation
942
+ # object.
943
+ #
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
962
+ end
963
+
798
964
  # Checks a script node for any immediately-deprecated interpolations, and
799
965
  # emits warnings for them.
800
966
  #