sass 3.5.2 → 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.
- checksums.yaml +4 -4
- data/CODE_OF_CONDUCT.md +1 -1
- data/CONTRIBUTING.md +3 -3
- data/README.md +17 -9
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/extra/sass-spec-ref.sh +9 -1
- data/lib/sass/engine.rb +1 -9
- data/lib/sass/exec/base.rb +0 -2
- data/lib/sass/exec/sass_scss.rb +1 -5
- data/lib/sass/importers/filesystem.rb +4 -2
- data/lib/sass/logger/base.rb +11 -0
- data/lib/sass/script/css_parser.rb +4 -1
- data/lib/sass/script/functions.rb +76 -41
- data/lib/sass/script/lexer.rb +62 -19
- data/lib/sass/script/parser.rb +260 -93
- data/lib/sass/script/tree/funcall.rb +0 -4
- data/lib/sass/script/tree/interpolation.rb +0 -3
- data/lib/sass/script/tree/operation.rb +1 -1
- data/lib/sass/script/value/color.rb +3 -2
- data/lib/sass/script/value/helpers.rb +8 -2
- data/lib/sass/script/value/number.rb +2 -1
- data/lib/sass/scss/css_parser.rb +6 -1
- data/lib/sass/scss/parser.rb +48 -18
- data/lib/sass/scss/rx.rb +1 -1
- data/lib/sass/scss/static_parser.rb +15 -18
- data/lib/sass/selector/comma_sequence.rb +2 -1
- data/lib/sass/selector/pseudo.rb +1 -1
- data/lib/sass/selector/sequence.rb +0 -4
- data/lib/sass/source/map.rb +0 -4
- data/lib/sass/tree/rule_node.rb +3 -6
- data/lib/sass/tree/visitors/perform.rb +2 -6
- data/lib/sass/tree/visitors/to_css.rb +4 -11
- data/lib/sass/util.rb +60 -20
- data/lib/sass/version.rb +0 -2
- metadata +38 -162
- data/Rakefile +0 -338
- data/lib/test.css +0 -4
- data/lib/test.css.map +0 -7
- data/test/sass-spec.yml +0 -3
- data/test/sass/cache_test.rb +0 -130
- data/test/sass/callbacks_test.rb +0 -60
- data/test/sass/compiler_test.rb +0 -225
- data/test/sass/conversion_test.rb +0 -2138
- data/test/sass/css2sass_test.rb +0 -523
- data/test/sass/css_variable_test.rb +0 -237
- data/test/sass/data/hsl-rgb.txt +0 -319
- data/test/sass/encoding_test.rb +0 -188
- data/test/sass/engine_test.rb +0 -3499
- data/test/sass/exec_test.rb +0 -95
- data/test/sass/extend_test.rb +0 -1678
- data/test/sass/fixtures/test_staleness_check_across_importers.css +0 -1
- data/test/sass/fixtures/test_staleness_check_across_importers.scss +0 -1
- data/test/sass/functions_test.rb +0 -2021
- data/test/sass/importer_test.rb +0 -420
- data/test/sass/logger_test.rb +0 -57
- data/test/sass/mock_importer.rb +0 -49
- data/test/sass/more_results/more1.css +0 -9
- data/test/sass/more_results/more1_with_line_comments.css +0 -26
- data/test/sass/more_results/more_import.css +0 -29
- data/test/sass/more_templates/_more_partial.sass +0 -2
- data/test/sass/more_templates/more1.sass +0 -23
- data/test/sass/more_templates/more_import.sass +0 -11
- data/test/sass/plugin_test.rb +0 -552
- data/test/sass/results/alt.css +0 -4
- data/test/sass/results/basic.css +0 -9
- data/test/sass/results/cached_import_option.css +0 -3
- data/test/sass/results/compact.css +0 -5
- data/test/sass/results/complex.css +0 -86
- data/test/sass/results/compressed.css +0 -1
- data/test/sass/results/expanded.css +0 -19
- data/test/sass/results/filename_fn.css +0 -3
- data/test/sass/results/if.css +0 -3
- data/test/sass/results/import.css +0 -31
- data/test/sass/results/import_charset.css +0 -5
- data/test/sass/results/import_charset_ibm866.css +0 -5
- data/test/sass/results/import_content.css +0 -1
- data/test/sass/results/line_numbers.css +0 -49
- data/test/sass/results/mixins.css +0 -95
- data/test/sass/results/multiline.css +0 -24
- data/test/sass/results/nested.css +0 -22
- data/test/sass/results/options.css +0 -1
- data/test/sass/results/parent_ref.css +0 -13
- data/test/sass/results/script.css +0 -16
- data/test/sass/results/scss_import.css +0 -31
- data/test/sass/results/scss_importee.css +0 -2
- data/test/sass/results/subdir/nested_subdir/nested_subdir.css +0 -1
- data/test/sass/results/subdir/subdir.css +0 -3
- data/test/sass/results/units.css +0 -11
- data/test/sass/results/warn.css +0 -0
- data/test/sass/results/warn_imported.css +0 -0
- data/test/sass/script_conversion_test.rb +0 -365
- data/test/sass/script_test.rb +0 -1429
- data/test/sass/scss/css_test.rb +0 -1266
- data/test/sass/scss/rx_test.rb +0 -159
- data/test/sass/scss/scss_test.rb +0 -4238
- data/test/sass/scss/test_helper.rb +0 -37
- data/test/sass/source_map_test.rb +0 -1052
- data/test/sass/superselector_test.rb +0 -209
- data/test/sass/templates/_cached_import_option_partial.scss +0 -1
- data/test/sass/templates/_double_import_loop2.sass +0 -1
- data/test/sass/templates/_filename_fn_import.scss +0 -11
- data/test/sass/templates/_imported_charset_ibm866.sass +0 -4
- data/test/sass/templates/_imported_charset_utf8.sass +0 -4
- data/test/sass/templates/_imported_content.sass +0 -3
- data/test/sass/templates/_partial.sass +0 -2
- data/test/sass/templates/_same_name_different_partiality.scss +0 -1
- data/test/sass/templates/alt.sass +0 -16
- data/test/sass/templates/basic.sass +0 -23
- data/test/sass/templates/bork1.sass +0 -2
- data/test/sass/templates/bork2.sass +0 -2
- data/test/sass/templates/bork3.sass +0 -2
- data/test/sass/templates/bork4.sass +0 -2
- data/test/sass/templates/bork5.sass +0 -3
- data/test/sass/templates/cached_import_option.scss +0 -3
- data/test/sass/templates/compact.sass +0 -17
- data/test/sass/templates/complex.sass +0 -305
- data/test/sass/templates/compressed.sass +0 -15
- data/test/sass/templates/double_import_loop1.sass +0 -1
- data/test/sass/templates/expanded.sass +0 -17
- data/test/sass/templates/filename_fn.scss +0 -18
- data/test/sass/templates/if.sass +0 -11
- data/test/sass/templates/import.sass +0 -12
- data/test/sass/templates/import_charset.sass +0 -9
- data/test/sass/templates/import_charset_ibm866.sass +0 -11
- data/test/sass/templates/import_content.sass +0 -4
- data/test/sass/templates/importee.less +0 -2
- data/test/sass/templates/importee.sass +0 -19
- data/test/sass/templates/line_numbers.sass +0 -13
- data/test/sass/templates/mixin_bork.sass +0 -5
- data/test/sass/templates/mixins.sass +0 -76
- data/test/sass/templates/multiline.sass +0 -20
- data/test/sass/templates/nested.sass +0 -25
- data/test/sass/templates/nested_bork1.sass +0 -2
- data/test/sass/templates/nested_bork2.sass +0 -2
- data/test/sass/templates/nested_bork3.sass +0 -2
- data/test/sass/templates/nested_bork4.sass +0 -2
- data/test/sass/templates/nested_import.sass +0 -2
- data/test/sass/templates/nested_mixin_bork.sass +0 -6
- data/test/sass/templates/options.sass +0 -2
- data/test/sass/templates/parent_ref.sass +0 -25
- data/test/sass/templates/same_name_different_ext.sass +0 -2
- data/test/sass/templates/same_name_different_ext.scss +0 -1
- data/test/sass/templates/same_name_different_partiality.scss +0 -1
- data/test/sass/templates/script.sass +0 -101
- data/test/sass/templates/scss_import.scss +0 -12
- data/test/sass/templates/scss_importee.scss +0 -1
- data/test/sass/templates/single_import_loop.sass +0 -1
- data/test/sass/templates/subdir/import_up1.scss +0 -1
- data/test/sass/templates/subdir/import_up2.scss +0 -1
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +0 -2
- data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +0 -3
- data/test/sass/templates/subdir/subdir.sass +0 -6
- data/test/sass/templates/units.sass +0 -11
- data/test/sass/templates/warn.sass +0 -3
- data/test/sass/templates/warn_imported.sass +0 -4
- data/test/sass/test_helper.rb +0 -8
- data/test/sass/util/multibyte_string_scanner_test.rb +0 -152
- data/test/sass/util/normalized_map_test.rb +0 -50
- data/test/sass/util/subset_map_test.rb +0 -90
- data/test/sass/util_test.rb +0 -403
- data/test/sass/value_helpers_test.rb +0 -178
- data/test/test_helper.rb +0 -149
data/lib/sass/script/lexer.rb
CHANGED
|
@@ -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?
|
|
270
|
-
interp_type, interp_value =
|
|
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
|
-
|
|
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
|
|
|
@@ -368,24 +406,20 @@ MESSAGE
|
|
|
368
406
|
# IDs in properties are used in the Basic User Interface Module
|
|
369
407
|
# (http://www.w3.org/TR/css3-ui/).
|
|
370
408
|
return unless scan(REGULAR_EXPRESSIONS[:id])
|
|
371
|
-
if @scanner[0] =~ /^\#[0-9a-fA-F]+$/
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
filename = @options[:filename]
|
|
376
|
-
Sass::Util.sass_warn <<MESSAGE
|
|
377
|
-
DEPRECATION WARNING on line #{line}, column #{offset}#{" of #{filename}" if filename}:
|
|
378
|
-
The value "#{@scanner[0]}" is currently parsed as a string, but it will be parsed as a color in
|
|
379
|
-
future versions of Sass. Use "unquote('#{@scanner[0]}')" to continue parsing it as a string.
|
|
380
|
-
MESSAGE
|
|
381
|
-
end
|
|
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)
|
|
412
|
+
return [:color, Script::Value::Color.from_hex(@scanner[0])]
|
|
382
413
|
end
|
|
383
|
-
[:ident, @scanner[0]]
|
|
414
|
+
[:ident, Sass::Util.normalize_ident_escapes(@scanner[0])]
|
|
384
415
|
end
|
|
385
416
|
|
|
386
417
|
def color
|
|
387
418
|
return unless @scanner.match?(REGULAR_EXPRESSIONS[:color])
|
|
388
|
-
|
|
419
|
+
unless @scanner[0].length == 4 || @scanner[0].length == 5 ||
|
|
420
|
+
@scanner[0].length == 7 || @scanner[0].length == 9
|
|
421
|
+
return
|
|
422
|
+
end
|
|
389
423
|
script_color = Script::Value::Color.from_hex(scan(REGULAR_EXPRESSIONS[:color]))
|
|
390
424
|
[:color, script_color]
|
|
391
425
|
end
|
|
@@ -393,6 +427,15 @@ MESSAGE
|
|
|
393
427
|
def selector
|
|
394
428
|
start_pos = source_position
|
|
395
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
|
+
|
|
396
439
|
script_selector = Script::Tree::Selector.new
|
|
397
440
|
script_selector.source_range = range(start_pos)
|
|
398
441
|
[:selector, script_selector]
|
data/lib/sass/script/parser.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
@@ -323,6 +327,7 @@ RUBY
|
|
|
323
327
|
pair = map_pair
|
|
324
328
|
return map unless pair
|
|
325
329
|
map.pairs << pair
|
|
330
|
+
map.source_range.end_pos = map.pairs.last.last.source_range.end_pos
|
|
326
331
|
end
|
|
327
332
|
map
|
|
328
333
|
end
|
|
@@ -385,8 +390,11 @@ RUBY
|
|
|
385
390
|
|
|
386
391
|
def try_ops_after_interp(ops, name, prev = nil)
|
|
387
392
|
return unless @lexer.after_interpolation?
|
|
388
|
-
op =
|
|
393
|
+
op = peek_toks(*ops)
|
|
389
394
|
return unless op
|
|
395
|
+
return if @stop_at && @stop_at.include?(op.type)
|
|
396
|
+
@lexer.next
|
|
397
|
+
|
|
390
398
|
interp = try_op_before_interp(op, prev, :after_interp)
|
|
391
399
|
return interp if interp
|
|
392
400
|
|
|
@@ -415,7 +423,7 @@ RUBY
|
|
|
415
423
|
while (interp = try_tok(:begin_interpolation))
|
|
416
424
|
wb = @lexer.whitespace?(interp)
|
|
417
425
|
char_before = @lexer.char(interp.pos - 1)
|
|
418
|
-
mid = assert_expr :expr
|
|
426
|
+
mid = without_stop_at {assert_expr :expr}
|
|
419
427
|
assert_tok :end_interpolation
|
|
420
428
|
wa = @lexer.whitespace?
|
|
421
429
|
char_after = @lexer.char
|
|
@@ -496,7 +504,7 @@ RUBY
|
|
|
496
504
|
unary :not, :ident
|
|
497
505
|
|
|
498
506
|
def ident
|
|
499
|
-
return
|
|
507
|
+
return css_min_max unless @lexer.peek && @lexer.peek.type == :ident
|
|
500
508
|
return if @stop_at && @stop_at.include?(@lexer.peek.value)
|
|
501
509
|
|
|
502
510
|
name = @lexer.next
|
|
@@ -513,6 +521,122 @@ RUBY
|
|
|
513
521
|
end
|
|
514
522
|
end
|
|
515
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
|
+
|
|
516
640
|
def funcall
|
|
517
641
|
tok = try_tok(:funcall)
|
|
518
642
|
return raw unless tok
|
|
@@ -529,28 +653,30 @@ RUBY
|
|
|
529
653
|
return [], nil unless try_tok(:lparen)
|
|
530
654
|
end
|
|
531
655
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
splat
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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)
|
|
548
676
|
end
|
|
549
|
-
|
|
550
|
-
|
|
677
|
+
assert_tok(:rparen)
|
|
678
|
+
return res, splat
|
|
551
679
|
end
|
|
552
|
-
assert_tok(:rparen)
|
|
553
|
-
return res, splat
|
|
554
680
|
end
|
|
555
681
|
|
|
556
682
|
def fn_arglist
|
|
@@ -562,36 +688,38 @@ RUBY
|
|
|
562
688
|
end
|
|
563
689
|
|
|
564
690
|
def arglist(subexpr, description)
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
|
578
705
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
|
588
717
|
end
|
|
589
|
-
args << e if e
|
|
590
|
-
end
|
|
591
718
|
|
|
592
|
-
|
|
719
|
+
return args, keywords, splat unless try_tok(:comma)
|
|
720
|
+
end
|
|
721
|
+
return args, keywords
|
|
593
722
|
end
|
|
594
|
-
return args, keywords
|
|
595
723
|
end
|
|
596
724
|
|
|
597
725
|
def raw
|
|
@@ -605,7 +733,7 @@ RUBY
|
|
|
605
733
|
return square_list unless first
|
|
606
734
|
str = literal_node(first.value, first.source_range)
|
|
607
735
|
return str unless try_tok(:string_interpolation)
|
|
608
|
-
mid = assert_expr :expr
|
|
736
|
+
mid = without_stop_at {assert_expr :expr}
|
|
609
737
|
assert_tok :end_interpolation
|
|
610
738
|
last = assert_expr(:special_fun)
|
|
611
739
|
node(
|
|
@@ -617,54 +745,58 @@ RUBY
|
|
|
617
745
|
start_pos = source_position
|
|
618
746
|
return paren unless try_tok(:lsquare)
|
|
619
747
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
while (e = interpolation(inner: :or_expr))
|
|
626
|
-
elements << e
|
|
627
|
-
end
|
|
628
|
-
|
|
629
|
-
# If there's a comma after a space-separated list, it's actually a
|
|
630
|
-
# space-separated list nested in a comma-separated list.
|
|
631
|
-
if try_tok(:comma)
|
|
632
|
-
e = if elements.length == 1
|
|
633
|
-
elements.first
|
|
634
|
-
else
|
|
635
|
-
node(
|
|
636
|
-
Sass::Script::Tree::ListLiteral.new(elements, separator: :space),
|
|
637
|
-
space_start_pos)
|
|
638
|
-
end
|
|
748
|
+
without_stop_at do
|
|
749
|
+
space_start_pos = source_position
|
|
750
|
+
e = interpolation(inner: :or_expr)
|
|
751
|
+
separator = nil
|
|
752
|
+
if e
|
|
639
753
|
elements = [e]
|
|
640
|
-
|
|
641
|
-
while (e = space)
|
|
754
|
+
while (e = interpolation(inner: :or_expr))
|
|
642
755
|
elements << e
|
|
643
|
-
break unless try_tok(:comma)
|
|
644
756
|
end
|
|
645
|
-
|
|
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
|
|
646
778
|
else
|
|
647
|
-
|
|
779
|
+
elements = []
|
|
648
780
|
end
|
|
649
|
-
else
|
|
650
|
-
elements = []
|
|
651
|
-
end
|
|
652
781
|
|
|
653
|
-
|
|
654
|
-
|
|
782
|
+
assert_tok(:rsquare)
|
|
783
|
+
end_pos = source_position
|
|
655
784
|
|
|
656
|
-
|
|
657
|
-
|
|
785
|
+
node(Sass::Script::Tree::ListLiteral.new(elements, separator: separator, bracketed: true),
|
|
786
|
+
start_pos, end_pos)
|
|
787
|
+
end
|
|
658
788
|
end
|
|
659
789
|
|
|
660
790
|
def paren
|
|
661
791
|
return variable unless try_tok(:lparen)
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
|
668
800
|
end
|
|
669
801
|
|
|
670
802
|
def variable
|
|
@@ -681,7 +813,7 @@ RUBY
|
|
|
681
813
|
return str unless try_tok(:string_interpolation)
|
|
682
814
|
mid = assert_expr :expr
|
|
683
815
|
assert_tok :end_interpolation
|
|
684
|
-
last = assert_expr(:string)
|
|
816
|
+
last = without_stop_at {assert_expr(:string)}
|
|
685
817
|
node(Tree::StringInterpolation.new(str, mid, last), first.source_range.start_pos)
|
|
686
818
|
end
|
|
687
819
|
|
|
@@ -739,7 +871,12 @@ RUBY
|
|
|
739
871
|
def peek_tok(name)
|
|
740
872
|
# Avoids an array allocation caused by argument globbing in the try_toks method.
|
|
741
873
|
peeked = @lexer.peek
|
|
742
|
-
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
|
|
743
880
|
end
|
|
744
881
|
|
|
745
882
|
def try_tok(name)
|
|
@@ -747,8 +884,7 @@ RUBY
|
|
|
747
884
|
end
|
|
748
885
|
|
|
749
886
|
def try_toks(*names)
|
|
750
|
-
|
|
751
|
-
peeked && names.include?(peeked.type) && @lexer.next
|
|
887
|
+
peek_toks(*names) && @lexer.next
|
|
752
888
|
end
|
|
753
889
|
|
|
754
890
|
def assert_done
|
|
@@ -762,6 +898,14 @@ RUBY
|
|
|
762
898
|
end
|
|
763
899
|
end
|
|
764
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
|
+
|
|
765
909
|
# @overload node(value, source_range)
|
|
766
910
|
# @param value [Sass::Script::Value::Base]
|
|
767
911
|
# @param source_range [Sass::Source::Range]
|
|
@@ -794,6 +938,29 @@ RUBY
|
|
|
794
938
|
node
|
|
795
939
|
end
|
|
796
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
|
+
|
|
797
964
|
# Checks a script node for any immediately-deprecated interpolations, and
|
|
798
965
|
# emits warnings for them.
|
|
799
966
|
#
|