sass 3.2.7 → 3.3.0.rc.1
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.
- data/MIT-LICENSE +2 -2
- data/README.md +14 -2
- data/Rakefile +25 -1
- data/VERSION +1 -1
- data/VERSION_DATE +1 -1
- data/VERSION_NAME +1 -1
- data/lib/sass/cache_stores/base.rb +4 -2
- data/lib/sass/cache_stores/chain.rb +2 -1
- data/lib/sass/cache_stores/filesystem.rb +2 -6
- data/lib/sass/cache_stores/memory.rb +1 -1
- data/lib/sass/cache_stores/null.rb +2 -2
- data/lib/sass/callbacks.rb +1 -0
- data/lib/sass/css.rb +10 -10
- data/lib/sass/engine.rb +403 -150
- data/lib/sass/environment.rb +136 -57
- data/lib/sass/error.rb +7 -7
- data/lib/sass/exec.rb +123 -39
- data/lib/sass/features.rb +41 -0
- data/lib/sass/importers/base.rb +33 -2
- data/lib/sass/importers/deprecated_path.rb +45 -0
- data/lib/sass/importers/filesystem.rb +25 -14
- data/lib/sass/importers.rb +1 -0
- data/lib/sass/logger/base.rb +3 -3
- data/lib/sass/logger/log_level.rb +4 -6
- data/lib/sass/media.rb +19 -19
- data/lib/sass/plugin/compiler.rb +141 -101
- data/lib/sass/plugin/configuration.rb +18 -22
- data/lib/sass/plugin/merb.rb +1 -1
- data/lib/sass/plugin/staleness_checker.rb +24 -8
- data/lib/sass/plugin.rb +4 -2
- data/lib/sass/repl.rb +3 -3
- data/lib/sass/script/css_lexer.rb +9 -4
- data/lib/sass/script/css_parser.rb +6 -2
- data/lib/sass/script/functions.rb +1343 -590
- data/lib/sass/script/lexer.rb +84 -52
- data/lib/sass/script/parser.rb +217 -97
- data/lib/sass/script/tree/funcall.rb +290 -0
- data/lib/sass/script/{interpolation.rb → tree/interpolation.rb} +34 -13
- data/lib/sass/script/tree/list_literal.rb +80 -0
- data/lib/sass/script/tree/literal.rb +47 -0
- data/lib/sass/script/tree/map_literal.rb +64 -0
- data/lib/sass/script/{node.rb → tree/node.rb} +22 -12
- data/lib/sass/script/{operation.rb → tree/operation.rb} +17 -25
- data/lib/sass/script/tree/selector.rb +30 -0
- data/lib/sass/script/{string_interpolation.rb → tree/string_interpolation.rb} +5 -4
- data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +14 -9
- data/lib/sass/script/tree/variable.rb +57 -0
- data/lib/sass/script/tree.rb +16 -0
- data/lib/sass/script/{arg_list.rb → value/arg_list.rb} +4 -24
- data/lib/sass/script/value/base.rb +248 -0
- data/lib/sass/script/value/bool.rb +36 -0
- data/lib/sass/script/{color.rb → value/color.rb} +239 -195
- data/lib/sass/script/value/helpers.rb +155 -0
- data/lib/sass/script/value/list.rb +119 -0
- data/lib/sass/script/value/map.rb +70 -0
- data/lib/sass/script/value/null.rb +45 -0
- data/lib/sass/script/{number.rb → value/number.rb} +91 -65
- data/lib/sass/script/{string.rb → value/string.rb} +9 -11
- data/lib/sass/script/value.rb +11 -0
- data/lib/sass/script.rb +35 -8
- data/lib/sass/scss/css_parser.rb +2 -1
- data/lib/sass/scss/parser.rb +338 -170
- data/lib/sass/scss/rx.rb +5 -6
- data/lib/sass/scss/script_lexer.rb +1 -0
- data/lib/sass/scss/script_parser.rb +1 -0
- data/lib/sass/scss/static_parser.rb +23 -6
- data/lib/sass/selector/abstract_sequence.rb +2 -2
- data/lib/sass/selector/comma_sequence.rb +21 -16
- data/lib/sass/selector/sequence.rb +60 -34
- data/lib/sass/selector/simple.rb +11 -12
- data/lib/sass/selector/simple_sequence.rb +55 -33
- data/lib/sass/selector.rb +52 -48
- data/lib/sass/source/map.rb +211 -0
- data/lib/sass/source/position.rb +39 -0
- data/lib/sass/source/range.rb +41 -0
- data/lib/sass/stack.rb +120 -0
- data/lib/sass/supports.rb +12 -13
- data/lib/sass/tree/at_root_node.rb +82 -0
- data/lib/sass/tree/comment_node.rb +3 -3
- data/lib/sass/tree/css_import_node.rb +11 -11
- data/lib/sass/tree/debug_node.rb +2 -2
- data/lib/sass/tree/directive_node.rb +13 -2
- data/lib/sass/tree/each_node.rb +8 -8
- data/lib/sass/tree/extend_node.rb +13 -6
- data/lib/sass/tree/for_node.rb +4 -4
- data/lib/sass/tree/function_node.rb +5 -4
- data/lib/sass/tree/if_node.rb +1 -1
- data/lib/sass/tree/import_node.rb +4 -5
- data/lib/sass/tree/media_node.rb +4 -14
- data/lib/sass/tree/mixin_def_node.rb +4 -4
- data/lib/sass/tree/mixin_node.rb +21 -8
- data/lib/sass/tree/node.rb +29 -12
- data/lib/sass/tree/prop_node.rb +38 -18
- data/lib/sass/tree/return_node.rb +3 -2
- data/lib/sass/tree/root_node.rb +19 -3
- data/lib/sass/tree/rule_node.rb +25 -17
- data/lib/sass/tree/supports_node.rb +0 -13
- data/lib/sass/tree/trace_node.rb +2 -1
- data/lib/sass/tree/variable_node.rb +9 -3
- data/lib/sass/tree/visitors/base.rb +6 -6
- data/lib/sass/tree/visitors/check_nesting.rb +12 -9
- data/lib/sass/tree/visitors/convert.rb +63 -38
- data/lib/sass/tree/visitors/cssize.rb +63 -23
- data/lib/sass/tree/visitors/deep_copy.rb +6 -5
- data/lib/sass/tree/visitors/extend.rb +7 -7
- data/lib/sass/tree/visitors/perform.rb +256 -151
- data/lib/sass/tree/visitors/set_options.rb +6 -6
- data/lib/sass/tree/visitors/to_css.rb +231 -81
- data/lib/sass/tree/warn_node.rb +2 -2
- data/lib/sass/tree/while_node.rb +2 -2
- data/lib/sass/util/multibyte_string_scanner.rb +2 -0
- data/lib/sass/util/normalized_map.rb +65 -0
- data/lib/sass/util/ordered_hash.rb +188 -0
- data/lib/sass/util/subset_map.rb +3 -2
- data/lib/sass/util/test.rb +9 -0
- data/lib/sass/util.rb +220 -34
- data/lib/sass/version.rb +9 -9
- data/lib/sass.rb +14 -7
- data/test/sass/compiler_test.rb +213 -0
- data/test/sass/conversion_test.rb +235 -9
- data/test/sass/engine_test.rb +230 -60
- data/test/sass/exec_test.rb +86 -0
- data/test/sass/extend_test.rb +215 -147
- data/test/sass/functions_test.rb +584 -99
- data/test/sass/importer_test.rb +165 -17
- data/test/sass/plugin_test.rb +19 -13
- data/test/sass/script_conversion_test.rb +40 -0
- data/test/sass/script_test.rb +231 -21
- data/test/sass/scss/css_test.rb +14 -5
- data/test/sass/scss/scss_test.rb +1266 -66
- data/test/sass/source_map_test.rb +879 -0
- data/test/sass/templates/bork5.sass +3 -0
- data/test/sass/util/normalized_map_test.rb +30 -0
- data/test/sass/util_test.rb +90 -0
- data/test/sass/value_helpers_test.rb +181 -0
- data/test/test_helper.rb +7 -2
- metadata +316 -291
- data/lib/sass/script/bool.rb +0 -18
- data/lib/sass/script/funcall.rb +0 -231
- data/lib/sass/script/list.rb +0 -84
- data/lib/sass/script/literal.rb +0 -239
- data/lib/sass/script/null.rb +0 -34
- data/lib/sass/script/variable.rb +0 -58
- data/test/Gemfile +0 -3
- data/vendor/listen/CHANGELOG.md +0 -221
- data/vendor/listen/CONTRIBUTING.md +0 -38
- data/vendor/listen/Gemfile +0 -30
- data/vendor/listen/Guardfile +0 -8
- data/vendor/listen/LICENSE +0 -20
- data/vendor/listen/README.md +0 -315
- data/vendor/listen/Rakefile +0 -47
- data/vendor/listen/Vagrantfile +0 -96
- data/vendor/listen/lib/listen/adapter.rb +0 -214
- data/vendor/listen/lib/listen/adapters/bsd.rb +0 -112
- data/vendor/listen/lib/listen/adapters/darwin.rb +0 -85
- data/vendor/listen/lib/listen/adapters/linux.rb +0 -113
- data/vendor/listen/lib/listen/adapters/polling.rb +0 -67
- data/vendor/listen/lib/listen/adapters/windows.rb +0 -87
- data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
- data/vendor/listen/lib/listen/directory_record.rb +0 -371
- data/vendor/listen/lib/listen/listener.rb +0 -225
- data/vendor/listen/lib/listen/multi_listener.rb +0 -143
- data/vendor/listen/lib/listen/turnstile.rb +0 -28
- data/vendor/listen/lib/listen/version.rb +0 -3
- data/vendor/listen/lib/listen.rb +0 -40
- data/vendor/listen/listen.gemspec +0 -22
- data/vendor/listen/spec/listen/adapter_spec.rb +0 -183
- data/vendor/listen/spec/listen/adapters/bsd_spec.rb +0 -36
- data/vendor/listen/spec/listen/adapters/darwin_spec.rb +0 -37
- data/vendor/listen/spec/listen/adapters/linux_spec.rb +0 -47
- data/vendor/listen/spec/listen/adapters/polling_spec.rb +0 -68
- data/vendor/listen/spec/listen/adapters/windows_spec.rb +0 -30
- data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
- data/vendor/listen/spec/listen/directory_record_spec.rb +0 -1225
- data/vendor/listen/spec/listen/listener_spec.rb +0 -169
- data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
- data/vendor/listen/spec/listen/turnstile_spec.rb +0 -56
- data/vendor/listen/spec/listen_spec.rb +0 -73
- data/vendor/listen/spec/spec_helper.rb +0 -21
- data/vendor/listen/spec/support/adapter_helper.rb +0 -629
- data/vendor/listen/spec/support/directory_record_helper.rb +0 -55
- data/vendor/listen/spec/support/fixtures_helper.rb +0 -29
- data/vendor/listen/spec/support/listeners_helper.rb +0 -156
- data/vendor/listen/spec/support/platform_helper.rb +0 -15
data/lib/sass/engine.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
require 'set'
|
|
2
2
|
require 'digest/sha1'
|
|
3
3
|
require 'sass/cache_stores'
|
|
4
|
+
require 'sass/source/position'
|
|
5
|
+
require 'sass/source/range'
|
|
6
|
+
require 'sass/source/map'
|
|
4
7
|
require 'sass/tree/node'
|
|
5
8
|
require 'sass/tree/root_node'
|
|
6
9
|
require 'sass/tree/rule_node'
|
|
@@ -26,6 +29,7 @@ require 'sass/tree/debug_node'
|
|
|
26
29
|
require 'sass/tree/warn_node'
|
|
27
30
|
require 'sass/tree/import_node'
|
|
28
31
|
require 'sass/tree/charset_node'
|
|
32
|
+
require 'sass/tree/at_root_node'
|
|
29
33
|
require 'sass/tree/visitors/base'
|
|
30
34
|
require 'sass/tree/visitors/perform'
|
|
31
35
|
require 'sass/tree/visitors/cssize'
|
|
@@ -39,6 +43,7 @@ require 'sass/selector'
|
|
|
39
43
|
require 'sass/environment'
|
|
40
44
|
require 'sass/script'
|
|
41
45
|
require 'sass/scss'
|
|
46
|
+
require 'sass/stack'
|
|
42
47
|
require 'sass/error'
|
|
43
48
|
require 'sass/importers'
|
|
44
49
|
require 'sass/shared'
|
|
@@ -52,12 +57,12 @@ module Sass
|
|
|
52
57
|
# `name`: `String`
|
|
53
58
|
# : The name of the mixin/function.
|
|
54
59
|
#
|
|
55
|
-
# `args`: `Array<(Script::Node, Script::Node)>`
|
|
60
|
+
# `args`: `Array<(Script::Tree::Node, Script::Tree::Node)>`
|
|
56
61
|
# : The arguments for the mixin/function.
|
|
57
62
|
# Each element is a tuple containing the variable node of the argument
|
|
58
63
|
# and the parse tree for the default value of the argument.
|
|
59
64
|
#
|
|
60
|
-
# `splat`: `Script::Node?`
|
|
65
|
+
# `splat`: `Script::Tree::Node?`
|
|
61
66
|
# : The variable node of the splat argument for this callable, or null.
|
|
62
67
|
#
|
|
63
68
|
# `environment`: {Sass::Environment}
|
|
@@ -188,6 +193,16 @@ module Sass
|
|
|
188
193
|
options[:filesystem_importer].new(p.to_s)
|
|
189
194
|
end
|
|
190
195
|
|
|
196
|
+
# Remove any deprecated importers if the location is imported explicitly
|
|
197
|
+
options[:load_paths].reject! do |importer|
|
|
198
|
+
importer.is_a?(Sass::Importers::DeprecatedPath) &&
|
|
199
|
+
options[:load_paths].find do |other_importer|
|
|
200
|
+
other_importer.is_a?(Sass::Importers::Filesystem) &&
|
|
201
|
+
other_importer != importer &&
|
|
202
|
+
other_importer.root == importer.root
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
191
206
|
# Backwards compatibility
|
|
192
207
|
options[:property_syntax] ||= options[:attribute_syntax]
|
|
193
208
|
case options[:property_syntax]
|
|
@@ -246,7 +261,7 @@ module Sass
|
|
|
246
261
|
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
|
247
262
|
# @see {Sass::Engine.for_file}
|
|
248
263
|
# @see {Sass::Plugin}
|
|
249
|
-
def initialize(template, options={})
|
|
264
|
+
def initialize(template, options = {})
|
|
250
265
|
@options = self.class.normalize_options(options)
|
|
251
266
|
@template = template
|
|
252
267
|
end
|
|
@@ -259,9 +274,27 @@ module Sass
|
|
|
259
274
|
# cannot be converted to UTF-8
|
|
260
275
|
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
|
261
276
|
def render
|
|
262
|
-
return
|
|
263
|
-
Sass::Util.silence_sass_warnings {
|
|
277
|
+
return encode_and_set_charset(_to_tree.render) unless @options[:quiet]
|
|
278
|
+
Sass::Util.silence_sass_warnings {encode_and_set_charset(_to_tree.render)}
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Render the template to CSS and return the source map.
|
|
282
|
+
#
|
|
283
|
+
# @param sourcemap_uri [String] The sourcemap URI to use in the
|
|
284
|
+
# `@sourceMappingURL` comment. If this is relative, it should be relative
|
|
285
|
+
# to the location of the CSS file.
|
|
286
|
+
# @return [(String, Sass::Source::Map)] The rendered CSS and the associated
|
|
287
|
+
# source map
|
|
288
|
+
# @raise [Sass::SyntaxError] if there's an error in the document, or if the
|
|
289
|
+
# public URL for this document couldn't be determined.
|
|
290
|
+
# @raise [Encoding::UndefinedConversionError] if the source encoding
|
|
291
|
+
# cannot be converted to UTF-8
|
|
292
|
+
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
|
293
|
+
def render_with_sourcemap(sourcemap_uri)
|
|
294
|
+
return _render_with_sourcemap(sourcemap_uri) unless @options[:quiet]
|
|
295
|
+
Sass::Util.silence_sass_warnings {_render_with_sourcemap(sourcemap_uri)}
|
|
264
296
|
end
|
|
297
|
+
|
|
265
298
|
alias_method :to_css, :render
|
|
266
299
|
|
|
267
300
|
# Parses the document into its parse tree. Memoized.
|
|
@@ -269,9 +302,11 @@ module Sass
|
|
|
269
302
|
# @return [Sass::Tree::Node] The root of the parse tree.
|
|
270
303
|
# @raise [Sass::SyntaxError] if there's an error in the document
|
|
271
304
|
def to_tree
|
|
272
|
-
@tree ||= @options[:quiet]
|
|
273
|
-
|
|
274
|
-
|
|
305
|
+
@tree ||= if @options[:quiet]
|
|
306
|
+
Sass::Util.silence_sass_warnings {_to_tree}
|
|
307
|
+
else
|
|
308
|
+
_to_tree
|
|
309
|
+
end
|
|
275
310
|
end
|
|
276
311
|
|
|
277
312
|
# Returns the original encoding of the document,
|
|
@@ -300,7 +335,8 @@ module Sass
|
|
|
300
335
|
#
|
|
301
336
|
# @private
|
|
302
337
|
def _dependencies(seen, engines)
|
|
303
|
-
|
|
338
|
+
key = [@options[:filename], @options[:importer]]
|
|
339
|
+
return if seen.include?(key)
|
|
304
340
|
seen << key
|
|
305
341
|
engines << self
|
|
306
342
|
to_tree.grep(Tree::ImportNode) do |n|
|
|
@@ -311,8 +347,34 @@ module Sass
|
|
|
311
347
|
|
|
312
348
|
private
|
|
313
349
|
|
|
314
|
-
def
|
|
315
|
-
|
|
350
|
+
def _render_with_sourcemap(sourcemap_uri)
|
|
351
|
+
if @options[:filename].nil?
|
|
352
|
+
raise Sass::SyntaxError.new(<<ERR)
|
|
353
|
+
Error generating source map: couldn't determine public URL for the source stylesheet.
|
|
354
|
+
No filename is available so there's nothing for the source map to link to.
|
|
355
|
+
ERR
|
|
356
|
+
elsif @options[:importer].nil? ||
|
|
357
|
+
!(@options[:importer].public_url(@options[:filename]) ||
|
|
358
|
+
@options[:importer].is_a?(Sass::Importers::Filesystem))
|
|
359
|
+
raise Sass::SyntaxError.new(<<ERR)
|
|
360
|
+
Error generating source map: couldn't determine public URL for "#{@options[:filename]}".
|
|
361
|
+
Without a public URL, there's nothing for the source map to link to.
|
|
362
|
+
Custom importers should define the #public_url method.
|
|
363
|
+
ERR
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
rendered, sourcemap = _to_tree.render_with_sourcemap
|
|
367
|
+
compressed = @options[:style] == :compressed
|
|
368
|
+
rendered << "\n" if rendered[-1] != ?\n
|
|
369
|
+
rendered << "\n" unless compressed
|
|
370
|
+
rendered << "/*# sourceMappingURL="
|
|
371
|
+
rendered << Sass::Util.escape_uri(sourcemap_uri)
|
|
372
|
+
rendered << " */"
|
|
373
|
+
rendered = encode_and_set_charset(rendered)
|
|
374
|
+
return rendered, sourcemap
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def encode_and_set_charset(rendered)
|
|
316
378
|
return rendered if ruby1_8?
|
|
317
379
|
begin
|
|
318
380
|
# Try to convert the result to the original encoding,
|
|
@@ -330,7 +392,7 @@ module Sass
|
|
|
330
392
|
key = sassc_key
|
|
331
393
|
sha = Digest::SHA1.hexdigest(@template)
|
|
332
394
|
|
|
333
|
-
if root = @options[:cache_store].retrieve(key, sha)
|
|
395
|
+
if (root = @options[:cache_store].retrieve(key, sha))
|
|
334
396
|
root.options = @options
|
|
335
397
|
return root
|
|
336
398
|
end
|
|
@@ -339,7 +401,7 @@ module Sass
|
|
|
339
401
|
check_encoding!
|
|
340
402
|
|
|
341
403
|
if @options[:syntax] == :scss
|
|
342
|
-
root = Sass::SCSS::Parser.new(@template, @options[:filename]).parse
|
|
404
|
+
root = Sass::SCSS::Parser.new(@template, @options[:filename], @options[:importer]).parse
|
|
343
405
|
else
|
|
344
406
|
root = Tree::RootNode.new(@template)
|
|
345
407
|
append_children(root, tree(tabulate(@template)).first, true)
|
|
@@ -424,12 +486,15 @@ END
|
|
|
424
486
|
raise SyntaxError.new(message, :line => index)
|
|
425
487
|
end
|
|
426
488
|
|
|
427
|
-
lines << Line.new(line.strip, line_tabs, index,
|
|
489
|
+
lines << Line.new(line.strip, line_tabs, index, line_tab_str.size, @options[:filename], [])
|
|
428
490
|
end
|
|
429
491
|
lines
|
|
430
492
|
end
|
|
431
493
|
|
|
494
|
+
# @comment
|
|
495
|
+
# rubocop:disable ParameterLists
|
|
432
496
|
def try_comment(line, last, tab_str, comment_tab_str, index)
|
|
497
|
+
# rubocop:enable ParameterLists
|
|
433
498
|
return unless last && last.comment?
|
|
434
499
|
# Nested comment stuff must be at least one whitespace char deeper
|
|
435
500
|
# than the normal indentation
|
|
@@ -454,7 +519,8 @@ MSG
|
|
|
454
519
|
nodes = []
|
|
455
520
|
while (line = arr[i]) && line.tabs >= base
|
|
456
521
|
if line.tabs > base
|
|
457
|
-
raise SyntaxError.new(
|
|
522
|
+
raise SyntaxError.new(
|
|
523
|
+
"The line was indented #{line.tabs - base} levels deeper than the previous line.",
|
|
458
524
|
:line => line.index) if line.tabs > base + 1
|
|
459
525
|
|
|
460
526
|
nodes.last.children, i = tree(arr, i)
|
|
@@ -468,6 +534,7 @@ MSG
|
|
|
468
534
|
|
|
469
535
|
def build_tree(parent, line, root = false)
|
|
470
536
|
@line = line.index
|
|
537
|
+
@offset = line.offset
|
|
471
538
|
node_or_nodes = parse_line(parent, line, root)
|
|
472
539
|
|
|
473
540
|
Array(node_or_nodes).each do |node|
|
|
@@ -551,12 +618,25 @@ WARNING
|
|
|
551
618
|
# which begin with ::,
|
|
552
619
|
# as well as pseudo-classes
|
|
553
620
|
# if we're using the new property syntax
|
|
554
|
-
Tree::RuleNode.new(parse_interp(line.text))
|
|
621
|
+
Tree::RuleNode.new(parse_interp(line.text), full_line_range(line))
|
|
555
622
|
else
|
|
623
|
+
name_start_offset = line.offset + 1 # +1 for the leading ':'
|
|
556
624
|
name, value = line.text.scan(PROPERTY_OLD)[0]
|
|
557
625
|
raise SyntaxError.new("Invalid property: \"#{line.text}\".",
|
|
558
626
|
:line => @line) if name.nil? || value.nil?
|
|
559
|
-
|
|
627
|
+
|
|
628
|
+
value_start_offset = name_end_offset = name_start_offset + name.length
|
|
629
|
+
unless value.empty?
|
|
630
|
+
# +1 and -1 both compensate for the leading ':', which is part of line.text
|
|
631
|
+
value_start_offset = name_start_offset + line.text.index(value, name.length + 1) - 1
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
property = parse_property(name, parse_interp(name), value, :old, line, value_start_offset)
|
|
635
|
+
property.name_source_range = Sass::Source::Range.new(
|
|
636
|
+
Sass::Source::Position.new(@line, to_parser_offset(name_start_offset)),
|
|
637
|
+
Sass::Source::Position.new(@line, to_parser_offset(name_end_offset)),
|
|
638
|
+
@options[:filename], @options[:importer])
|
|
639
|
+
property
|
|
560
640
|
end
|
|
561
641
|
when ?$
|
|
562
642
|
parse_variable(line)
|
|
@@ -565,12 +645,12 @@ WARNING
|
|
|
565
645
|
when DIRECTIVE_CHAR
|
|
566
646
|
parse_directive(parent, line, root)
|
|
567
647
|
when ESCAPE_CHAR
|
|
568
|
-
Tree::RuleNode.new(parse_interp(line.text[1..-1]))
|
|
648
|
+
Tree::RuleNode.new(parse_interp(line.text[1..-1]), full_line_range(line))
|
|
569
649
|
when MIXIN_DEFINITION_CHAR
|
|
570
650
|
parse_mixin_definition(line)
|
|
571
651
|
when MIXIN_INCLUDE_CHAR
|
|
572
652
|
if line.text[1].nil? || line.text[1] == ?\s
|
|
573
|
-
Tree::RuleNode.new(parse_interp(line.text))
|
|
653
|
+
Tree::RuleNode.new(parse_interp(line.text), full_line_range(line))
|
|
574
654
|
else
|
|
575
655
|
parse_mixin_include(line, root)
|
|
576
656
|
end
|
|
@@ -582,32 +662,70 @@ WARNING
|
|
|
582
662
|
def parse_property_or_rule(line)
|
|
583
663
|
scanner = Sass::Util::MultibyteStringScanner.new(line.text)
|
|
584
664
|
hack_char = scanner.scan(/[:\*\.]|\#(?!\{)/)
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
665
|
+
offset = line.offset
|
|
666
|
+
offset += hack_char.length if hack_char
|
|
667
|
+
parser = Sass::SCSS::Parser.new(scanner,
|
|
668
|
+
@options[:filename], @options[:importer],
|
|
669
|
+
@line, to_parser_offset(offset))
|
|
670
|
+
|
|
671
|
+
unless (res = parser.parse_interp_ident)
|
|
672
|
+
parsed = parse_interp(line.text, line.offset)
|
|
673
|
+
return Tree::RuleNode.new(parsed, full_line_range(line))
|
|
589
674
|
end
|
|
675
|
+
|
|
676
|
+
ident_range = Sass::Source::Range.new(
|
|
677
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset)),
|
|
678
|
+
Sass::Source::Position.new(@line, parser.offset),
|
|
679
|
+
@options[:filename], @options[:importer])
|
|
680
|
+
offset = parser.offset - 1
|
|
590
681
|
res.unshift(hack_char) if hack_char
|
|
591
|
-
|
|
682
|
+
|
|
683
|
+
# Handle comments after a property name but before the colon.
|
|
684
|
+
if (comment = scanner.scan(Sass::SCSS::RX::COMMENT))
|
|
592
685
|
res << comment
|
|
686
|
+
offset += comment.length
|
|
593
687
|
end
|
|
594
688
|
|
|
595
689
|
name = line.text[0...scanner.pos]
|
|
596
|
-
if scanner.scan(/\s*:(?:\s
|
|
597
|
-
|
|
690
|
+
if (scanned = scanner.scan(/\s*:(?:\s+|$)/)) # test for a property
|
|
691
|
+
offset += scanned.length
|
|
692
|
+
property = parse_property(name, res, scanner.rest, :new, line, offset)
|
|
693
|
+
property.name_source_range = ident_range
|
|
694
|
+
property
|
|
598
695
|
else
|
|
599
696
|
res.pop if comment
|
|
600
|
-
|
|
697
|
+
|
|
698
|
+
if (trailing = (scanner.scan(/\s*#{Sass::SCSS::RX::COMMENT}/) ||
|
|
699
|
+
scanner.scan(/\s*#{Sass::SCSS::RX::SINGLE_LINE_COMMENT}/)))
|
|
700
|
+
trailing.strip!
|
|
701
|
+
end
|
|
702
|
+
interp_parsed = parse_interp(scanner.rest)
|
|
703
|
+
selector_range = Sass::Source::Range.new(
|
|
704
|
+
ident_range.start_pos,
|
|
705
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
|
|
706
|
+
@options[:filename], @options[:importer])
|
|
707
|
+
rule = Tree::RuleNode.new(res + interp_parsed, selector_range)
|
|
708
|
+
rule << Tree::CommentNode.new([trailing], :silent) if trailing
|
|
709
|
+
rule
|
|
601
710
|
end
|
|
602
711
|
end
|
|
603
712
|
|
|
604
|
-
|
|
713
|
+
# @comment
|
|
714
|
+
# rubocop:disable ParameterLists
|
|
715
|
+
def parse_property(name, parsed_name, value, prop, line, start_offset)
|
|
716
|
+
# rubocop:enable ParameterLists
|
|
605
717
|
if value.strip.empty?
|
|
606
|
-
expr = Sass::Script::String.new("")
|
|
718
|
+
expr = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
|
|
719
|
+
end_offset = start_offset
|
|
607
720
|
else
|
|
608
|
-
expr = parse_script(value, :offset =>
|
|
721
|
+
expr = parse_script(value, :offset => to_parser_offset(start_offset))
|
|
722
|
+
end_offset = expr.source_range.end_pos.offset - 1
|
|
609
723
|
end
|
|
610
724
|
node = Tree::PropNode.new(parse_interp(name), expr, prop)
|
|
725
|
+
node.value_source_range = Sass::Source::Range.new(
|
|
726
|
+
Sass::Source::Position.new(line.index, to_parser_offset(start_offset)),
|
|
727
|
+
Sass::Source::Position.new(line.index, to_parser_offset(end_offset)),
|
|
728
|
+
@options[:filename], @options[:importer])
|
|
611
729
|
if value.strip.empty? && line.children.empty?
|
|
612
730
|
raise SyntaxError.new(
|
|
613
731
|
"Invalid property: \"#{node.declaration}\" (no value)." +
|
|
@@ -618,15 +736,24 @@ WARNING
|
|
|
618
736
|
end
|
|
619
737
|
|
|
620
738
|
def parse_variable(line)
|
|
621
|
-
name, value,
|
|
739
|
+
name, value, flags = line.text.scan(Script::MATCH)[0]
|
|
622
740
|
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
|
|
623
741
|
:line => @line + 1) unless line.children.empty?
|
|
624
742
|
raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
|
|
625
743
|
:line => @line) unless name && value
|
|
744
|
+
flags = flags ? flags.split(/\s+/) : []
|
|
745
|
+
if (invalid_flag = flags.find {|f| f != '!default' && f != '!global'})
|
|
746
|
+
raise SyntaxError.new("Invalid flag \"#{invalid_flag}\".", :line => @line)
|
|
747
|
+
end
|
|
626
748
|
|
|
627
|
-
|
|
749
|
+
# This workaround is needed for the case when the variable value is part of the identifier,
|
|
750
|
+
# otherwise we end up with the offset equal to the value index inside the name:
|
|
751
|
+
# $red_color: red;
|
|
752
|
+
var_lhs_length = 1 + name.length # 1 stands for '$'
|
|
753
|
+
index = line.text.index(value, line.offset + var_lhs_length) || 0
|
|
754
|
+
expr = parse_script(value, :offset => to_parser_offset(line.offset + index))
|
|
628
755
|
|
|
629
|
-
Tree::VariableNode.new(name, expr, default)
|
|
756
|
+
Tree::VariableNode.new(name, expr, flags.include?('!default'), flags.include?('!global'))
|
|
630
757
|
end
|
|
631
758
|
|
|
632
759
|
def parse_comment(line)
|
|
@@ -636,107 +763,162 @@ WARNING
|
|
|
636
763
|
if silent
|
|
637
764
|
value = [line.text]
|
|
638
765
|
else
|
|
639
|
-
value = self.class.parse_interp(
|
|
640
|
-
|
|
766
|
+
value = self.class.parse_interp(
|
|
767
|
+
line.text, line.index, to_parser_offset(line.offset), :filename => @filename)
|
|
641
768
|
end
|
|
642
769
|
value = with_extracted_values(value) do |str|
|
|
643
770
|
str = str.gsub(/^#{line.comment_tab_str}/m, '')[2..-1] # get rid of // or /*
|
|
644
771
|
format_comment_text(str, silent)
|
|
645
772
|
end
|
|
646
|
-
type = if silent
|
|
773
|
+
type = if silent
|
|
774
|
+
:silent
|
|
775
|
+
elsif loud
|
|
776
|
+
:loud
|
|
777
|
+
else
|
|
778
|
+
:normal
|
|
779
|
+
end
|
|
647
780
|
Tree::CommentNode.new(value, type)
|
|
648
781
|
else
|
|
649
|
-
Tree::RuleNode.new(parse_interp(line.text))
|
|
782
|
+
Tree::RuleNode.new(parse_interp(line.text), full_line_range(line))
|
|
650
783
|
end
|
|
651
784
|
end
|
|
652
785
|
|
|
786
|
+
DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
|
|
787
|
+
:each, :while, :if, :else, :extend, :import, :media, :charset, :content,
|
|
788
|
+
:at_root]
|
|
789
|
+
|
|
790
|
+
# @comment
|
|
791
|
+
# rubocop:disable MethodLength
|
|
653
792
|
def parse_directive(parent, line, root)
|
|
654
793
|
directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
|
|
655
794
|
offset = directive.size + whitespace.size + 1 if whitespace
|
|
656
795
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
when 'content'
|
|
665
|
-
parse_content_directive(line)
|
|
666
|
-
when 'include'
|
|
667
|
-
parse_mixin_include(line, root)
|
|
668
|
-
when 'function'
|
|
669
|
-
parse_function(line, root)
|
|
670
|
-
when 'for'
|
|
671
|
-
parse_for(line, root, value)
|
|
672
|
-
when 'each'
|
|
673
|
-
parse_each(line, root, value)
|
|
674
|
-
when 'else'
|
|
675
|
-
parse_else(parent, line, value)
|
|
676
|
-
when 'while'
|
|
677
|
-
raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
|
|
678
|
-
Tree::WhileNode.new(parse_script(value, :offset => offset))
|
|
679
|
-
when 'if'
|
|
680
|
-
raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
|
|
681
|
-
Tree::IfNode.new(parse_script(value, :offset => offset))
|
|
682
|
-
when 'debug'
|
|
683
|
-
raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
|
|
684
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
|
|
685
|
-
:line => @line + 1) unless line.children.empty?
|
|
686
|
-
offset = line.offset + line.text.index(value).to_i
|
|
687
|
-
Tree::DebugNode.new(parse_script(value, :offset => offset))
|
|
688
|
-
when 'extend'
|
|
689
|
-
raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
|
|
690
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
|
|
691
|
-
:line => @line + 1) unless line.children.empty?
|
|
692
|
-
optional = !!value.gsub!(/\s+#{Sass::SCSS::RX::OPTIONAL}$/, '')
|
|
693
|
-
offset = line.offset + line.text.index(value).to_i
|
|
694
|
-
Tree::ExtendNode.new(parse_interp(value, offset), optional)
|
|
695
|
-
when 'warn'
|
|
696
|
-
raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
|
|
697
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
|
|
698
|
-
:line => @line + 1) unless line.children.empty?
|
|
699
|
-
offset = line.offset + line.text.index(value).to_i
|
|
700
|
-
Tree::WarnNode.new(parse_script(value, :offset => offset))
|
|
701
|
-
when 'return'
|
|
702
|
-
raise SyntaxError.new("Invalid @return: expected expression.") unless value
|
|
703
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath return directives.",
|
|
704
|
-
:line => @line + 1) unless line.children.empty?
|
|
705
|
-
offset = line.offset + line.text.index(value).to_i
|
|
706
|
-
Tree::ReturnNode.new(parse_script(value, :offset => offset))
|
|
707
|
-
when 'charset'
|
|
708
|
-
name = value && value[/\A(["'])(.*)\1\Z/, 2] #"
|
|
709
|
-
raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name
|
|
710
|
-
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath charset directives.",
|
|
711
|
-
:line => @line + 1) unless line.children.empty?
|
|
712
|
-
Tree::CharsetNode.new(name)
|
|
713
|
-
when 'media'
|
|
796
|
+
directive_name = directive.gsub('-', '_').to_sym
|
|
797
|
+
if DIRECTIVES.include?(directive_name)
|
|
798
|
+
return send("parse_#{directive_name}_directive", parent, line, root, value, offset)
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
unprefixed_directive = directive.gsub(/^-[a-z0-9]+-/i, '')
|
|
802
|
+
if unprefixed_directive == 'supports'
|
|
714
803
|
parser = Sass::SCSS::Parser.new(value, @options[:filename], @line)
|
|
715
|
-
Tree::
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
804
|
+
return Tree::SupportsNode.new(directive, parser.parse_supports_condition)
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
Tree::DirectiveNode.new(
|
|
808
|
+
value.nil? ? ["@#{directive}"] : ["@#{directive} "] + parse_interp(value, offset))
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
def parse_while_directive(parent, line, root, value, offset)
|
|
812
|
+
raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
|
|
813
|
+
Tree::WhileNode.new(parse_script(value, :offset => offset))
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
def parse_if_directive(parent, line, root, value, offset)
|
|
817
|
+
raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
|
|
818
|
+
Tree::IfNode.new(parse_script(value, :offset => offset))
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
def parse_debug_directive(parent, line, root, value, offset)
|
|
822
|
+
raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
|
|
823
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
|
|
824
|
+
:line => @line + 1) unless line.children.empty?
|
|
825
|
+
offset = line.offset + line.text.index(value).to_i
|
|
826
|
+
Tree::DebugNode.new(parse_script(value, :offset => offset))
|
|
827
|
+
end
|
|
722
828
|
|
|
723
|
-
|
|
724
|
-
|
|
829
|
+
def parse_extend_directive(parent, line, root, value, offset)
|
|
830
|
+
raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
|
|
831
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
|
|
832
|
+
:line => @line + 1) unless line.children.empty?
|
|
833
|
+
optional = !!value.gsub!(/\s+#{Sass::SCSS::RX::OPTIONAL}$/, '')
|
|
834
|
+
offset = line.offset + line.text.index(value).to_i
|
|
835
|
+
interp_parsed = parse_interp(value, offset)
|
|
836
|
+
selector_range = Sass::Source::Range.new(
|
|
837
|
+
Sass::Source::Position.new(@line, to_parser_offset(offset)),
|
|
838
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
|
|
839
|
+
@options[:filename], @options[:importer]
|
|
840
|
+
)
|
|
841
|
+
Tree::ExtendNode.new(interp_parsed, optional, selector_range)
|
|
842
|
+
end
|
|
843
|
+
# @comment
|
|
844
|
+
# rubocop:enable MethodLength
|
|
845
|
+
|
|
846
|
+
def parse_warn_directive(parent, line, root, value, offset)
|
|
847
|
+
raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
|
|
848
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
|
|
849
|
+
:line => @line + 1) unless line.children.empty?
|
|
850
|
+
offset = line.offset + line.text.index(value).to_i
|
|
851
|
+
Tree::WarnNode.new(parse_script(value, :offset => offset))
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
def parse_return_directive(parent, line, root, value, offset)
|
|
855
|
+
raise SyntaxError.new("Invalid @return: expected expression.") unless value
|
|
856
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath return directives.",
|
|
857
|
+
:line => @line + 1) unless line.children.empty?
|
|
858
|
+
offset = line.offset + line.text.index(value).to_i
|
|
859
|
+
Tree::ReturnNode.new(parse_script(value, :offset => offset))
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
def parse_charset_directive(parent, line, root, value, offset)
|
|
863
|
+
name = value && value[/\A(["'])(.*)\1\Z/, 2] # "
|
|
864
|
+
raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name
|
|
865
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath charset directives.",
|
|
866
|
+
:line => @line + 1) unless line.children.empty?
|
|
867
|
+
Tree::CharsetNode.new(name)
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
def parse_media_directive(parent, line, root, value, offset)
|
|
871
|
+
parser = Sass::SCSS::Parser.new(value,
|
|
872
|
+
@options[:filename], @options[:importer],
|
|
873
|
+
@line, to_parser_offset(@offset))
|
|
874
|
+
offset = line.offset + line.text.index('media').to_i - 1
|
|
875
|
+
parsed_media_query_list = parser.parse_media_query_list.to_a
|
|
876
|
+
node = Tree::MediaNode.new(parsed_media_query_list)
|
|
877
|
+
node.source_range = Sass::Source::Range.new(
|
|
878
|
+
Sass::Source::Position.new(@line, to_parser_offset(offset)),
|
|
879
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
|
|
880
|
+
@options[:filename], @options[:importer])
|
|
881
|
+
node
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
def parse_at_root_directive(parent, line, root, value, offset)
|
|
885
|
+
return Sass::Tree::AtRootNode.new unless value
|
|
886
|
+
|
|
887
|
+
if value.start_with?('(')
|
|
888
|
+
parser = Sass::SCSS::Parser.new(value,
|
|
889
|
+
@options[:filename], @options[:importer],
|
|
890
|
+
@line, to_parser_offset(@offset))
|
|
891
|
+
offset = line.offset + line.text.index('at-root').to_i - 1
|
|
892
|
+
return Tree::AtRootNode.new(parser.parse_at_root_query)
|
|
725
893
|
end
|
|
894
|
+
|
|
895
|
+
at_root_node = Tree::AtRootNode.new
|
|
896
|
+
parsed = parse_interp(value, offset)
|
|
897
|
+
rule_node = Tree::RuleNode.new(parsed, full_line_range(line))
|
|
898
|
+
|
|
899
|
+
# The caller expects to automatically add children to the returned node
|
|
900
|
+
# and we want it to add children to the rule node instead, so we
|
|
901
|
+
# manually handle the wiring here and return nil so the caller doesn't
|
|
902
|
+
# duplicate our efforts.
|
|
903
|
+
append_children(rule_node, line.children, false)
|
|
904
|
+
at_root_node << rule_node
|
|
905
|
+
parent << at_root_node
|
|
906
|
+
nil
|
|
726
907
|
end
|
|
727
908
|
|
|
728
|
-
def
|
|
729
|
-
var, from_expr, to_name, to_expr =
|
|
909
|
+
def parse_for_directive(parent, line, root, value, offset)
|
|
910
|
+
var, from_expr, to_name, to_expr =
|
|
911
|
+
value.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
|
|
730
912
|
|
|
731
913
|
if var.nil? # scan failed, try to figure out why for error message
|
|
732
|
-
if
|
|
914
|
+
if value !~ /^[^\s]+/
|
|
733
915
|
expected = "variable name"
|
|
734
|
-
elsif
|
|
916
|
+
elsif value !~ /^[^\s]+\s+from\s+.+/
|
|
735
917
|
expected = "'from <expr>'"
|
|
736
918
|
else
|
|
737
919
|
expected = "'to <expr>' or 'through <expr>'"
|
|
738
920
|
end
|
|
739
|
-
raise SyntaxError.new("Invalid for directive '@for #{
|
|
921
|
+
raise SyntaxError.new("Invalid for directive '@for #{value}': expected #{expected}.")
|
|
740
922
|
end
|
|
741
923
|
raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
|
|
742
924
|
|
|
@@ -746,31 +928,35 @@ WARNING
|
|
|
746
928
|
Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
|
|
747
929
|
end
|
|
748
930
|
|
|
749
|
-
def
|
|
750
|
-
|
|
931
|
+
def parse_each_directive(parent, line, root, value, offset)
|
|
932
|
+
vars, list_expr = value.scan(/^([^\s]+(?:\s*,\s*[^\s]+)*)\s+in\s+(.+)$/).first
|
|
751
933
|
|
|
752
|
-
if
|
|
753
|
-
if
|
|
934
|
+
if vars.nil? # scan failed, try to figure out why for error message
|
|
935
|
+
if value !~ /^[^\s]+/
|
|
754
936
|
expected = "variable name"
|
|
755
|
-
elsif
|
|
937
|
+
elsif value !~ /^[^\s]+(?:\s*,\s*[^\s]+)*[^\s]+\s+from\s+.+/
|
|
756
938
|
expected = "'in <expr>'"
|
|
757
939
|
end
|
|
758
|
-
raise SyntaxError.new("Invalid
|
|
940
|
+
raise SyntaxError.new("Invalid each directive '@each #{value}': expected #{expected}.")
|
|
941
|
+
end
|
|
942
|
+
|
|
943
|
+
vars = vars.split(',').map do |var|
|
|
944
|
+
var.strip!
|
|
945
|
+
raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
|
|
946
|
+
var[1..-1]
|
|
759
947
|
end
|
|
760
|
-
raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
|
|
761
948
|
|
|
762
|
-
var = var[1..-1]
|
|
763
949
|
parsed_list = parse_script(list_expr, :offset => line.offset + line.text.index(list_expr))
|
|
764
|
-
Tree::EachNode.new(
|
|
950
|
+
Tree::EachNode.new(vars, parsed_list)
|
|
765
951
|
end
|
|
766
952
|
|
|
767
|
-
def
|
|
953
|
+
def parse_else_directive(parent, line, root, value, offset)
|
|
768
954
|
previous = parent.children.last
|
|
769
955
|
raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
|
|
770
956
|
|
|
771
|
-
if
|
|
772
|
-
if
|
|
773
|
-
raise SyntaxError.new("Invalid else directive '@else #{
|
|
957
|
+
if value
|
|
958
|
+
if value !~ /^if\s+(.+)/
|
|
959
|
+
raise SyntaxError.new("Invalid else directive '@else #{value}': expected 'if <expr>'.")
|
|
774
960
|
end
|
|
775
961
|
expr = parse_script($1, :offset => line.offset + line.text.index($1))
|
|
776
962
|
end
|
|
@@ -781,7 +967,7 @@ WARNING
|
|
|
781
967
|
nil
|
|
782
968
|
end
|
|
783
969
|
|
|
784
|
-
def
|
|
970
|
+
def parse_import_directive(parent, line, root, value, offset)
|
|
785
971
|
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
|
|
786
972
|
:line => @line + 1) unless line.children.empty?
|
|
787
973
|
|
|
@@ -789,8 +975,9 @@ WARNING
|
|
|
789
975
|
values = []
|
|
790
976
|
|
|
791
977
|
loop do
|
|
792
|
-
unless node = parse_import_arg(scanner, offset + scanner.pos)
|
|
793
|
-
raise SyntaxError.new(
|
|
978
|
+
unless (node = parse_import_arg(scanner, offset + scanner.pos))
|
|
979
|
+
raise SyntaxError.new(
|
|
980
|
+
"Invalid @import: expected file to import, was #{scanner.rest.inspect}",
|
|
794
981
|
:line => @line)
|
|
795
982
|
end
|
|
796
983
|
values << node
|
|
@@ -802,35 +989,80 @@ WARNING
|
|
|
802
989
|
:line => @line)
|
|
803
990
|
end
|
|
804
991
|
|
|
805
|
-
|
|
992
|
+
values
|
|
806
993
|
end
|
|
807
994
|
|
|
995
|
+
# @comment
|
|
996
|
+
# rubocop:disable MethodLength
|
|
808
997
|
def parse_import_arg(scanner, offset)
|
|
809
998
|
return if scanner.eos?
|
|
810
999
|
|
|
811
1000
|
if scanner.match?(/url\(/i)
|
|
812
|
-
script_parser = Sass::Script::Parser.new(scanner, @line, offset, @options)
|
|
1001
|
+
script_parser = Sass::Script::Parser.new(scanner, @line, to_parser_offset(offset), @options)
|
|
813
1002
|
str = script_parser.parse_string
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1003
|
+
|
|
1004
|
+
media_parser = Sass::SCSS::Parser.new(scanner,
|
|
1005
|
+
@options[:filename], @options[:importer],
|
|
1006
|
+
@line, str.source_range.end_pos.offset)
|
|
1007
|
+
if (media = media_parser.parse_media_query_list)
|
|
1008
|
+
end_pos = Sass::Source::Position.new(@line, media_parser.offset + 1)
|
|
1009
|
+
node = Tree::CssImportNode.new(str, media.to_a)
|
|
1010
|
+
else
|
|
1011
|
+
end_pos = str.source_range.end_pos
|
|
1012
|
+
node = Tree::CssImportNode.new(str)
|
|
1013
|
+
end
|
|
1014
|
+
|
|
1015
|
+
node.source_range = Sass::Source::Range.new(
|
|
1016
|
+
str.source_range.start_pos, end_pos,
|
|
1017
|
+
@options[:filename], @options[:importer])
|
|
1018
|
+
return node
|
|
817
1019
|
end
|
|
818
1020
|
|
|
819
|
-
unless str = scanner.scan(Sass::SCSS::RX::STRING)
|
|
820
|
-
|
|
1021
|
+
unless (str = scanner.scan(Sass::SCSS::RX::STRING))
|
|
1022
|
+
scanned = scanner.scan(/[^,;]+/)
|
|
1023
|
+
node = Tree::ImportNode.new(scanned)
|
|
1024
|
+
start_parser_offset = to_parser_offset(offset)
|
|
1025
|
+
node.source_range = Sass::Source::Range.new(
|
|
1026
|
+
Sass::Source::Position.new(@line, start_parser_offset),
|
|
1027
|
+
Sass::Source::Position.new(@line, start_parser_offset + scanned.length),
|
|
1028
|
+
@options[:filename], @options[:importer])
|
|
1029
|
+
return node
|
|
821
1030
|
end
|
|
822
1031
|
|
|
1032
|
+
start_offset = offset
|
|
1033
|
+
offset += str.length
|
|
823
1034
|
val = scanner[1] || scanner[2]
|
|
824
|
-
scanner.scan(/\s*/)
|
|
1035
|
+
scanned = scanner.scan(/\s*/)
|
|
825
1036
|
if !scanner.match?(/[,;]|$/)
|
|
826
|
-
|
|
1037
|
+
offset += scanned.length if scanned
|
|
1038
|
+
media_parser = Sass::SCSS::Parser.new(scanner,
|
|
1039
|
+
@options[:filename], @options[:importer], @line, offset)
|
|
827
1040
|
media = media_parser.parse_media_query_list
|
|
828
|
-
Tree::CssImportNode.new(str || uri, media.to_a)
|
|
829
|
-
|
|
830
|
-
|
|
1041
|
+
node = Tree::CssImportNode.new(str || uri, media.to_a)
|
|
1042
|
+
node.source_range = Sass::Source::Range.new(
|
|
1043
|
+
Sass::Source::Position.new(@line, to_parser_offset(start_offset)),
|
|
1044
|
+
Sass::Source::Position.new(@line, media_parser.offset),
|
|
1045
|
+
@options[:filename], @options[:importer])
|
|
1046
|
+
elsif val =~ %r{^(https?:)?//}
|
|
1047
|
+
node = Tree::CssImportNode.new("url(#{val})")
|
|
1048
|
+
node.source_range = Sass::Source::Range.new(
|
|
1049
|
+
Sass::Source::Position.new(@line, to_parser_offset(start_offset)),
|
|
1050
|
+
Sass::Source::Position.new(@line, to_parser_offset(offset)),
|
|
1051
|
+
@options[:filename], @options[:importer])
|
|
831
1052
|
else
|
|
832
|
-
Tree::ImportNode.new(val)
|
|
1053
|
+
node = Tree::ImportNode.new(val)
|
|
1054
|
+
node.source_range = Sass::Source::Range.new(
|
|
1055
|
+
Sass::Source::Position.new(@line, to_parser_offset(start_offset)),
|
|
1056
|
+
Sass::Source::Position.new(@line, to_parser_offset(offset)),
|
|
1057
|
+
@options[:filename], @options[:importer])
|
|
833
1058
|
end
|
|
1059
|
+
node
|
|
1060
|
+
end
|
|
1061
|
+
# @comment
|
|
1062
|
+
# rubocop:enable MethodLength
|
|
1063
|
+
|
|
1064
|
+
def parse_mixin_directive(parent, line, root, value, offset)
|
|
1065
|
+
parse_mixin_definition(line)
|
|
834
1066
|
end
|
|
835
1067
|
|
|
836
1068
|
MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
|
|
@@ -839,45 +1071,53 @@ WARNING
|
|
|
839
1071
|
raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
|
|
840
1072
|
|
|
841
1073
|
offset = line.offset + line.text.size - arg_string.size
|
|
842
|
-
args, splat = Script::Parser.new(arg_string.strip, @line, offset, @options).
|
|
1074
|
+
args, splat = Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options).
|
|
843
1075
|
parse_mixin_definition_arglist
|
|
844
1076
|
Tree::MixinDefNode.new(name, args, splat)
|
|
845
1077
|
end
|
|
846
1078
|
|
|
847
1079
|
CONTENT_RE = /^@content\s*(.+)?$/
|
|
848
|
-
def parse_content_directive(line)
|
|
1080
|
+
def parse_content_directive(parent, line, root, value, offset)
|
|
849
1081
|
trailing = line.text.scan(CONTENT_RE).first.first
|
|
850
|
-
|
|
1082
|
+
unless trailing.nil?
|
|
1083
|
+
raise SyntaxError.new(
|
|
1084
|
+
"Invalid content directive. Trailing characters found: \"#{trailing}\".")
|
|
1085
|
+
end
|
|
851
1086
|
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath @content directives.",
|
|
852
1087
|
:line => line.index + 1) unless line.children.empty?
|
|
853
1088
|
Tree::ContentNode.new
|
|
854
1089
|
end
|
|
855
1090
|
|
|
1091
|
+
def parse_include_directive(parent, line, root, value, offset)
|
|
1092
|
+
parse_mixin_include(line, root)
|
|
1093
|
+
end
|
|
1094
|
+
|
|
856
1095
|
MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
|
|
857
1096
|
def parse_mixin_include(line, root)
|
|
858
1097
|
name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
|
|
859
1098
|
raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
|
|
860
1099
|
|
|
861
1100
|
offset = line.offset + line.text.size - arg_string.size
|
|
862
|
-
args, keywords, splat
|
|
863
|
-
|
|
864
|
-
|
|
1101
|
+
args, keywords, splat, kwarg_splat =
|
|
1102
|
+
Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options).
|
|
1103
|
+
parse_mixin_include_arglist
|
|
1104
|
+
Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat)
|
|
865
1105
|
end
|
|
866
1106
|
|
|
867
1107
|
FUNCTION_RE = /^@function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
|
|
868
|
-
def
|
|
1108
|
+
def parse_function_directive(parent, line, root, value, offset)
|
|
869
1109
|
name, arg_string = line.text.scan(FUNCTION_RE).first
|
|
870
1110
|
raise SyntaxError.new("Invalid function definition \"#{line.text}\".") if name.nil?
|
|
871
1111
|
|
|
872
1112
|
offset = line.offset + line.text.size - arg_string.size
|
|
873
|
-
args, splat = Script::Parser.new(arg_string.strip, @line, offset, @options).
|
|
1113
|
+
args, splat = Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options).
|
|
874
1114
|
parse_function_definition_arglist
|
|
875
1115
|
Tree::FunctionNode.new(name, args, splat)
|
|
876
1116
|
end
|
|
877
1117
|
|
|
878
1118
|
def parse_script(script, options = {})
|
|
879
1119
|
line = options[:line] || @line
|
|
880
|
-
offset = options[:offset] ||
|
|
1120
|
+
offset = options[:offset] || @offset + 1
|
|
881
1121
|
Script.parse(script, line, offset, @options)
|
|
882
1122
|
end
|
|
883
1123
|
|
|
@@ -890,7 +1130,7 @@ WARNING
|
|
|
890
1130
|
end
|
|
891
1131
|
|
|
892
1132
|
return silent ? "//" : "/* */" if content.empty?
|
|
893
|
-
content.last.gsub!(
|
|
1133
|
+
content.last.gsub!(/ ?\*\/ *$/, '')
|
|
894
1134
|
content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
|
|
895
1135
|
content.first.gsub!(/^ /, '') unless removed_first
|
|
896
1136
|
if silent
|
|
@@ -905,8 +1145,20 @@ WARNING
|
|
|
905
1145
|
self.class.parse_interp(text, @line, offset, :filename => @filename)
|
|
906
1146
|
end
|
|
907
1147
|
|
|
1148
|
+
# Parser tracks 1-based line and offset, so our offset should be converted.
|
|
1149
|
+
def to_parser_offset(offset)
|
|
1150
|
+
offset + 1
|
|
1151
|
+
end
|
|
1152
|
+
|
|
1153
|
+
def full_line_range(line)
|
|
1154
|
+
Sass::Source::Range.new(
|
|
1155
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset)),
|
|
1156
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
|
|
1157
|
+
@options[:filename], @options[:importer])
|
|
1158
|
+
end
|
|
1159
|
+
|
|
908
1160
|
# It's important that this have strings (at least)
|
|
909
|
-
# at the beginning, the end, and between each Script::Node.
|
|
1161
|
+
# at the beginning, the end, and between each Script::Tree::Node.
|
|
910
1162
|
#
|
|
911
1163
|
# @private
|
|
912
1164
|
def self.parse_interp(text, line, offset, options)
|
|
@@ -914,12 +1166,13 @@ WARNING
|
|
|
914
1166
|
rest = Sass::Shared.handle_interpolation text do |scan|
|
|
915
1167
|
escapes = scan[2].size
|
|
916
1168
|
res << scan.matched[0...-2 - escapes]
|
|
917
|
-
if escapes
|
|
1169
|
+
if escapes.odd?
|
|
918
1170
|
res << "\\" * (escapes - 1) << '#{'
|
|
919
1171
|
else
|
|
920
1172
|
res << "\\" * [0, escapes - 1].max
|
|
1173
|
+
# Add 1 to emulate to_parser_offset.
|
|
921
1174
|
res << Script::Parser.new(
|
|
922
|
-
scan, line, offset + scan.pos - scan.matched_size, options).
|
|
1175
|
+
scan, line, offset + scan.pos - scan.matched_size + 1, options).
|
|
923
1176
|
parse_interpolated
|
|
924
1177
|
end
|
|
925
1178
|
end
|