sass 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|