sass 3.1.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTING +1 -1
- data/MIT-LICENSE +2 -2
- data/README.md +29 -17
- data/Rakefile +43 -9
- data/VERSION +1 -1
- data/VERSION_DATE +1 -0
- data/VERSION_NAME +1 -1
- data/bin/sass +6 -1
- data/bin/sass-convert +6 -1
- data/bin/scss +6 -1
- data/ext/mkrf_conf.rb +27 -0
- data/lib/sass/cache_stores/base.rb +7 -3
- data/lib/sass/cache_stores/chain.rb +3 -2
- data/lib/sass/cache_stores/filesystem.rb +5 -7
- data/lib/sass/cache_stores/memory.rb +1 -1
- data/lib/sass/cache_stores/null.rb +2 -2
- data/lib/sass/callbacks.rb +2 -1
- data/lib/sass/css.rb +168 -53
- data/lib/sass/engine.rb +502 -174
- data/lib/sass/environment.rb +151 -111
- data/lib/sass/error.rb +7 -7
- data/lib/sass/exec.rb +176 -60
- data/lib/sass/features.rb +40 -0
- data/lib/sass/importers/base.rb +46 -7
- data/lib/sass/importers/deprecated_path.rb +51 -0
- data/lib/sass/importers/filesystem.rb +113 -30
- data/lib/sass/importers.rb +1 -0
- data/lib/sass/logger/base.rb +30 -0
- data/lib/sass/logger/log_level.rb +45 -0
- data/lib/sass/logger.rb +12 -0
- data/lib/sass/media.rb +213 -0
- data/lib/sass/plugin/compiler.rb +194 -104
- data/lib/sass/plugin/configuration.rb +18 -25
- data/lib/sass/plugin/merb.rb +1 -1
- data/lib/sass/plugin/staleness_checker.rb +37 -11
- data/lib/sass/plugin.rb +10 -13
- data/lib/sass/railtie.rb +2 -1
- data/lib/sass/repl.rb +5 -6
- data/lib/sass/script/css_lexer.rb +8 -4
- data/lib/sass/script/css_parser.rb +5 -2
- data/lib/sass/script/functions.rb +1547 -618
- data/lib/sass/script/lexer.rb +122 -72
- data/lib/sass/script/parser.rb +304 -135
- data/lib/sass/script/tree/funcall.rb +306 -0
- data/lib/sass/script/{interpolation.rb → tree/interpolation.rb} +43 -13
- data/lib/sass/script/tree/list_literal.rb +77 -0
- data/lib/sass/script/tree/literal.rb +45 -0
- data/lib/sass/script/tree/map_literal.rb +64 -0
- data/lib/sass/script/{node.rb → tree/node.rb} +30 -12
- data/lib/sass/script/{operation.rb → tree/operation.rb} +33 -21
- data/lib/sass/script/{string_interpolation.rb → tree/string_interpolation.rb} +14 -4
- data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +21 -9
- data/lib/sass/script/tree/variable.rb +57 -0
- data/lib/sass/script/tree.rb +15 -0
- data/lib/sass/script/value/arg_list.rb +36 -0
- data/lib/sass/script/value/base.rb +238 -0
- data/lib/sass/script/value/bool.rb +40 -0
- data/lib/sass/script/{color.rb → value/color.rb} +256 -74
- data/lib/sass/script/value/deprecated_false.rb +55 -0
- data/lib/sass/script/value/helpers.rb +155 -0
- data/lib/sass/script/value/list.rb +128 -0
- data/lib/sass/script/value/map.rb +70 -0
- data/lib/sass/script/value/null.rb +49 -0
- data/lib/sass/script/{number.rb → value/number.rb} +115 -62
- data/lib/sass/script/{string.rb → value/string.rb} +9 -11
- data/lib/sass/script/value.rb +12 -0
- data/lib/sass/script.rb +35 -9
- data/lib/sass/scss/css_parser.rb +2 -12
- data/lib/sass/scss/parser.rb +657 -230
- data/lib/sass/scss/rx.rb +17 -12
- data/lib/sass/scss/static_parser.rb +37 -6
- data/lib/sass/scss.rb +0 -1
- data/lib/sass/selector/abstract_sequence.rb +35 -3
- data/lib/sass/selector/comma_sequence.rb +29 -14
- data/lib/sass/selector/sequence.rb +371 -74
- data/lib/sass/selector/simple.rb +28 -13
- data/lib/sass/selector/simple_sequence.rb +163 -36
- data/lib/sass/selector.rb +138 -36
- data/lib/sass/shared.rb +3 -5
- data/lib/sass/source/map.rb +196 -0
- data/lib/sass/source/position.rb +39 -0
- data/lib/sass/source/range.rb +41 -0
- data/lib/sass/stack.rb +126 -0
- data/lib/sass/supports.rb +228 -0
- data/lib/sass/tree/at_root_node.rb +82 -0
- data/lib/sass/tree/comment_node.rb +34 -29
- data/lib/sass/tree/content_node.rb +9 -0
- data/lib/sass/tree/css_import_node.rb +60 -0
- data/lib/sass/tree/debug_node.rb +3 -3
- data/lib/sass/tree/directive_node.rb +33 -3
- data/lib/sass/tree/each_node.rb +9 -9
- data/lib/sass/tree/extend_node.rb +20 -6
- data/lib/sass/tree/for_node.rb +6 -6
- data/lib/sass/tree/function_node.rb +12 -4
- data/lib/sass/tree/if_node.rb +2 -15
- data/lib/sass/tree/import_node.rb +11 -5
- data/lib/sass/tree/media_node.rb +27 -11
- data/lib/sass/tree/mixin_def_node.rb +15 -4
- data/lib/sass/tree/mixin_node.rb +27 -7
- data/lib/sass/tree/node.rb +69 -35
- data/lib/sass/tree/prop_node.rb +47 -31
- data/lib/sass/tree/return_node.rb +4 -3
- data/lib/sass/tree/root_node.rb +20 -4
- data/lib/sass/tree/rule_node.rb +37 -26
- data/lib/sass/tree/supports_node.rb +38 -0
- data/lib/sass/tree/trace_node.rb +33 -0
- data/lib/sass/tree/variable_node.rb +10 -4
- data/lib/sass/tree/visitors/base.rb +5 -8
- data/lib/sass/tree/visitors/check_nesting.rb +67 -52
- data/lib/sass/tree/visitors/convert.rb +134 -53
- data/lib/sass/tree/visitors/cssize.rb +245 -51
- data/lib/sass/tree/visitors/deep_copy.rb +102 -0
- data/lib/sass/tree/visitors/extend.rb +68 -0
- data/lib/sass/tree/visitors/perform.rb +331 -105
- data/lib/sass/tree/visitors/set_options.rb +125 -0
- data/lib/sass/tree/visitors/to_css.rb +259 -95
- data/lib/sass/tree/warn_node.rb +3 -3
- data/lib/sass/tree/while_node.rb +3 -3
- data/lib/sass/util/cross_platform_random.rb +19 -0
- data/lib/sass/util/multibyte_string_scanner.rb +157 -0
- data/lib/sass/util/normalized_map.rb +130 -0
- data/lib/sass/util/ordered_hash.rb +192 -0
- data/lib/sass/util/subset_map.rb +11 -2
- data/lib/sass/util/test.rb +9 -0
- data/lib/sass/util.rb +565 -39
- data/lib/sass/version.rb +27 -15
- data/lib/sass.rb +39 -4
- data/test/sass/cache_test.rb +15 -0
- data/test/sass/compiler_test.rb +223 -0
- data/test/sass/conversion_test.rb +901 -107
- data/test/sass/css2sass_test.rb +94 -0
- data/test/sass/engine_test.rb +1059 -164
- data/test/sass/exec_test.rb +86 -0
- data/test/sass/extend_test.rb +933 -837
- data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
- data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
- data/test/sass/functions_test.rb +995 -136
- data/test/sass/importer_test.rb +338 -18
- data/test/sass/logger_test.rb +58 -0
- data/test/sass/more_results/more_import.css +2 -2
- data/test/sass/plugin_test.rb +114 -30
- data/test/sass/results/cached_import_option.css +3 -0
- data/test/sass/results/filename_fn.css +3 -0
- data/test/sass/results/import.css +2 -2
- data/test/sass/results/import_charset.css +1 -0
- data/test/sass/results/import_charset_1_8.css +1 -0
- data/test/sass/results/import_charset_ibm866.css +1 -0
- data/test/sass/results/import_content.css +1 -0
- data/test/sass/results/script.css +1 -1
- data/test/sass/results/scss_import.css +2 -2
- data/test/sass/results/units.css +2 -2
- data/test/sass/script_conversion_test.rb +43 -1
- data/test/sass/script_test.rb +380 -36
- data/test/sass/scss/css_test.rb +257 -75
- data/test/sass/scss/scss_test.rb +2322 -110
- data/test/sass/source_map_test.rb +887 -0
- data/test/sass/templates/_cached_import_option_partial.scss +1 -0
- data/test/sass/templates/_double_import_loop2.sass +1 -0
- data/test/sass/templates/_filename_fn_import.scss +11 -0
- data/test/sass/templates/_imported_content.sass +3 -0
- data/test/sass/templates/_same_name_different_partiality.scss +1 -0
- data/test/sass/templates/bork5.sass +3 -0
- data/test/sass/templates/cached_import_option.scss +3 -0
- data/test/sass/templates/double_import_loop1.sass +1 -0
- data/test/sass/templates/filename_fn.scss +18 -0
- data/test/sass/templates/import_charset.sass +2 -0
- data/test/sass/templates/import_charset_1_8.sass +2 -0
- data/test/sass/templates/import_charset_ibm866.sass +2 -0
- data/test/sass/templates/import_content.sass +4 -0
- data/test/sass/templates/same_name_different_ext.sass +2 -0
- data/test/sass/templates/same_name_different_ext.scss +1 -0
- data/test/sass/templates/same_name_different_partiality.scss +1 -0
- data/test/sass/templates/single_import_loop.sass +1 -0
- data/test/sass/templates/subdir/import_up1.scss +1 -0
- data/test/sass/templates/subdir/import_up2.scss +1 -0
- data/test/sass/test_helper.rb +1 -1
- data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
- data/test/sass/util/normalized_map_test.rb +51 -0
- data/test/sass/util_test.rb +183 -0
- data/test/sass/value_helpers_test.rb +181 -0
- data/test/test_helper.rb +45 -5
- data/vendor/listen/CHANGELOG.md +228 -0
- data/vendor/listen/CONTRIBUTING.md +38 -0
- data/vendor/listen/Gemfile +30 -0
- data/vendor/listen/Guardfile +8 -0
- data/vendor/{fssm → listen}/LICENSE +1 -1
- data/vendor/listen/README.md +315 -0
- data/vendor/listen/Rakefile +47 -0
- data/vendor/listen/Vagrantfile +96 -0
- data/vendor/listen/lib/listen/adapter.rb +214 -0
- data/vendor/listen/lib/listen/adapters/bsd.rb +112 -0
- data/vendor/listen/lib/listen/adapters/darwin.rb +85 -0
- data/vendor/listen/lib/listen/adapters/linux.rb +113 -0
- data/vendor/listen/lib/listen/adapters/polling.rb +67 -0
- data/vendor/listen/lib/listen/adapters/windows.rb +87 -0
- data/vendor/listen/lib/listen/dependency_manager.rb +126 -0
- data/vendor/listen/lib/listen/directory_record.rb +371 -0
- data/vendor/listen/lib/listen/listener.rb +225 -0
- data/vendor/listen/lib/listen/multi_listener.rb +143 -0
- data/vendor/listen/lib/listen/turnstile.rb +28 -0
- data/vendor/listen/lib/listen/version.rb +3 -0
- data/vendor/listen/lib/listen.rb +40 -0
- data/vendor/listen/listen.gemspec +22 -0
- data/vendor/listen/spec/listen/adapter_spec.rb +183 -0
- data/vendor/listen/spec/listen/adapters/bsd_spec.rb +36 -0
- data/vendor/listen/spec/listen/adapters/darwin_spec.rb +37 -0
- data/vendor/listen/spec/listen/adapters/linux_spec.rb +47 -0
- data/vendor/listen/spec/listen/adapters/polling_spec.rb +68 -0
- data/vendor/listen/spec/listen/adapters/windows_spec.rb +30 -0
- data/vendor/listen/spec/listen/dependency_manager_spec.rb +107 -0
- data/vendor/listen/spec/listen/directory_record_spec.rb +1225 -0
- data/vendor/listen/spec/listen/listener_spec.rb +169 -0
- data/vendor/listen/spec/listen/multi_listener_spec.rb +174 -0
- data/vendor/listen/spec/listen/turnstile_spec.rb +56 -0
- data/vendor/listen/spec/listen_spec.rb +73 -0
- data/vendor/listen/spec/spec_helper.rb +21 -0
- data/vendor/listen/spec/support/adapter_helper.rb +629 -0
- data/vendor/listen/spec/support/directory_record_helper.rb +55 -0
- data/vendor/listen/spec/support/fixtures_helper.rb +29 -0
- data/vendor/listen/spec/support/listeners_helper.rb +156 -0
- data/vendor/listen/spec/support/platform_helper.rb +15 -0
- metadata +344 -271
- data/lib/sass/less.rb +0 -382
- data/lib/sass/script/bool.rb +0 -18
- data/lib/sass/script/funcall.rb +0 -162
- data/lib/sass/script/list.rb +0 -76
- data/lib/sass/script/literal.rb +0 -245
- data/lib/sass/script/variable.rb +0 -54
- data/lib/sass/scss/sass_parser.rb +0 -11
- data/test/sass/less_conversion_test.rb +0 -653
- data/vendor/fssm/README.markdown +0 -55
- data/vendor/fssm/Rakefile +0 -59
- data/vendor/fssm/VERSION.yml +0 -5
- data/vendor/fssm/example.rb +0 -9
- data/vendor/fssm/fssm.gemspec +0 -77
- data/vendor/fssm/lib/fssm/backends/fsevents.rb +0 -36
- data/vendor/fssm/lib/fssm/backends/inotify.rb +0 -26
- data/vendor/fssm/lib/fssm/backends/polling.rb +0 -25
- data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +0 -131
- data/vendor/fssm/lib/fssm/monitor.rb +0 -26
- data/vendor/fssm/lib/fssm/path.rb +0 -91
- data/vendor/fssm/lib/fssm/pathname.rb +0 -502
- data/vendor/fssm/lib/fssm/state/directory.rb +0 -57
- data/vendor/fssm/lib/fssm/state/file.rb +0 -24
- data/vendor/fssm/lib/fssm/support.rb +0 -63
- data/vendor/fssm/lib/fssm/tree.rb +0 -176
- data/vendor/fssm/lib/fssm.rb +0 -33
- data/vendor/fssm/profile/prof-cache.rb +0 -40
- data/vendor/fssm/profile/prof-fssm-pathname.html +0 -1231
- data/vendor/fssm/profile/prof-pathname.rb +0 -68
- data/vendor/fssm/profile/prof-plain-pathname.html +0 -988
- data/vendor/fssm/profile/prof.html +0 -2379
- data/vendor/fssm/spec/path_spec.rb +0 -75
- data/vendor/fssm/spec/root/duck/quack.txt +0 -0
- data/vendor/fssm/spec/root/file.css +0 -0
- data/vendor/fssm/spec/root/file.rb +0 -0
- data/vendor/fssm/spec/root/file.yml +0 -0
- data/vendor/fssm/spec/root/moo/cow.txt +0 -0
- data/vendor/fssm/spec/spec_helper.rb +0 -14
data/lib/sass/engine.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
require 'strscan'
|
2
1
|
require 'set'
|
3
2
|
require 'digest/sha1'
|
4
3
|
require 'sass/cache_stores'
|
4
|
+
require 'sass/source/position'
|
5
|
+
require 'sass/source/range'
|
6
|
+
require 'sass/source/map'
|
5
7
|
require 'sass/tree/node'
|
6
8
|
require 'sass/tree/root_node'
|
7
9
|
require 'sass/tree/rule_node'
|
@@ -9,9 +11,13 @@ require 'sass/tree/comment_node'
|
|
9
11
|
require 'sass/tree/prop_node'
|
10
12
|
require 'sass/tree/directive_node'
|
11
13
|
require 'sass/tree/media_node'
|
14
|
+
require 'sass/tree/supports_node'
|
15
|
+
require 'sass/tree/css_import_node'
|
12
16
|
require 'sass/tree/variable_node'
|
13
17
|
require 'sass/tree/mixin_def_node'
|
14
18
|
require 'sass/tree/mixin_node'
|
19
|
+
require 'sass/tree/trace_node'
|
20
|
+
require 'sass/tree/content_node'
|
15
21
|
require 'sass/tree/function_node'
|
16
22
|
require 'sass/tree/return_node'
|
17
23
|
require 'sass/tree/extend_node'
|
@@ -23,32 +29,41 @@ require 'sass/tree/debug_node'
|
|
23
29
|
require 'sass/tree/warn_node'
|
24
30
|
require 'sass/tree/import_node'
|
25
31
|
require 'sass/tree/charset_node'
|
32
|
+
require 'sass/tree/at_root_node'
|
26
33
|
require 'sass/tree/visitors/base'
|
27
34
|
require 'sass/tree/visitors/perform'
|
28
35
|
require 'sass/tree/visitors/cssize'
|
36
|
+
require 'sass/tree/visitors/extend'
|
29
37
|
require 'sass/tree/visitors/convert'
|
30
38
|
require 'sass/tree/visitors/to_css'
|
39
|
+
require 'sass/tree/visitors/deep_copy'
|
40
|
+
require 'sass/tree/visitors/set_options'
|
31
41
|
require 'sass/tree/visitors/check_nesting'
|
32
42
|
require 'sass/selector'
|
33
43
|
require 'sass/environment'
|
34
44
|
require 'sass/script'
|
35
45
|
require 'sass/scss'
|
46
|
+
require 'sass/stack'
|
36
47
|
require 'sass/error'
|
37
48
|
require 'sass/importers'
|
38
49
|
require 'sass/shared'
|
50
|
+
require 'sass/media'
|
51
|
+
require 'sass/supports'
|
39
52
|
|
40
53
|
module Sass
|
41
|
-
|
42
54
|
# A Sass mixin or function.
|
43
55
|
#
|
44
56
|
# `name`: `String`
|
45
57
|
# : The name of the mixin/function.
|
46
58
|
#
|
47
|
-
# `args`: `Array<(
|
59
|
+
# `args`: `Array<(Script::Tree::Node, Script::Tree::Node)>`
|
48
60
|
# : The arguments for the mixin/function.
|
49
|
-
# Each element is a tuple containing the
|
61
|
+
# Each element is a tuple containing the variable node of the argument
|
50
62
|
# and the parse tree for the default value of the argument.
|
51
63
|
#
|
64
|
+
# `splat`: `Script::Tree::Node?`
|
65
|
+
# : The variable node of the splat argument for this callable, or null.
|
66
|
+
#
|
52
67
|
# `environment`: {Sass::Environment}
|
53
68
|
# : The environment in which the mixin/function was defined.
|
54
69
|
# This is captured so that the mixin/function can have access
|
@@ -56,7 +71,13 @@ module Sass
|
|
56
71
|
#
|
57
72
|
# `tree`: `Array<Tree::Node>`
|
58
73
|
# : The parse tree for the mixin/function.
|
59
|
-
|
74
|
+
#
|
75
|
+
# `has_content`: `Boolean`
|
76
|
+
# : Whether the callable accepts a content block.
|
77
|
+
#
|
78
|
+
# `type`: `String`
|
79
|
+
# : The user-friendly name of the type of the callable.
|
80
|
+
Callable = Struct.new(:name, :args, :splat, :environment, :tree, :has_content, :type)
|
60
81
|
|
61
82
|
# This class handles the parsing and compilation of the Sass template.
|
62
83
|
# Example usage:
|
@@ -66,8 +87,6 @@ module Sass
|
|
66
87
|
# output = sass_engine.render
|
67
88
|
# puts output
|
68
89
|
class Engine
|
69
|
-
include Sass::Util
|
70
|
-
|
71
90
|
# A line of Sass code.
|
72
91
|
#
|
73
92
|
# `text`: `String`
|
@@ -88,7 +107,10 @@ module Sass
|
|
88
107
|
#
|
89
108
|
# `children`: `Array<Line>`
|
90
109
|
# : The lines nested below this one.
|
91
|
-
|
110
|
+
#
|
111
|
+
# `comment_tab_str`: `String?`
|
112
|
+
# : The prefix indentation for this comment, if it is a comment.
|
113
|
+
class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children, :comment_tab_str)
|
92
114
|
def comment?
|
93
115
|
text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR)
|
94
116
|
end
|
@@ -105,6 +127,10 @@ module Sass
|
|
105
127
|
# which is not output as a CSS comment.
|
106
128
|
SASS_COMMENT_CHAR = ?/
|
107
129
|
|
130
|
+
# The character that indicates that a comment allows interpolation
|
131
|
+
# and should be preserved even in `:compressed` mode.
|
132
|
+
SASS_LOUD_COMMENT_CHAR = ?!
|
133
|
+
|
108
134
|
# The character that follows the general COMMENT_CHAR and designates a CSS comment,
|
109
135
|
# which is embedded in the CSS document.
|
110
136
|
CSS_COMMENT_CHAR = ?*
|
@@ -159,11 +185,21 @@ module Sass
|
|
159
185
|
# for quite a long time.
|
160
186
|
options[:line_comments] ||= options[:line_numbers]
|
161
187
|
|
162
|
-
options[:load_paths] = options[:load_paths].map do |p|
|
188
|
+
options[:load_paths] = (options[:load_paths] + Sass.load_paths).map do |p|
|
163
189
|
next p unless p.is_a?(String) || (defined?(Pathname) && p.is_a?(Pathname))
|
164
190
|
options[:filesystem_importer].new(p.to_s)
|
165
191
|
end
|
166
192
|
|
193
|
+
# Remove any deprecated importers if the location is imported explicitly
|
194
|
+
options[:load_paths].reject! do |importer|
|
195
|
+
importer.is_a?(Sass::Importers::DeprecatedPath) &&
|
196
|
+
options[:load_paths].find do |other_importer|
|
197
|
+
other_importer.is_a?(Sass::Importers::Filesystem) &&
|
198
|
+
other_importer != importer &&
|
199
|
+
other_importer.root == importer.root
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
167
203
|
# Backwards compatibility
|
168
204
|
options[:property_syntax] ||= options[:attribute_syntax]
|
169
205
|
case options[:property_syntax]
|
@@ -210,7 +246,7 @@ module Sass
|
|
210
246
|
# If you're compiling a single Sass file from the filesystem,
|
211
247
|
# use \{Sass::Engine.for\_file}.
|
212
248
|
# If you're compiling multiple files from the filesystem,
|
213
|
-
# use {Sass::Plugin.
|
249
|
+
# use {Sass::Plugin}.
|
214
250
|
#
|
215
251
|
# @param template [String] The Sass template.
|
216
252
|
# This template can be encoded using any encoding
|
@@ -222,7 +258,7 @@ module Sass
|
|
222
258
|
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
223
259
|
# @see {Sass::Engine.for_file}
|
224
260
|
# @see {Sass::Plugin}
|
225
|
-
def initialize(template, options={})
|
261
|
+
def initialize(template, options = {})
|
226
262
|
@options = self.class.normalize_options(options)
|
227
263
|
@template = template
|
228
264
|
end
|
@@ -235,9 +271,27 @@ module Sass
|
|
235
271
|
# cannot be converted to UTF-8
|
236
272
|
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
237
273
|
def render
|
238
|
-
return
|
239
|
-
Sass::Util.silence_sass_warnings {
|
274
|
+
return encode_and_set_charset(_to_tree.render) unless @options[:quiet]
|
275
|
+
Sass::Util.silence_sass_warnings {encode_and_set_charset(_to_tree.render)}
|
276
|
+
end
|
277
|
+
|
278
|
+
# Render the template to CSS and return the source map.
|
279
|
+
#
|
280
|
+
# @param sourcemap_uri [String] The sourcemap URI to use in the
|
281
|
+
# `@sourceMappingURL` comment. If this is relative, it should be relative
|
282
|
+
# to the location of the CSS file.
|
283
|
+
# @return [(String, Sass::Source::Map)] The rendered CSS and the associated
|
284
|
+
# source map
|
285
|
+
# @raise [Sass::SyntaxError] if there's an error in the document, or if the
|
286
|
+
# public URL for this document couldn't be determined.
|
287
|
+
# @raise [Encoding::UndefinedConversionError] if the source encoding
|
288
|
+
# cannot be converted to UTF-8
|
289
|
+
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
|
290
|
+
def render_with_sourcemap(sourcemap_uri)
|
291
|
+
return _render_with_sourcemap(sourcemap_uri) unless @options[:quiet]
|
292
|
+
Sass::Util.silence_sass_warnings {_render_with_sourcemap(sourcemap_uri)}
|
240
293
|
end
|
294
|
+
|
241
295
|
alias_method :to_css, :render
|
242
296
|
|
243
297
|
# Parses the document into its parse tree. Memoized.
|
@@ -245,9 +299,11 @@ module Sass
|
|
245
299
|
# @return [Sass::Tree::Node] The root of the parse tree.
|
246
300
|
# @raise [Sass::SyntaxError] if there's an error in the document
|
247
301
|
def to_tree
|
248
|
-
@tree ||= @options[:quiet]
|
249
|
-
|
250
|
-
|
302
|
+
@tree ||= if @options[:quiet]
|
303
|
+
Sass::Util.silence_sass_warnings {_to_tree}
|
304
|
+
else
|
305
|
+
_to_tree
|
306
|
+
end
|
251
307
|
end
|
252
308
|
|
253
309
|
# Returns the original encoding of the document,
|
@@ -269,14 +325,15 @@ module Sass
|
|
269
325
|
# @return [[Sass::Engine]] The dependency documents.
|
270
326
|
def dependencies
|
271
327
|
_dependencies(Set.new, engines = Set.new)
|
272
|
-
engines
|
328
|
+
Sass::Util.array_minus(engines, [self])
|
273
329
|
end
|
274
330
|
|
275
331
|
# Helper for \{#dependencies}.
|
276
332
|
#
|
277
333
|
# @private
|
278
334
|
def _dependencies(seen, engines)
|
279
|
-
|
335
|
+
key = [@options[:filename], @options[:importer]]
|
336
|
+
return if seen.include?(key)
|
280
337
|
seen << key
|
281
338
|
engines << self
|
282
339
|
to_tree.grep(Tree::ImportNode) do |n|
|
@@ -287,9 +344,43 @@ module Sass
|
|
287
344
|
|
288
345
|
private
|
289
346
|
|
290
|
-
def
|
291
|
-
|
292
|
-
|
347
|
+
def _render_with_sourcemap(sourcemap_uri)
|
348
|
+
filename = @options[:filename]
|
349
|
+
importer = @options[:importer]
|
350
|
+
sourcemap_dir = @options[:sourcemap_filename] &&
|
351
|
+
File.dirname(File.expand_path(@options[:sourcemap_filename]))
|
352
|
+
if filename.nil?
|
353
|
+
raise Sass::SyntaxError.new(<<ERR)
|
354
|
+
Error generating source map: couldn't determine public URL for the source stylesheet.
|
355
|
+
No filename is available so there's nothing for the source map to link to.
|
356
|
+
ERR
|
357
|
+
elsif importer.nil?
|
358
|
+
raise Sass::SyntaxError.new(<<ERR)
|
359
|
+
Error generating source map: couldn't determine public URL for "#{filename}".
|
360
|
+
Without a public URL, there's nothing for the source map to link to.
|
361
|
+
An importer was not set for this file.
|
362
|
+
ERR
|
363
|
+
elsif Sass::Util.silence_warnings {importer.public_url(filename, sourcemap_dir).nil?}
|
364
|
+
raise Sass::SyntaxError.new(<<ERR)
|
365
|
+
Error generating source map: couldn't determine public URL for "#{filename}".
|
366
|
+
Without a public URL, there's nothing for the source map to link to.
|
367
|
+
Custom importers should define the #public_url method.
|
368
|
+
ERR
|
369
|
+
end
|
370
|
+
|
371
|
+
rendered, sourcemap = _to_tree.render_with_sourcemap
|
372
|
+
compressed = @options[:style] == :compressed
|
373
|
+
rendered << "\n" if rendered[-1] != ?\n
|
374
|
+
rendered << "\n" unless compressed
|
375
|
+
rendered << "/*# sourceMappingURL="
|
376
|
+
rendered << Sass::Util.escape_uri(sourcemap_uri)
|
377
|
+
rendered << " */\n"
|
378
|
+
rendered = encode_and_set_charset(rendered)
|
379
|
+
return rendered, sourcemap
|
380
|
+
end
|
381
|
+
|
382
|
+
def encode_and_set_charset(rendered)
|
383
|
+
return rendered if Sass::Util.ruby1_8?
|
293
384
|
begin
|
294
385
|
# Try to convert the result to the original encoding,
|
295
386
|
# but if that doesn't work fall back on UTF-8
|
@@ -306,8 +397,7 @@ module Sass
|
|
306
397
|
key = sassc_key
|
307
398
|
sha = Digest::SHA1.hexdigest(@template)
|
308
399
|
|
309
|
-
if root = @options[:cache_store].retrieve(key, sha)
|
310
|
-
@options = root.options.merge(@options)
|
400
|
+
if (root = @options[:cache_store].retrieve(key, sha))
|
311
401
|
root.options = @options
|
312
402
|
return root
|
313
403
|
end
|
@@ -316,7 +406,7 @@ module Sass
|
|
316
406
|
check_encoding!
|
317
407
|
|
318
408
|
if @options[:syntax] == :scss
|
319
|
-
root = Sass::SCSS::Parser.new(@template).parse
|
409
|
+
root = Sass::SCSS::Parser.new(@template, @options[:filename], @options[:importer]).parse
|
320
410
|
else
|
321
411
|
root = Tree::RootNode.new(@template)
|
322
412
|
append_children(root, tree(tabulate(@template)).first, true)
|
@@ -326,7 +416,7 @@ module Sass
|
|
326
416
|
if @options[:cache] && key && sha
|
327
417
|
begin
|
328
418
|
old_options = root.options
|
329
|
-
root.options = {
|
419
|
+
root.options = {}
|
330
420
|
@options[:cache_store].store(key, sha, root)
|
331
421
|
ensure
|
332
422
|
root.options = old_options
|
@@ -346,7 +436,7 @@ module Sass
|
|
346
436
|
def check_encoding!
|
347
437
|
return if @checked_encoding
|
348
438
|
@checked_encoding = true
|
349
|
-
@template, @original_encoding = check_sass_encoding(@template) do |msg, line|
|
439
|
+
@template, @original_encoding = Sass::Util.check_sass_encoding(@template) do |msg, line|
|
350
440
|
raise Sass::SyntaxError.new(msg, :line => line)
|
351
441
|
end
|
352
442
|
end
|
@@ -356,7 +446,7 @@ module Sass
|
|
356
446
|
comment_tab_str = nil
|
357
447
|
first = true
|
358
448
|
lines = []
|
359
|
-
string.gsub(/\r
|
449
|
+
string.gsub(/\r\n|\r|\n/, "\n").scan(/^[^\n]*?$/).each_with_index do |line, index|
|
360
450
|
index += (@options[:line] || 1)
|
361
451
|
if line.strip.empty?
|
362
452
|
lines.last.text << "\n" if lines.last && lines.last.comment?
|
@@ -401,12 +491,15 @@ END
|
|
401
491
|
raise SyntaxError.new(message, :line => index)
|
402
492
|
end
|
403
493
|
|
404
|
-
lines << Line.new(line.strip, line_tabs, index,
|
494
|
+
lines << Line.new(line.strip, line_tabs, index, line_tab_str.size, @options[:filename], [])
|
405
495
|
end
|
406
496
|
lines
|
407
497
|
end
|
408
498
|
|
499
|
+
# @comment
|
500
|
+
# rubocop:disable ParameterLists
|
409
501
|
def try_comment(line, last, tab_str, comment_tab_str, index)
|
502
|
+
# rubocop:enable ParameterLists
|
410
503
|
return unless last && last.comment?
|
411
504
|
# Nested comment stuff must be at least one whitespace char deeper
|
412
505
|
# than the normal indentation
|
@@ -419,7 +512,8 @@ but this line was indented by #{Sass::Shared.human_indentation line[/^\s*/]}.
|
|
419
512
|
MSG
|
420
513
|
end
|
421
514
|
|
422
|
-
last.
|
515
|
+
last.comment_tab_str ||= comment_tab_str
|
516
|
+
last.text << "\n" << line
|
423
517
|
true
|
424
518
|
end
|
425
519
|
|
@@ -430,7 +524,8 @@ MSG
|
|
430
524
|
nodes = []
|
431
525
|
while (line = arr[i]) && line.tabs >= base
|
432
526
|
if line.tabs > base
|
433
|
-
raise SyntaxError.new(
|
527
|
+
raise SyntaxError.new(
|
528
|
+
"The line was indented #{line.tabs - base} levels deeper than the previous line.",
|
434
529
|
:line => line.index) if line.tabs > base + 1
|
435
530
|
|
436
531
|
nodes.last.children, i = tree(arr, i)
|
@@ -444,6 +539,7 @@ MSG
|
|
444
539
|
|
445
540
|
def build_tree(parent, line, root = false)
|
446
541
|
@line = line.index
|
542
|
+
@offset = line.offset
|
447
543
|
node_or_nodes = parse_line(parent, line, root)
|
448
544
|
|
449
545
|
Array(node_or_nodes).each do |node|
|
@@ -482,11 +578,13 @@ MSG
|
|
482
578
|
continued_rule = nil
|
483
579
|
end
|
484
580
|
|
485
|
-
if child.is_a?(Tree::CommentNode) && child.silent
|
581
|
+
if child.is_a?(Tree::CommentNode) && child.type == :silent
|
486
582
|
if continued_comment &&
|
487
583
|
child.line == continued_comment.line +
|
488
|
-
continued_comment.
|
489
|
-
continued_comment.value
|
584
|
+
continued_comment.lines + 1
|
585
|
+
continued_comment.value.last.sub!(/ \*\/\Z/, '')
|
586
|
+
child.value.first.gsub!(/\A\/\*/, ' *')
|
587
|
+
continued_comment.value += ["\n"] + child.value
|
490
588
|
next
|
491
589
|
end
|
492
590
|
|
@@ -527,26 +625,39 @@ WARNING
|
|
527
625
|
# which begin with ::,
|
528
626
|
# as well as pseudo-classes
|
529
627
|
# if we're using the new property syntax
|
530
|
-
Tree::RuleNode.new(parse_interp(line.text))
|
628
|
+
Tree::RuleNode.new(parse_interp(line.text), full_line_range(line))
|
531
629
|
else
|
630
|
+
name_start_offset = line.offset + 1 # +1 for the leading ':'
|
532
631
|
name, value = line.text.scan(PROPERTY_OLD)[0]
|
533
632
|
raise SyntaxError.new("Invalid property: \"#{line.text}\".",
|
534
633
|
:line => @line) if name.nil? || value.nil?
|
535
|
-
|
634
|
+
|
635
|
+
value_start_offset = name_end_offset = name_start_offset + name.length
|
636
|
+
unless value.empty?
|
637
|
+
# +1 and -1 both compensate for the leading ':', which is part of line.text
|
638
|
+
value_start_offset = name_start_offset + line.text.index(value, name.length + 1) - 1
|
639
|
+
end
|
640
|
+
|
641
|
+
property = parse_property(name, parse_interp(name), value, :old, line, value_start_offset)
|
642
|
+
property.name_source_range = Sass::Source::Range.new(
|
643
|
+
Sass::Source::Position.new(@line, to_parser_offset(name_start_offset)),
|
644
|
+
Sass::Source::Position.new(@line, to_parser_offset(name_end_offset)),
|
645
|
+
@options[:filename], @options[:importer])
|
646
|
+
property
|
536
647
|
end
|
537
648
|
when ?$
|
538
649
|
parse_variable(line)
|
539
650
|
when COMMENT_CHAR
|
540
|
-
parse_comment(line
|
651
|
+
parse_comment(line)
|
541
652
|
when DIRECTIVE_CHAR
|
542
653
|
parse_directive(parent, line, root)
|
543
654
|
when ESCAPE_CHAR
|
544
|
-
Tree::RuleNode.new(parse_interp(line.text[1..-1]))
|
655
|
+
Tree::RuleNode.new(parse_interp(line.text[1..-1]), full_line_range(line))
|
545
656
|
when MIXIN_DEFINITION_CHAR
|
546
657
|
parse_mixin_definition(line)
|
547
658
|
when MIXIN_INCLUDE_CHAR
|
548
659
|
if line.text[1].nil? || line.text[1] == ?\s
|
549
|
-
Tree::RuleNode.new(parse_interp(line.text))
|
660
|
+
Tree::RuleNode.new(parse_interp(line.text), full_line_range(line))
|
550
661
|
else
|
551
662
|
parse_mixin_include(line, root)
|
552
663
|
end
|
@@ -556,140 +667,266 @@ WARNING
|
|
556
667
|
end
|
557
668
|
|
558
669
|
def parse_property_or_rule(line)
|
559
|
-
scanner =
|
670
|
+
scanner = Sass::Util::MultibyteStringScanner.new(line.text)
|
560
671
|
hack_char = scanner.scan(/[:\*\.]|\#(?!\{)/)
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
672
|
+
offset = line.offset
|
673
|
+
offset += hack_char.length if hack_char
|
674
|
+
parser = Sass::SCSS::Parser.new(scanner,
|
675
|
+
@options[:filename], @options[:importer],
|
676
|
+
@line, to_parser_offset(offset))
|
677
|
+
|
678
|
+
unless (res = parser.parse_interp_ident)
|
679
|
+
parsed = parse_interp(line.text, line.offset)
|
680
|
+
return Tree::RuleNode.new(parsed, full_line_range(line))
|
565
681
|
end
|
682
|
+
|
683
|
+
ident_range = Sass::Source::Range.new(
|
684
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset)),
|
685
|
+
Sass::Source::Position.new(@line, parser.offset),
|
686
|
+
@options[:filename], @options[:importer])
|
687
|
+
offset = parser.offset - 1
|
566
688
|
res.unshift(hack_char) if hack_char
|
567
|
-
|
689
|
+
|
690
|
+
# Handle comments after a property name but before the colon.
|
691
|
+
if (comment = scanner.scan(Sass::SCSS::RX::COMMENT))
|
568
692
|
res << comment
|
693
|
+
offset += comment.length
|
569
694
|
end
|
570
695
|
|
571
696
|
name = line.text[0...scanner.pos]
|
572
|
-
if scanner.scan(/\s*:(?:\s
|
573
|
-
|
697
|
+
if (scanned = scanner.scan(/\s*:(?:\s+|$)/)) # test for a property
|
698
|
+
offset += scanned.length
|
699
|
+
property = parse_property(name, res, scanner.rest, :new, line, offset)
|
700
|
+
property.name_source_range = ident_range
|
701
|
+
property
|
574
702
|
else
|
575
703
|
res.pop if comment
|
576
|
-
|
704
|
+
|
705
|
+
if (trailing = (scanner.scan(/\s*#{Sass::SCSS::RX::COMMENT}/) ||
|
706
|
+
scanner.scan(/\s*#{Sass::SCSS::RX::SINGLE_LINE_COMMENT}/)))
|
707
|
+
trailing.strip!
|
708
|
+
end
|
709
|
+
interp_parsed = parse_interp(scanner.rest)
|
710
|
+
selector_range = Sass::Source::Range.new(
|
711
|
+
ident_range.start_pos,
|
712
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
|
713
|
+
@options[:filename], @options[:importer])
|
714
|
+
rule = Tree::RuleNode.new(res + interp_parsed, selector_range)
|
715
|
+
rule << Tree::CommentNode.new([trailing], :silent) if trailing
|
716
|
+
rule
|
577
717
|
end
|
578
718
|
end
|
579
719
|
|
580
|
-
|
720
|
+
# @comment
|
721
|
+
# rubocop:disable ParameterLists
|
722
|
+
def parse_property(name, parsed_name, value, prop, line, start_offset)
|
723
|
+
# rubocop:enable ParameterLists
|
581
724
|
if value.strip.empty?
|
582
|
-
expr = Sass::Script::String.new("")
|
725
|
+
expr = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
|
726
|
+
end_offset = start_offset
|
583
727
|
else
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
728
|
+
expr = parse_script(value, :offset => to_parser_offset(start_offset))
|
729
|
+
end_offset = expr.source_range.end_pos.offset - 1
|
730
|
+
end
|
731
|
+
node = Tree::PropNode.new(parse_interp(name), expr, prop)
|
732
|
+
node.value_source_range = Sass::Source::Range.new(
|
733
|
+
Sass::Source::Position.new(line.index, to_parser_offset(start_offset)),
|
734
|
+
Sass::Source::Position.new(line.index, to_parser_offset(end_offset)),
|
735
|
+
@options[:filename], @options[:importer])
|
736
|
+
if value.strip.empty? && line.children.empty?
|
737
|
+
raise SyntaxError.new(
|
738
|
+
"Invalid property: \"#{node.declaration}\" (no value)." +
|
739
|
+
node.pseudo_class_selector_message)
|
591
740
|
end
|
592
|
-
|
741
|
+
|
742
|
+
node
|
593
743
|
end
|
594
744
|
|
595
745
|
def parse_variable(line)
|
596
|
-
name, value,
|
746
|
+
name, value, flags = line.text.scan(Script::MATCH)[0]
|
597
747
|
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
|
598
748
|
:line => @line + 1) unless line.children.empty?
|
599
749
|
raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
|
600
750
|
:line => @line) unless name && value
|
751
|
+
flags = flags ? flags.split(/\s+/) : []
|
752
|
+
if (invalid_flag = flags.find {|f| f != '!default' && f != '!global'})
|
753
|
+
raise SyntaxError.new("Invalid flag \"#{invalid_flag}\".", :line => @line)
|
754
|
+
end
|
601
755
|
|
602
|
-
|
756
|
+
# This workaround is needed for the case when the variable value is part of the identifier,
|
757
|
+
# otherwise we end up with the offset equal to the value index inside the name:
|
758
|
+
# $red_color: red;
|
759
|
+
var_lhs_length = 1 + name.length # 1 stands for '$'
|
760
|
+
index = line.text.index(value, line.offset + var_lhs_length) || 0
|
761
|
+
expr = parse_script(value, :offset => to_parser_offset(line.offset + index))
|
603
762
|
|
604
|
-
Tree::VariableNode.new(name, expr, default)
|
763
|
+
Tree::VariableNode.new(name, expr, flags.include?('!default'), flags.include?('!global'))
|
605
764
|
end
|
606
765
|
|
607
766
|
def parse_comment(line)
|
608
|
-
if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
|
609
|
-
silent = line[1] == SASS_COMMENT_CHAR
|
610
|
-
|
611
|
-
|
612
|
-
|
767
|
+
if line.text[1] == CSS_COMMENT_CHAR || line.text[1] == SASS_COMMENT_CHAR
|
768
|
+
silent = line.text[1] == SASS_COMMENT_CHAR
|
769
|
+
loud = !silent && line.text[2] == SASS_LOUD_COMMENT_CHAR
|
770
|
+
if silent
|
771
|
+
value = [line.text]
|
772
|
+
else
|
773
|
+
value = self.class.parse_interp(
|
774
|
+
line.text, line.index, to_parser_offset(line.offset), :filename => @filename)
|
775
|
+
end
|
776
|
+
value = Sass::Util.with_extracted_values(value) do |str|
|
777
|
+
str = str.gsub(/^#{line.comment_tab_str}/m, '')[2..-1] # get rid of // or /*
|
778
|
+
format_comment_text(str, silent)
|
779
|
+
end
|
780
|
+
type = if silent
|
781
|
+
:silent
|
782
|
+
elsif loud
|
783
|
+
:loud
|
784
|
+
else
|
785
|
+
:normal
|
786
|
+
end
|
787
|
+
Tree::CommentNode.new(value, type)
|
613
788
|
else
|
614
|
-
Tree::RuleNode.new(parse_interp(line))
|
789
|
+
Tree::RuleNode.new(parse_interp(line.text), full_line_range(line))
|
615
790
|
end
|
616
791
|
end
|
617
792
|
|
793
|
+
DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for,
|
794
|
+
:each, :while, :if, :else, :extend, :import, :media, :charset, :content,
|
795
|
+
:at_root]
|
796
|
+
|
797
|
+
# @comment
|
798
|
+
# rubocop:disable MethodLength
|
618
799
|
def parse_directive(parent, line, root)
|
619
800
|
directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
|
801
|
+
raise SyntaxError.new("Invalid directive: '@'.") unless directive
|
620
802
|
offset = directive.size + whitespace.size + 1 if whitespace
|
621
803
|
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
804
|
+
directive_name = directive.gsub('-', '_').to_sym
|
805
|
+
if DIRECTIVES.include?(directive_name)
|
806
|
+
return send("parse_#{directive_name}_directive", parent, line, root, value, offset)
|
807
|
+
end
|
808
|
+
|
809
|
+
unprefixed_directive = directive.gsub(/^-[a-z0-9]+-/i, '')
|
810
|
+
if unprefixed_directive == 'supports'
|
811
|
+
parser = Sass::SCSS::Parser.new(value, @options[:filename], @line)
|
812
|
+
return Tree::SupportsNode.new(directive, parser.parse_supports_condition)
|
813
|
+
end
|
814
|
+
|
815
|
+
Tree::DirectiveNode.new(
|
816
|
+
value.nil? ? ["@#{directive}"] : ["@#{directive} "] + parse_interp(value, offset))
|
817
|
+
end
|
818
|
+
|
819
|
+
def parse_while_directive(parent, line, root, value, offset)
|
820
|
+
raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
|
821
|
+
Tree::WhileNode.new(parse_script(value, :offset => offset))
|
822
|
+
end
|
823
|
+
|
824
|
+
def parse_if_directive(parent, line, root, value, offset)
|
825
|
+
raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
|
826
|
+
Tree::IfNode.new(parse_script(value, :offset => offset))
|
827
|
+
end
|
828
|
+
|
829
|
+
def parse_debug_directive(parent, line, root, value, offset)
|
830
|
+
raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
|
831
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
|
832
|
+
:line => @line + 1) unless line.children.empty?
|
833
|
+
offset = line.offset + line.text.index(value).to_i
|
834
|
+
Tree::DebugNode.new(parse_script(value, :offset => offset))
|
835
|
+
end
|
836
|
+
|
837
|
+
def parse_extend_directive(parent, line, root, value, offset)
|
838
|
+
raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
|
839
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
|
840
|
+
:line => @line + 1) unless line.children.empty?
|
841
|
+
optional = !!value.gsub!(/\s+#{Sass::SCSS::RX::OPTIONAL}$/, '')
|
842
|
+
offset = line.offset + line.text.index(value).to_i
|
843
|
+
interp_parsed = parse_interp(value, offset)
|
844
|
+
selector_range = Sass::Source::Range.new(
|
845
|
+
Sass::Source::Position.new(@line, to_parser_offset(offset)),
|
846
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
|
847
|
+
@options[:filename], @options[:importer]
|
848
|
+
)
|
849
|
+
Tree::ExtendNode.new(interp_parsed, optional, selector_range)
|
850
|
+
end
|
851
|
+
# @comment
|
852
|
+
# rubocop:enable MethodLength
|
853
|
+
|
854
|
+
def parse_warn_directive(parent, line, root, value, offset)
|
855
|
+
raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
|
856
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
|
857
|
+
:line => @line + 1) unless line.children.empty?
|
858
|
+
offset = line.offset + line.text.index(value).to_i
|
859
|
+
Tree::WarnNode.new(parse_script(value, :offset => offset))
|
860
|
+
end
|
861
|
+
|
862
|
+
def parse_return_directive(parent, line, root, value, offset)
|
863
|
+
raise SyntaxError.new("Invalid @return: expected expression.") unless value
|
864
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath return directives.",
|
865
|
+
:line => @line + 1) unless line.children.empty?
|
866
|
+
offset = line.offset + line.text.index(value).to_i
|
867
|
+
Tree::ReturnNode.new(parse_script(value, :offset => offset))
|
868
|
+
end
|
869
|
+
|
870
|
+
def parse_charset_directive(parent, line, root, value, offset)
|
871
|
+
name = value && value[/\A(["'])(.*)\1\Z/, 2] # "
|
872
|
+
raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name
|
873
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath charset directives.",
|
874
|
+
:line => @line + 1) unless line.children.empty?
|
875
|
+
Tree::CharsetNode.new(name)
|
876
|
+
end
|
877
|
+
|
878
|
+
def parse_media_directive(parent, line, root, value, offset)
|
879
|
+
parser = Sass::SCSS::Parser.new(value,
|
880
|
+
@options[:filename], @options[:importer],
|
881
|
+
@line, to_parser_offset(@offset))
|
882
|
+
offset = line.offset + line.text.index('media').to_i - 1
|
883
|
+
parsed_media_query_list = parser.parse_media_query_list.to_a
|
884
|
+
node = Tree::MediaNode.new(parsed_media_query_list)
|
885
|
+
node.source_range = Sass::Source::Range.new(
|
886
|
+
Sass::Source::Position.new(@line, to_parser_offset(offset)),
|
887
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
|
888
|
+
@options[:filename], @options[:importer])
|
889
|
+
node
|
890
|
+
end
|
891
|
+
|
892
|
+
def parse_at_root_directive(parent, line, root, value, offset)
|
893
|
+
return Sass::Tree::AtRootNode.new unless value
|
894
|
+
|
895
|
+
if value.start_with?('(')
|
896
|
+
parser = Sass::SCSS::Parser.new(value,
|
897
|
+
@options[:filename], @options[:importer],
|
898
|
+
@line, to_parser_offset(@offset))
|
899
|
+
offset = line.offset + line.text.index('at-root').to_i - 1
|
900
|
+
return Tree::AtRootNode.new(parser.parse_at_root_query)
|
678
901
|
end
|
902
|
+
|
903
|
+
at_root_node = Tree::AtRootNode.new
|
904
|
+
parsed = parse_interp(value, offset)
|
905
|
+
rule_node = Tree::RuleNode.new(parsed, full_line_range(line))
|
906
|
+
|
907
|
+
# The caller expects to automatically add children to the returned node
|
908
|
+
# and we want it to add children to the rule node instead, so we
|
909
|
+
# manually handle the wiring here and return nil so the caller doesn't
|
910
|
+
# duplicate our efforts.
|
911
|
+
append_children(rule_node, line.children, false)
|
912
|
+
at_root_node << rule_node
|
913
|
+
parent << at_root_node
|
914
|
+
nil
|
679
915
|
end
|
680
916
|
|
681
|
-
def
|
682
|
-
var, from_expr, to_name, to_expr =
|
917
|
+
def parse_for_directive(parent, line, root, value, offset)
|
918
|
+
var, from_expr, to_name, to_expr =
|
919
|
+
value.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
|
683
920
|
|
684
921
|
if var.nil? # scan failed, try to figure out why for error message
|
685
|
-
if
|
922
|
+
if value !~ /^[^\s]+/
|
686
923
|
expected = "variable name"
|
687
|
-
elsif
|
924
|
+
elsif value !~ /^[^\s]+\s+from\s+.+/
|
688
925
|
expected = "'from <expr>'"
|
689
926
|
else
|
690
927
|
expected = "'to <expr>' or 'through <expr>'"
|
691
928
|
end
|
692
|
-
raise SyntaxError.new("Invalid for directive '@for #{
|
929
|
+
raise SyntaxError.new("Invalid for directive '@for #{value}': expected #{expected}.")
|
693
930
|
end
|
694
931
|
raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
|
695
932
|
|
@@ -699,31 +936,35 @@ WARNING
|
|
699
936
|
Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
|
700
937
|
end
|
701
938
|
|
702
|
-
def
|
703
|
-
|
939
|
+
def parse_each_directive(parent, line, root, value, offset)
|
940
|
+
vars, list_expr = value.scan(/^([^\s]+(?:\s*,\s*[^\s]+)*)\s+in\s+(.+)$/).first
|
704
941
|
|
705
|
-
if
|
706
|
-
if
|
942
|
+
if vars.nil? # scan failed, try to figure out why for error message
|
943
|
+
if value !~ /^[^\s]+/
|
707
944
|
expected = "variable name"
|
708
|
-
elsif
|
945
|
+
elsif value !~ /^[^\s]+(?:\s*,\s*[^\s]+)*[^\s]+\s+from\s+.+/
|
709
946
|
expected = "'in <expr>'"
|
710
947
|
end
|
711
|
-
raise SyntaxError.new("Invalid
|
948
|
+
raise SyntaxError.new("Invalid each directive '@each #{value}': expected #{expected}.")
|
949
|
+
end
|
950
|
+
|
951
|
+
vars = vars.split(',').map do |var|
|
952
|
+
var.strip!
|
953
|
+
raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
|
954
|
+
var[1..-1]
|
712
955
|
end
|
713
|
-
raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
|
714
956
|
|
715
|
-
var = var[1..-1]
|
716
957
|
parsed_list = parse_script(list_expr, :offset => line.offset + line.text.index(list_expr))
|
717
|
-
Tree::EachNode.new(
|
958
|
+
Tree::EachNode.new(vars, parsed_list)
|
718
959
|
end
|
719
960
|
|
720
|
-
def
|
961
|
+
def parse_else_directive(parent, line, root, value, offset)
|
721
962
|
previous = parent.children.last
|
722
963
|
raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
|
723
964
|
|
724
|
-
if
|
725
|
-
if
|
726
|
-
raise SyntaxError.new("Invalid else directive '@else #{
|
965
|
+
if value
|
966
|
+
if value !~ /^if\s+(.+)/
|
967
|
+
raise SyntaxError.new("Invalid else directive '@else #{value}': expected 'if <expr>'.")
|
727
968
|
end
|
728
969
|
expr = parse_script($1, :offset => line.offset + line.text.index($1))
|
729
970
|
end
|
@@ -734,43 +975,102 @@ WARNING
|
|
734
975
|
nil
|
735
976
|
end
|
736
977
|
|
737
|
-
def
|
978
|
+
def parse_import_directive(parent, line, root, value, offset)
|
738
979
|
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
|
739
980
|
:line => @line + 1) unless line.children.empty?
|
740
981
|
|
741
|
-
scanner =
|
982
|
+
scanner = Sass::Util::MultibyteStringScanner.new(value)
|
742
983
|
values = []
|
743
984
|
|
744
985
|
loop do
|
745
|
-
unless node = parse_import_arg(scanner)
|
746
|
-
raise SyntaxError.new(
|
986
|
+
unless (node = parse_import_arg(scanner, offset + scanner.pos))
|
987
|
+
raise SyntaxError.new(
|
988
|
+
"Invalid @import: expected file to import, was #{scanner.rest.inspect}",
|
747
989
|
:line => @line)
|
748
990
|
end
|
749
991
|
values << node
|
750
992
|
break unless scanner.scan(/,\s*/)
|
751
993
|
end
|
752
994
|
|
753
|
-
|
995
|
+
if scanner.scan(/;/)
|
996
|
+
raise SyntaxError.new("Invalid @import: expected end of line, was \";\".",
|
997
|
+
:line => @line)
|
998
|
+
end
|
999
|
+
|
1000
|
+
values
|
754
1001
|
end
|
755
1002
|
|
756
|
-
|
1003
|
+
# @comment
|
1004
|
+
# rubocop:disable MethodLength
|
1005
|
+
def parse_import_arg(scanner, offset)
|
757
1006
|
return if scanner.eos?
|
758
|
-
|
759
|
-
|
760
|
-
|
1007
|
+
|
1008
|
+
if scanner.match?(/url\(/i)
|
1009
|
+
script_parser = Sass::Script::Parser.new(scanner, @line, to_parser_offset(offset), @options)
|
1010
|
+
str = script_parser.parse_string
|
1011
|
+
|
1012
|
+
media_parser = Sass::SCSS::Parser.new(scanner,
|
1013
|
+
@options[:filename], @options[:importer],
|
1014
|
+
@line, str.source_range.end_pos.offset)
|
1015
|
+
if (media = media_parser.parse_media_query_list)
|
1016
|
+
end_pos = Sass::Source::Position.new(@line, media_parser.offset + 1)
|
1017
|
+
node = Tree::CssImportNode.new(str, media.to_a)
|
1018
|
+
else
|
1019
|
+
end_pos = str.source_range.end_pos
|
1020
|
+
node = Tree::CssImportNode.new(str)
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
node.source_range = Sass::Source::Range.new(
|
1024
|
+
str.source_range.start_pos, end_pos,
|
1025
|
+
@options[:filename], @options[:importer])
|
1026
|
+
return node
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
unless (str = scanner.scan(Sass::SCSS::RX::STRING))
|
1030
|
+
scanned = scanner.scan(/[^,;]+/)
|
1031
|
+
node = Tree::ImportNode.new(scanned)
|
1032
|
+
start_parser_offset = to_parser_offset(offset)
|
1033
|
+
node.source_range = Sass::Source::Range.new(
|
1034
|
+
Sass::Source::Position.new(@line, start_parser_offset),
|
1035
|
+
Sass::Source::Position.new(@line, start_parser_offset + scanned.length),
|
1036
|
+
@options[:filename], @options[:importer])
|
1037
|
+
return node
|
761
1038
|
end
|
762
1039
|
|
1040
|
+
start_offset = offset
|
1041
|
+
offset += str.length
|
763
1042
|
val = scanner[1] || scanner[2]
|
764
|
-
scanner.scan(/\s*/)
|
765
|
-
if
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
Tree::
|
1043
|
+
scanned = scanner.scan(/\s*/)
|
1044
|
+
if !scanner.match?(/[,;]|$/)
|
1045
|
+
offset += scanned.length if scanned
|
1046
|
+
media_parser = Sass::SCSS::Parser.new(scanner,
|
1047
|
+
@options[:filename], @options[:importer], @line, offset)
|
1048
|
+
media = media_parser.parse_media_query_list
|
1049
|
+
node = Tree::CssImportNode.new(str || uri, media.to_a)
|
1050
|
+
node.source_range = Sass::Source::Range.new(
|
1051
|
+
Sass::Source::Position.new(@line, to_parser_offset(start_offset)),
|
1052
|
+
Sass::Source::Position.new(@line, media_parser.offset),
|
1053
|
+
@options[:filename], @options[:importer])
|
1054
|
+
elsif val =~ %r{^(https?:)?//}
|
1055
|
+
node = Tree::CssImportNode.new("url(#{val})")
|
1056
|
+
node.source_range = Sass::Source::Range.new(
|
1057
|
+
Sass::Source::Position.new(@line, to_parser_offset(start_offset)),
|
1058
|
+
Sass::Source::Position.new(@line, to_parser_offset(offset)),
|
1059
|
+
@options[:filename], @options[:importer])
|
771
1060
|
else
|
772
|
-
Tree::ImportNode.new(val)
|
1061
|
+
node = Tree::ImportNode.new(val)
|
1062
|
+
node.source_range = Sass::Source::Range.new(
|
1063
|
+
Sass::Source::Position.new(@line, to_parser_offset(start_offset)),
|
1064
|
+
Sass::Source::Position.new(@line, to_parser_offset(offset)),
|
1065
|
+
@options[:filename], @options[:importer])
|
773
1066
|
end
|
1067
|
+
node
|
1068
|
+
end
|
1069
|
+
# @comment
|
1070
|
+
# rubocop:enable MethodLength
|
1071
|
+
|
1072
|
+
def parse_mixin_directive(parent, line, root, value, offset)
|
1073
|
+
parse_mixin_definition(line)
|
774
1074
|
end
|
775
1075
|
|
776
1076
|
MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
|
@@ -779,9 +1079,25 @@ WARNING
|
|
779
1079
|
raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
|
780
1080
|
|
781
1081
|
offset = line.offset + line.text.size - arg_string.size
|
782
|
-
args = Script::Parser.new(arg_string.strip, @line, offset, @options).
|
1082
|
+
args, splat = Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options).
|
783
1083
|
parse_mixin_definition_arglist
|
784
|
-
Tree::MixinDefNode.new(name, args)
|
1084
|
+
Tree::MixinDefNode.new(name, args, splat)
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
CONTENT_RE = /^@content\s*(.+)?$/
|
1088
|
+
def parse_content_directive(parent, line, root, value, offset)
|
1089
|
+
trailing = line.text.scan(CONTENT_RE).first.first
|
1090
|
+
unless trailing.nil?
|
1091
|
+
raise SyntaxError.new(
|
1092
|
+
"Invalid content directive. Trailing characters found: \"#{trailing}\".")
|
1093
|
+
end
|
1094
|
+
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath @content directives.",
|
1095
|
+
:line => line.index + 1) unless line.children.empty?
|
1096
|
+
Tree::ContentNode.new
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
def parse_include_directive(parent, line, root, value, offset)
|
1100
|
+
parse_mixin_include(line, root)
|
785
1101
|
end
|
786
1102
|
|
787
1103
|
MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
|
@@ -790,27 +1106,26 @@ WARNING
|
|
790
1106
|
raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
|
791
1107
|
|
792
1108
|
offset = line.offset + line.text.size - arg_string.size
|
793
|
-
args, keywords
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
Tree::MixinNode.new(name, args, keywords)
|
1109
|
+
args, keywords, splat, kwarg_splat =
|
1110
|
+
Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options).
|
1111
|
+
parse_mixin_include_arglist
|
1112
|
+
Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat)
|
798
1113
|
end
|
799
1114
|
|
800
1115
|
FUNCTION_RE = /^@function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
|
801
|
-
def
|
1116
|
+
def parse_function_directive(parent, line, root, value, offset)
|
802
1117
|
name, arg_string = line.text.scan(FUNCTION_RE).first
|
803
1118
|
raise SyntaxError.new("Invalid function definition \"#{line.text}\".") if name.nil?
|
804
1119
|
|
805
1120
|
offset = line.offset + line.text.size - arg_string.size
|
806
|
-
args = Script::Parser.new(arg_string.strip, @line, offset, @options).
|
1121
|
+
args, splat = Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options).
|
807
1122
|
parse_function_definition_arglist
|
808
|
-
Tree::FunctionNode.new(name, args)
|
1123
|
+
Tree::FunctionNode.new(name, args, splat)
|
809
1124
|
end
|
810
1125
|
|
811
1126
|
def parse_script(script, options = {})
|
812
1127
|
line = options[:line] || @line
|
813
|
-
offset = options[:offset] ||
|
1128
|
+
offset = options[:offset] || @offset + 1
|
814
1129
|
Script.parse(script, line, offset, @options)
|
815
1130
|
end
|
816
1131
|
|
@@ -822,12 +1137,12 @@ WARNING
|
|
822
1137
|
content.shift
|
823
1138
|
end
|
824
1139
|
|
825
|
-
return
|
826
|
-
content.last.gsub!(
|
1140
|
+
return "/* */" if content.empty?
|
1141
|
+
content.last.gsub!(/ ?\*\/ *$/, '')
|
827
1142
|
content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
|
828
1143
|
content.first.gsub!(/^ /, '') unless removed_first
|
829
1144
|
if silent
|
830
|
-
"
|
1145
|
+
"/*" + content.join("\n *") + " */"
|
831
1146
|
else
|
832
1147
|
# The #gsub fixes the case of a trailing */
|
833
1148
|
"/*" + content.join("\n *").gsub(/ \*\Z/, '') + " */"
|
@@ -838,8 +1153,20 @@ WARNING
|
|
838
1153
|
self.class.parse_interp(text, @line, offset, :filename => @filename)
|
839
1154
|
end
|
840
1155
|
|
1156
|
+
# Parser tracks 1-based line and offset, so our offset should be converted.
|
1157
|
+
def to_parser_offset(offset)
|
1158
|
+
offset + 1
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
def full_line_range(line)
|
1162
|
+
Sass::Source::Range.new(
|
1163
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset)),
|
1164
|
+
Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length),
|
1165
|
+
@options[:filename], @options[:importer])
|
1166
|
+
end
|
1167
|
+
|
841
1168
|
# It's important that this have strings (at least)
|
842
|
-
# at the beginning, the end, and between each Script::Node.
|
1169
|
+
# at the beginning, the end, and between each Script::Tree::Node.
|
843
1170
|
#
|
844
1171
|
# @private
|
845
1172
|
def self.parse_interp(text, line, offset, options)
|
@@ -847,12 +1174,13 @@ WARNING
|
|
847
1174
|
rest = Sass::Shared.handle_interpolation text do |scan|
|
848
1175
|
escapes = scan[2].size
|
849
1176
|
res << scan.matched[0...-2 - escapes]
|
850
|
-
if escapes
|
1177
|
+
if escapes.odd?
|
851
1178
|
res << "\\" * (escapes - 1) << '#{'
|
852
1179
|
else
|
853
1180
|
res << "\\" * [0, escapes - 1].max
|
1181
|
+
# Add 1 to emulate to_parser_offset.
|
854
1182
|
res << Script::Parser.new(
|
855
|
-
scan, line, offset + scan.pos - scan.matched_size, options).
|
1183
|
+
scan, line, offset + scan.pos - scan.matched_size + 1, options).
|
856
1184
|
parse_interpolated
|
857
1185
|
end
|
858
1186
|
end
|