sass4 4.0.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/.yardopts +13 -0
- data/AGENTS.md +534 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/CONTRIBUTING.md +148 -0
- data/MIT-LICENSE +20 -0
- data/README.md +242 -0
- data/VERSION +1 -0
- data/VERSION_NAME +1 -0
- data/bin/sass +13 -0
- data/bin/sass-convert +12 -0
- data/bin/scss +13 -0
- data/extra/sass-spec-ref.sh +40 -0
- data/extra/update_watch.rb +13 -0
- data/init.rb +18 -0
- data/lib/sass/cache_stores/base.rb +88 -0
- data/lib/sass/cache_stores/chain.rb +34 -0
- data/lib/sass/cache_stores/filesystem.rb +60 -0
- data/lib/sass/cache_stores/memory.rb +46 -0
- data/lib/sass/cache_stores/null.rb +25 -0
- data/lib/sass/cache_stores.rb +15 -0
- data/lib/sass/callbacks.rb +67 -0
- data/lib/sass/css.rb +407 -0
- data/lib/sass/deprecation.rb +55 -0
- data/lib/sass/engine.rb +1236 -0
- data/lib/sass/environment.rb +236 -0
- data/lib/sass/error.rb +198 -0
- data/lib/sass/exec/base.rb +188 -0
- data/lib/sass/exec/sass_convert.rb +283 -0
- data/lib/sass/exec/sass_scss.rb +436 -0
- data/lib/sass/exec.rb +9 -0
- data/lib/sass/features.rb +48 -0
- data/lib/sass/importers/base.rb +182 -0
- data/lib/sass/importers/deprecated_path.rb +51 -0
- data/lib/sass/importers/filesystem.rb +221 -0
- data/lib/sass/importers.rb +23 -0
- data/lib/sass/logger/base.rb +47 -0
- data/lib/sass/logger/delayed.rb +50 -0
- data/lib/sass/logger/log_level.rb +45 -0
- data/lib/sass/logger.rb +17 -0
- data/lib/sass/media.rb +210 -0
- data/lib/sass/plugin/compiler.rb +552 -0
- data/lib/sass/plugin/configuration.rb +134 -0
- data/lib/sass/plugin/generic.rb +15 -0
- data/lib/sass/plugin/merb.rb +48 -0
- data/lib/sass/plugin/rack.rb +60 -0
- data/lib/sass/plugin/rails.rb +47 -0
- data/lib/sass/plugin/staleness_checker.rb +199 -0
- data/lib/sass/plugin.rb +134 -0
- data/lib/sass/railtie.rb +10 -0
- data/lib/sass/repl.rb +57 -0
- data/lib/sass/root.rb +7 -0
- data/lib/sass/script/css_lexer.rb +33 -0
- data/lib/sass/script/css_parser.rb +36 -0
- data/lib/sass/script/functions.rb +3103 -0
- data/lib/sass/script/lexer.rb +518 -0
- data/lib/sass/script/parser.rb +1164 -0
- data/lib/sass/script/tree/funcall.rb +314 -0
- data/lib/sass/script/tree/interpolation.rb +220 -0
- data/lib/sass/script/tree/list_literal.rb +119 -0
- data/lib/sass/script/tree/literal.rb +49 -0
- data/lib/sass/script/tree/map_literal.rb +64 -0
- data/lib/sass/script/tree/node.rb +119 -0
- data/lib/sass/script/tree/operation.rb +149 -0
- data/lib/sass/script/tree/selector.rb +26 -0
- data/lib/sass/script/tree/string_interpolation.rb +125 -0
- data/lib/sass/script/tree/unary_operation.rb +69 -0
- data/lib/sass/script/tree/variable.rb +57 -0
- data/lib/sass/script/tree.rb +16 -0
- data/lib/sass/script/value/arg_list.rb +36 -0
- data/lib/sass/script/value/base.rb +258 -0
- data/lib/sass/script/value/bool.rb +35 -0
- data/lib/sass/script/value/callable.rb +25 -0
- data/lib/sass/script/value/color.rb +704 -0
- data/lib/sass/script/value/function.rb +19 -0
- data/lib/sass/script/value/helpers.rb +298 -0
- data/lib/sass/script/value/list.rb +135 -0
- data/lib/sass/script/value/map.rb +70 -0
- data/lib/sass/script/value/null.rb +44 -0
- data/lib/sass/script/value/number.rb +564 -0
- data/lib/sass/script/value/string.rb +138 -0
- data/lib/sass/script/value.rb +13 -0
- data/lib/sass/script.rb +66 -0
- data/lib/sass/scss/css_parser.rb +61 -0
- data/lib/sass/scss/parser.rb +1343 -0
- data/lib/sass/scss/rx.rb +134 -0
- data/lib/sass/scss/static_parser.rb +351 -0
- data/lib/sass/scss.rb +14 -0
- data/lib/sass/selector/abstract_sequence.rb +112 -0
- data/lib/sass/selector/comma_sequence.rb +195 -0
- data/lib/sass/selector/pseudo.rb +291 -0
- data/lib/sass/selector/sequence.rb +661 -0
- data/lib/sass/selector/simple.rb +124 -0
- data/lib/sass/selector/simple_sequence.rb +348 -0
- data/lib/sass/selector.rb +327 -0
- data/lib/sass/shared.rb +76 -0
- data/lib/sass/source/map.rb +209 -0
- data/lib/sass/source/position.rb +39 -0
- data/lib/sass/source/range.rb +41 -0
- data/lib/sass/stack.rb +140 -0
- data/lib/sass/supports.rb +225 -0
- data/lib/sass/tree/at_root_node.rb +83 -0
- data/lib/sass/tree/charset_node.rb +22 -0
- data/lib/sass/tree/comment_node.rb +82 -0
- data/lib/sass/tree/content_node.rb +9 -0
- data/lib/sass/tree/css_import_node.rb +68 -0
- data/lib/sass/tree/debug_node.rb +18 -0
- data/lib/sass/tree/directive_node.rb +59 -0
- data/lib/sass/tree/each_node.rb +24 -0
- data/lib/sass/tree/error_node.rb +18 -0
- data/lib/sass/tree/extend_node.rb +43 -0
- data/lib/sass/tree/for_node.rb +36 -0
- data/lib/sass/tree/function_node.rb +44 -0
- data/lib/sass/tree/if_node.rb +52 -0
- data/lib/sass/tree/import_node.rb +75 -0
- data/lib/sass/tree/keyframe_rule_node.rb +15 -0
- data/lib/sass/tree/media_node.rb +48 -0
- data/lib/sass/tree/mixin_def_node.rb +38 -0
- data/lib/sass/tree/mixin_node.rb +52 -0
- data/lib/sass/tree/node.rb +240 -0
- data/lib/sass/tree/prop_node.rb +162 -0
- data/lib/sass/tree/return_node.rb +19 -0
- data/lib/sass/tree/root_node.rb +44 -0
- data/lib/sass/tree/rule_node.rb +153 -0
- 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 +36 -0
- data/lib/sass/tree/visitors/base.rb +72 -0
- data/lib/sass/tree/visitors/check_nesting.rb +173 -0
- data/lib/sass/tree/visitors/convert.rb +350 -0
- data/lib/sass/tree/visitors/cssize.rb +362 -0
- data/lib/sass/tree/visitors/deep_copy.rb +107 -0
- data/lib/sass/tree/visitors/extend.rb +64 -0
- data/lib/sass/tree/visitors/perform.rb +572 -0
- data/lib/sass/tree/visitors/set_options.rb +139 -0
- data/lib/sass/tree/visitors/to_css.rb +440 -0
- data/lib/sass/tree/warn_node.rb +18 -0
- data/lib/sass/tree/while_node.rb +18 -0
- data/lib/sass/util/multibyte_string_scanner.rb +151 -0
- data/lib/sass/util/normalized_map.rb +122 -0
- data/lib/sass/util/subset_map.rb +109 -0
- data/lib/sass/util/test.rb +9 -0
- data/lib/sass/util.rb +1137 -0
- data/lib/sass/version.rb +120 -0
- data/lib/sass.rb +102 -0
- data/rails/init.rb +1 -0
- metadata +283 -0
@@ -0,0 +1,572 @@
|
|
1
|
+
# A visitor for converting a dynamic Sass tree into a static Sass tree.
|
2
|
+
class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base
|
3
|
+
@@function_name_deprecation = Sass::Deprecation.new
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# @param root [Tree::Node] The root node of the tree to visit.
|
7
|
+
# @param environment [Sass::Environment] The lexical environment.
|
8
|
+
# @return [Tree::Node] The resulting tree of static nodes.
|
9
|
+
def visit(root, environment = nil)
|
10
|
+
new(environment).send(:visit, root)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @api private
|
14
|
+
def perform_arguments(callable, args, splat, environment)
|
15
|
+
desc = "#{callable.type.capitalize} #{callable.name}"
|
16
|
+
downcase_desc = "#{callable.type} #{callable.name}"
|
17
|
+
|
18
|
+
# All keywords are contained in splat.keywords for consistency,
|
19
|
+
# even if there were no splats passed in.
|
20
|
+
old_keywords_accessed = splat.keywords_accessed
|
21
|
+
keywords = splat.keywords
|
22
|
+
splat.keywords_accessed = old_keywords_accessed
|
23
|
+
|
24
|
+
begin
|
25
|
+
unless keywords.empty?
|
26
|
+
unknown_args = Sass::Util.array_minus(keywords.keys,
|
27
|
+
callable.args.map {|var| var.first.underscored_name})
|
28
|
+
if callable.splat && unknown_args.include?(callable.splat.underscored_name)
|
29
|
+
raise Sass::SyntaxError.new("Argument $#{callable.splat.name} of #{downcase_desc} " +
|
30
|
+
"cannot be used as a named argument.")
|
31
|
+
elsif unknown_args.any?
|
32
|
+
description = unknown_args.length > 1 ? 'the following arguments:' : 'an argument named'
|
33
|
+
raise Sass::SyntaxError.new("#{desc} doesn't have #{description} " +
|
34
|
+
"#{unknown_args.map {|name| "$#{name}"}.join ', '}.")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
rescue Sass::SyntaxError => keyword_exception
|
38
|
+
end
|
39
|
+
|
40
|
+
# If there's no splat, raise the keyword exception immediately. The actual
|
41
|
+
# raising happens in the ensure clause at the end of this function.
|
42
|
+
return if keyword_exception && !callable.splat
|
43
|
+
|
44
|
+
splat_sep = :comma
|
45
|
+
if splat
|
46
|
+
args += splat.to_a
|
47
|
+
splat_sep = splat.separator
|
48
|
+
end
|
49
|
+
|
50
|
+
if args.size > callable.args.size && !callable.splat
|
51
|
+
extra_args_because_of_splat = splat && args.size - splat.to_a.size <= callable.args.size
|
52
|
+
|
53
|
+
takes = callable.args.size
|
54
|
+
passed = args.size
|
55
|
+
message = "#{desc} takes #{takes} argument#{'s' unless takes == 1} " +
|
56
|
+
"but #{passed} #{passed == 1 ? 'was' : 'were'} passed."
|
57
|
+
raise Sass::SyntaxError.new(message) unless extra_args_because_of_splat
|
58
|
+
# TODO: when the deprecation period is over, make this an error.
|
59
|
+
Sass::Util.sass_warn("WARNING: #{message}\n" +
|
60
|
+
environment.stack.to_s.gsub(/^/m, " " * 8) + "\n" +
|
61
|
+
"This will be an error in future versions of Sass.")
|
62
|
+
end
|
63
|
+
|
64
|
+
env = Sass::Environment.new(callable.environment)
|
65
|
+
callable.args.zip(args[0...callable.args.length]) do |(var, default), value|
|
66
|
+
if value && keywords.has_key?(var.name)
|
67
|
+
raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} " +
|
68
|
+
"both by position and by name.")
|
69
|
+
end
|
70
|
+
|
71
|
+
value ||= keywords.delete(var.name)
|
72
|
+
value ||= default && default.perform(env)
|
73
|
+
raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value
|
74
|
+
env.set_local_var(var.name, value)
|
75
|
+
end
|
76
|
+
|
77
|
+
if callable.splat
|
78
|
+
rest = args[callable.args.length..-1] || []
|
79
|
+
arg_list = Sass::Script::Value::ArgList.new(rest, keywords, splat_sep)
|
80
|
+
arg_list.options = env.options
|
81
|
+
env.set_local_var(callable.splat.name, arg_list)
|
82
|
+
end
|
83
|
+
|
84
|
+
yield env
|
85
|
+
rescue StandardError => e
|
86
|
+
ensure
|
87
|
+
# If there's a keyword exception, we don't want to throw it immediately,
|
88
|
+
# because the invalid keywords may be part of a glob argument that should be
|
89
|
+
# passed on to another function. So we only raise it if we reach the end of
|
90
|
+
# this function *and* the keywords attached to the argument list glob object
|
91
|
+
# haven't been accessed.
|
92
|
+
#
|
93
|
+
# The keyword exception takes precedence over any Sass errors, but not over
|
94
|
+
# non-Sass exceptions.
|
95
|
+
if keyword_exception &&
|
96
|
+
!(arg_list && arg_list.keywords_accessed) &&
|
97
|
+
(e.nil? || e.is_a?(Sass::SyntaxError))
|
98
|
+
raise keyword_exception
|
99
|
+
elsif e
|
100
|
+
raise e
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# @api private
|
105
|
+
# @return [Sass::Script::Value::ArgList]
|
106
|
+
def perform_splat(splat, performed_keywords, kwarg_splat, environment)
|
107
|
+
args, kwargs, separator = [], nil, :comma
|
108
|
+
|
109
|
+
if splat
|
110
|
+
splat = splat.perform(environment)
|
111
|
+
separator = splat.separator || separator
|
112
|
+
if splat.is_a?(Sass::Script::Value::ArgList)
|
113
|
+
args = splat.to_a
|
114
|
+
kwargs = splat.keywords
|
115
|
+
elsif splat.is_a?(Sass::Script::Value::Map)
|
116
|
+
kwargs = arg_hash(splat)
|
117
|
+
else
|
118
|
+
args = splat.to_a
|
119
|
+
end
|
120
|
+
end
|
121
|
+
kwargs ||= Sass::Util::NormalizedMap.new
|
122
|
+
kwargs.update(performed_keywords)
|
123
|
+
|
124
|
+
if kwarg_splat
|
125
|
+
kwarg_splat = kwarg_splat.perform(environment)
|
126
|
+
unless kwarg_splat.is_a?(Sass::Script::Value::Map)
|
127
|
+
raise Sass::SyntaxError.new("Variable keyword arguments must be a map " +
|
128
|
+
"(was #{kwarg_splat.inspect}).")
|
129
|
+
end
|
130
|
+
kwargs.update(arg_hash(kwarg_splat))
|
131
|
+
end
|
132
|
+
|
133
|
+
Sass::Script::Value::ArgList.new(args, kwargs, separator)
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def arg_hash(map)
|
139
|
+
Sass::Util.map_keys(map.to_h) do |key|
|
140
|
+
next key.value if key.is_a?(Sass::Script::Value::String)
|
141
|
+
raise Sass::SyntaxError.new("Variable keyword argument map must have string keys.\n" +
|
142
|
+
"#{key.inspect} is not a string in #{map.inspect}.")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
protected
|
148
|
+
|
149
|
+
def initialize(env)
|
150
|
+
@environment = env
|
151
|
+
@in_keyframes = false
|
152
|
+
@at_root_without_rule = false
|
153
|
+
end
|
154
|
+
|
155
|
+
# If an exception is raised, this adds proper metadata to the backtrace.
|
156
|
+
def visit(node)
|
157
|
+
return super(node.dup) unless @environment
|
158
|
+
@environment.stack.with_base(node.filename, node.line) {super(node.dup)}
|
159
|
+
rescue Sass::SyntaxError => e
|
160
|
+
e.modify_backtrace(:filename => node.filename, :line => node.line)
|
161
|
+
raise e
|
162
|
+
end
|
163
|
+
|
164
|
+
# Keeps track of the current environment.
|
165
|
+
def visit_children(parent)
|
166
|
+
with_environment Sass::Environment.new(@environment, parent.options) do
|
167
|
+
parent.children = super.flatten
|
168
|
+
parent
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Runs a block of code with the current environment replaced with the given one.
|
173
|
+
#
|
174
|
+
# @param env [Sass::Environment] The new environment for the duration of the block.
|
175
|
+
# @yield A block in which the environment is set to `env`.
|
176
|
+
# @return [Object] The return value of the block.
|
177
|
+
def with_environment(env)
|
178
|
+
old_env, @environment = @environment, env
|
179
|
+
yield
|
180
|
+
ensure
|
181
|
+
@environment = old_env
|
182
|
+
end
|
183
|
+
|
184
|
+
# Sets the options on the environment if this is the top-level root.
|
185
|
+
def visit_root(node)
|
186
|
+
yield
|
187
|
+
rescue Sass::SyntaxError => e
|
188
|
+
e.sass_template ||= node.template
|
189
|
+
raise e
|
190
|
+
end
|
191
|
+
|
192
|
+
# Removes this node from the tree if it's a silent comment.
|
193
|
+
def visit_comment(node)
|
194
|
+
return [] if node.invisible?
|
195
|
+
node.resolved_value = run_interp_no_strip(node.value)
|
196
|
+
node.resolved_value.gsub!(/\\([\\#])/, '\1')
|
197
|
+
node
|
198
|
+
end
|
199
|
+
|
200
|
+
# Prints the expression to STDERR.
|
201
|
+
def visit_debug(node)
|
202
|
+
res = node.expr.perform(@environment)
|
203
|
+
if res.is_a?(Sass::Script::Value::String)
|
204
|
+
res = res.value
|
205
|
+
else
|
206
|
+
res = res.to_sass
|
207
|
+
end
|
208
|
+
if node.filename
|
209
|
+
Sass::Util.sass_warn "#{node.filename}:#{node.line} DEBUG: #{res}"
|
210
|
+
else
|
211
|
+
Sass::Util.sass_warn "Line #{node.line} DEBUG: #{res}"
|
212
|
+
end
|
213
|
+
[]
|
214
|
+
end
|
215
|
+
|
216
|
+
# Throws the expression as an error.
|
217
|
+
def visit_error(node)
|
218
|
+
res = node.expr.perform(@environment)
|
219
|
+
if res.is_a?(Sass::Script::Value::String)
|
220
|
+
res = res.value
|
221
|
+
else
|
222
|
+
res = res.to_sass
|
223
|
+
end
|
224
|
+
raise Sass::SyntaxError.new(res)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Runs the child nodes once for each value in the list.
|
228
|
+
def visit_each(node)
|
229
|
+
list = node.list.perform(@environment)
|
230
|
+
|
231
|
+
with_environment Sass::SemiGlobalEnvironment.new(@environment) do
|
232
|
+
list.to_a.map do |value|
|
233
|
+
if node.vars.length == 1
|
234
|
+
@environment.set_local_var(node.vars.first, value)
|
235
|
+
else
|
236
|
+
node.vars.zip(value.to_a) do |(var, sub_value)|
|
237
|
+
@environment.set_local_var(var, sub_value || Sass::Script::Value::Null.new)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
node.children.map {|c| visit(c)}
|
241
|
+
end.flatten
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Runs SassScript interpolation in the selector,
|
246
|
+
# and then parses the result into a {Sass::Selector::CommaSequence}.
|
247
|
+
def visit_extend(node)
|
248
|
+
parser = Sass::SCSS::StaticParser.new(run_interp(node.selector),
|
249
|
+
node.filename, node.options[:importer], node.line)
|
250
|
+
node.resolved_selector = parser.parse_selector
|
251
|
+
node
|
252
|
+
end
|
253
|
+
|
254
|
+
# Runs the child nodes once for each time through the loop, varying the variable each time.
|
255
|
+
def visit_for(node)
|
256
|
+
from = node.from.perform(@environment)
|
257
|
+
to = node.to.perform(@environment)
|
258
|
+
from.assert_int!
|
259
|
+
to.assert_int!
|
260
|
+
|
261
|
+
to = to.coerce(from.numerator_units, from.denominator_units)
|
262
|
+
direction = from.to_i > to.to_i ? -1 : 1
|
263
|
+
range = Range.new(direction * from.to_i, direction * to.to_i, node.exclusive)
|
264
|
+
|
265
|
+
with_environment Sass::SemiGlobalEnvironment.new(@environment) do
|
266
|
+
range.map do |i|
|
267
|
+
@environment.set_local_var(node.var,
|
268
|
+
Sass::Script::Value::Number.new(direction * i,
|
269
|
+
from.numerator_units, from.denominator_units))
|
270
|
+
node.children.map {|c| visit(c)}
|
271
|
+
end.flatten
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Loads the function into the environment.
|
276
|
+
def visit_function(node)
|
277
|
+
env = Sass::Environment.new(@environment, node.options)
|
278
|
+
|
279
|
+
if node.normalized_name == 'calc' || node.normalized_name == 'element' ||
|
280
|
+
node.name == 'expression' || node.name == 'url'
|
281
|
+
@@function_name_deprecation.warn(node.filename, node.line, <<WARNING)
|
282
|
+
Naming a function "#{node.name}" is disallowed and will be an error in future versions of Sass.
|
283
|
+
This name conflicts with an existing CSS function with special parse rules.
|
284
|
+
WARNING
|
285
|
+
end
|
286
|
+
|
287
|
+
@environment.set_local_function(node.name,
|
288
|
+
Sass::Callable.new(node.name, node.args, node.splat, env,
|
289
|
+
node.children, false, "function", :stylesheet))
|
290
|
+
[]
|
291
|
+
end
|
292
|
+
|
293
|
+
# Runs the child nodes if the conditional expression is true;
|
294
|
+
# otherwise, tries the else nodes.
|
295
|
+
def visit_if(node)
|
296
|
+
if node.expr.nil? || node.expr.perform(@environment).to_bool
|
297
|
+
with_environment Sass::SemiGlobalEnvironment.new(@environment) do
|
298
|
+
node.children.map {|c| visit(c)}
|
299
|
+
end.flatten
|
300
|
+
elsif node.else
|
301
|
+
visit(node.else)
|
302
|
+
else
|
303
|
+
[]
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# Returns a static DirectiveNode if this is importing a CSS file,
|
308
|
+
# or parses and includes the imported Sass file.
|
309
|
+
def visit_import(node)
|
310
|
+
if (path = node.css_import?)
|
311
|
+
resolved_node = Sass::Tree::CssImportNode.resolved("url(#{path})")
|
312
|
+
resolved_node.options = node.options
|
313
|
+
resolved_node.source_range = node.source_range
|
314
|
+
return resolved_node
|
315
|
+
end
|
316
|
+
file = node.imported_file
|
317
|
+
if @environment.stack.frames.any? {|f| f.is_import? && f.filename == file.options[:filename]}
|
318
|
+
handle_import_loop!(node)
|
319
|
+
end
|
320
|
+
|
321
|
+
begin
|
322
|
+
@environment.stack.with_import(node.filename, node.line) do
|
323
|
+
root = file.to_tree
|
324
|
+
Sass::Tree::Visitors::CheckNesting.visit(root)
|
325
|
+
node.children = root.children.map {|c| visit(c)}.flatten
|
326
|
+
node
|
327
|
+
end
|
328
|
+
rescue Sass::SyntaxError => e
|
329
|
+
e.modify_backtrace(:filename => node.imported_file.options[:filename])
|
330
|
+
e.add_backtrace(:filename => node.filename, :line => node.line)
|
331
|
+
raise e
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Loads a mixin into the environment.
|
336
|
+
def visit_mixindef(node)
|
337
|
+
env = Sass::Environment.new(@environment, node.options)
|
338
|
+
@environment.set_local_mixin(node.name,
|
339
|
+
Sass::Callable.new(node.name, node.args, node.splat, env,
|
340
|
+
node.children, node.has_content, "mixin", :stylesheet))
|
341
|
+
[]
|
342
|
+
end
|
343
|
+
|
344
|
+
# Runs a mixin.
|
345
|
+
def visit_mixin(node)
|
346
|
+
@environment.stack.with_mixin(node.filename, node.line, node.name) do
|
347
|
+
mixin = @environment.mixin(node.name)
|
348
|
+
raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin
|
349
|
+
|
350
|
+
if node.has_children && !mixin.has_content
|
351
|
+
raise Sass::SyntaxError.new(%(Mixin "#{node.name}" does not accept a content block.))
|
352
|
+
end
|
353
|
+
|
354
|
+
args = node.args.map {|a| a.perform(@environment)}
|
355
|
+
keywords = Sass::Util.map_vals(node.keywords) {|v| v.perform(@environment)}
|
356
|
+
splat = self.class.perform_splat(node.splat, keywords, node.kwarg_splat, @environment)
|
357
|
+
|
358
|
+
self.class.perform_arguments(mixin, args, splat, @environment) do |env|
|
359
|
+
env.caller = Sass::Environment.new(@environment)
|
360
|
+
env.content = [node.children, @environment] if node.has_children
|
361
|
+
|
362
|
+
trace_node = Sass::Tree::TraceNode.from_node(node.name, node)
|
363
|
+
with_environment(env) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten}
|
364
|
+
trace_node
|
365
|
+
end
|
366
|
+
end
|
367
|
+
rescue Sass::SyntaxError => e
|
368
|
+
e.modify_backtrace(:mixin => node.name, :line => node.line)
|
369
|
+
e.add_backtrace(:line => node.line)
|
370
|
+
raise e
|
371
|
+
end
|
372
|
+
|
373
|
+
def visit_content(node)
|
374
|
+
content, content_env = @environment.content
|
375
|
+
return [] unless content
|
376
|
+
@environment.stack.with_mixin(node.filename, node.line, '@content') do
|
377
|
+
trace_node = Sass::Tree::TraceNode.from_node('@content', node)
|
378
|
+
content_env = Sass::Environment.new(content_env)
|
379
|
+
content_env.caller = Sass::Environment.new(@environment)
|
380
|
+
with_environment(content_env) do
|
381
|
+
trace_node.children = content.map {|c| visit(c.dup)}.flatten
|
382
|
+
end
|
383
|
+
trace_node
|
384
|
+
end
|
385
|
+
rescue Sass::SyntaxError => e
|
386
|
+
e.modify_backtrace(:mixin => '@content', :line => node.line)
|
387
|
+
e.add_backtrace(:line => node.line)
|
388
|
+
raise e
|
389
|
+
end
|
390
|
+
|
391
|
+
# Runs any SassScript that may be embedded in a property.
|
392
|
+
def visit_prop(node)
|
393
|
+
node.resolved_name = run_interp(node.name)
|
394
|
+
|
395
|
+
# If the node's value is just a variable or similar, we may get a useful
|
396
|
+
# source range from evaluating it.
|
397
|
+
if node.value.length == 1 && node.value.first.is_a?(Sass::Script::Tree::Node)
|
398
|
+
result = node.value.first.perform(@environment)
|
399
|
+
node.resolved_value = result.to_s
|
400
|
+
node.value_source_range = result.source_range if result.source_range
|
401
|
+
elsif node.custom_property?
|
402
|
+
node.resolved_value = run_interp_no_strip(node.value)
|
403
|
+
else
|
404
|
+
node.resolved_value = run_interp(node.value)
|
405
|
+
end
|
406
|
+
|
407
|
+
yield
|
408
|
+
end
|
409
|
+
|
410
|
+
# Returns the value of the expression.
|
411
|
+
def visit_return(node)
|
412
|
+
throw :_sass_return, node.expr.perform(@environment)
|
413
|
+
end
|
414
|
+
|
415
|
+
# Runs SassScript interpolation in the selector,
|
416
|
+
# and then parses the result into a {Sass::Selector::CommaSequence}.
|
417
|
+
def visit_rule(node)
|
418
|
+
old_at_root_without_rule = @at_root_without_rule
|
419
|
+
parser = Sass::SCSS::StaticParser.new(run_interp(node.rule),
|
420
|
+
node.filename, node.options[:importer], node.line)
|
421
|
+
if @in_keyframes
|
422
|
+
keyframe_rule_node = Sass::Tree::KeyframeRuleNode.new(parser.parse_keyframes_selector)
|
423
|
+
keyframe_rule_node.options = node.options
|
424
|
+
keyframe_rule_node.line = node.line
|
425
|
+
keyframe_rule_node.filename = node.filename
|
426
|
+
keyframe_rule_node.source_range = node.source_range
|
427
|
+
keyframe_rule_node.has_children = node.has_children
|
428
|
+
with_environment Sass::Environment.new(@environment, node.options) do
|
429
|
+
keyframe_rule_node.children = node.children.map {|c| visit(c)}.flatten
|
430
|
+
end
|
431
|
+
keyframe_rule_node
|
432
|
+
else
|
433
|
+
@at_root_without_rule = false
|
434
|
+
node.parsed_rules ||= parser.parse_selector
|
435
|
+
node.resolved_rules = node.parsed_rules.resolve_parent_refs(
|
436
|
+
@environment.selector, !old_at_root_without_rule)
|
437
|
+
node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors]
|
438
|
+
with_environment Sass::Environment.new(@environment, node.options) do
|
439
|
+
@environment.selector = node.resolved_rules
|
440
|
+
node.children = node.children.map {|c| visit(c)}.flatten
|
441
|
+
end
|
442
|
+
node
|
443
|
+
end
|
444
|
+
ensure
|
445
|
+
@at_root_without_rule = old_at_root_without_rule
|
446
|
+
end
|
447
|
+
|
448
|
+
# Sets a variable that indicates that the first level of rule nodes
|
449
|
+
# shouldn't include the parent selector by default.
|
450
|
+
def visit_atroot(node)
|
451
|
+
if node.query
|
452
|
+
parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
|
453
|
+
node.filename, node.options[:importer], node.line)
|
454
|
+
node.resolved_type, node.resolved_value = parser.parse_static_at_root_query
|
455
|
+
else
|
456
|
+
node.resolved_type, node.resolved_value = :without, ['rule']
|
457
|
+
end
|
458
|
+
|
459
|
+
old_at_root_without_rule = @at_root_without_rule
|
460
|
+
old_in_keyframes = @in_keyframes
|
461
|
+
@at_root_without_rule = true if node.exclude?('rule')
|
462
|
+
@in_keyframes = false if node.exclude?('keyframes')
|
463
|
+
yield
|
464
|
+
ensure
|
465
|
+
@in_keyframes = old_in_keyframes
|
466
|
+
@at_root_without_rule = old_at_root_without_rule
|
467
|
+
end
|
468
|
+
|
469
|
+
# Loads the new variable value into the environment.
|
470
|
+
def visit_variable(node)
|
471
|
+
env = @environment
|
472
|
+
env = env.global_env if node.global
|
473
|
+
if node.guarded
|
474
|
+
var = env.var(node.name)
|
475
|
+
return [] if var && !var.null?
|
476
|
+
end
|
477
|
+
|
478
|
+
val = node.expr.perform(@environment)
|
479
|
+
if node.expr.source_range
|
480
|
+
val.source_range = node.expr.source_range
|
481
|
+
else
|
482
|
+
val.source_range = node.source_range
|
483
|
+
end
|
484
|
+
env.set_var(node.name, val)
|
485
|
+
[]
|
486
|
+
end
|
487
|
+
|
488
|
+
# Prints the expression to STDERR with a stylesheet trace.
|
489
|
+
def visit_warn(node)
|
490
|
+
res = node.expr.perform(@environment)
|
491
|
+
res = res.value if res.is_a?(Sass::Script::Value::String)
|
492
|
+
@environment.stack.with_directive(node.filename, node.line, "@warn") do
|
493
|
+
msg = "WARNING: #{res}\n "
|
494
|
+
msg << @environment.stack.to_s.gsub("\n", "\n ") << "\n"
|
495
|
+
Sass::Util.sass_warn msg
|
496
|
+
end
|
497
|
+
[]
|
498
|
+
end
|
499
|
+
|
500
|
+
# Runs the child nodes until the continuation expression becomes false.
|
501
|
+
def visit_while(node)
|
502
|
+
children = []
|
503
|
+
with_environment Sass::SemiGlobalEnvironment.new(@environment) do
|
504
|
+
children += node.children.map {|c| visit(c)} while node.expr.perform(@environment).to_bool
|
505
|
+
end
|
506
|
+
children.flatten
|
507
|
+
end
|
508
|
+
|
509
|
+
def visit_directive(node)
|
510
|
+
node.resolved_value = run_interp(node.value)
|
511
|
+
old_in_keyframes, @in_keyframes = @in_keyframes, node.normalized_name == "@keyframes"
|
512
|
+
with_environment Sass::Environment.new(@environment) do
|
513
|
+
node.children = node.children.map {|c| visit(c)}.flatten
|
514
|
+
node
|
515
|
+
end
|
516
|
+
ensure
|
517
|
+
@in_keyframes = old_in_keyframes
|
518
|
+
end
|
519
|
+
|
520
|
+
def visit_media(node)
|
521
|
+
parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
|
522
|
+
node.filename, node.options[:importer], node.line)
|
523
|
+
node.resolved_query ||= parser.parse_media_query_list
|
524
|
+
yield
|
525
|
+
end
|
526
|
+
|
527
|
+
def visit_supports(node)
|
528
|
+
node.condition = node.condition.deep_copy
|
529
|
+
node.condition.perform(@environment)
|
530
|
+
yield
|
531
|
+
end
|
532
|
+
|
533
|
+
def visit_cssimport(node)
|
534
|
+
node.resolved_uri = run_interp([node.uri])
|
535
|
+
if node.query && !node.query.empty?
|
536
|
+
parser = Sass::SCSS::StaticParser.new(run_interp(node.query),
|
537
|
+
node.filename, node.options[:importer], node.line)
|
538
|
+
node.resolved_query ||= parser.parse_media_query_list
|
539
|
+
end
|
540
|
+
if node.supports_condition
|
541
|
+
node.supports_condition.perform(@environment)
|
542
|
+
end
|
543
|
+
yield
|
544
|
+
end
|
545
|
+
|
546
|
+
private
|
547
|
+
|
548
|
+
def run_interp_no_strip(text)
|
549
|
+
text.map do |r|
|
550
|
+
next r if r.is_a?(String)
|
551
|
+
r.perform(@environment).to_s(:quote => :none)
|
552
|
+
end.join
|
553
|
+
end
|
554
|
+
|
555
|
+
def run_interp(text)
|
556
|
+
Sass::Util.strip_except_escapes(run_interp_no_strip(text))
|
557
|
+
end
|
558
|
+
|
559
|
+
def handle_import_loop!(node)
|
560
|
+
msg = "An @import loop has been found:"
|
561
|
+
files = @environment.stack.frames.select {|f| f.is_import?}.map {|f| f.filename}.compact
|
562
|
+
if node.filename == node.imported_file.options[:filename]
|
563
|
+
raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself")
|
564
|
+
end
|
565
|
+
|
566
|
+
files << node.filename << node.imported_file.options[:filename]
|
567
|
+
msg << "\n" << files.each_cons(2).map do |m1, m2|
|
568
|
+
" #{m1} imports #{m2}"
|
569
|
+
end.join("\n")
|
570
|
+
raise Sass::SyntaxError.new(msg)
|
571
|
+
end
|
572
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# A visitor for setting options on the Sass tree
|
2
|
+
class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
|
3
|
+
# @param root [Tree::Node] The root node of the tree to visit.
|
4
|
+
# @param options [{Symbol => Object}] The options has to set.
|
5
|
+
def self.visit(root, options); new(options).send(:visit, root); end
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def visit(node)
|
14
|
+
node.instance_variable_set('@options', @options)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def visit_comment(node)
|
19
|
+
node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
|
20
|
+
yield
|
21
|
+
end
|
22
|
+
|
23
|
+
def visit_debug(node)
|
24
|
+
node.expr.options = @options
|
25
|
+
yield
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit_error(node)
|
29
|
+
node.expr.options = @options
|
30
|
+
yield
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_each(node)
|
34
|
+
node.list.options = @options
|
35
|
+
yield
|
36
|
+
end
|
37
|
+
|
38
|
+
def visit_extend(node)
|
39
|
+
node.selector.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
|
40
|
+
yield
|
41
|
+
end
|
42
|
+
|
43
|
+
def visit_for(node)
|
44
|
+
node.from.options = @options
|
45
|
+
node.to.options = @options
|
46
|
+
yield
|
47
|
+
end
|
48
|
+
|
49
|
+
def visit_function(node)
|
50
|
+
node.args.each do |k, v|
|
51
|
+
k.options = @options
|
52
|
+
v.options = @options if v
|
53
|
+
end
|
54
|
+
node.splat.options = @options if node.splat
|
55
|
+
yield
|
56
|
+
end
|
57
|
+
|
58
|
+
def visit_if(node)
|
59
|
+
node.expr.options = @options if node.expr
|
60
|
+
visit(node.else) if node.else
|
61
|
+
yield
|
62
|
+
end
|
63
|
+
|
64
|
+
def visit_import(node)
|
65
|
+
# We have no good way of propagating the new options through an Engine
|
66
|
+
# instance, so we just null it out. This also lets us avoid caching an
|
67
|
+
# imported Engine along with the importing source tree.
|
68
|
+
node.imported_file = nil
|
69
|
+
yield
|
70
|
+
end
|
71
|
+
|
72
|
+
def visit_mixindef(node)
|
73
|
+
node.args.each do |k, v|
|
74
|
+
k.options = @options
|
75
|
+
v.options = @options if v
|
76
|
+
end
|
77
|
+
node.splat.options = @options if node.splat
|
78
|
+
yield
|
79
|
+
end
|
80
|
+
|
81
|
+
def visit_mixin(node)
|
82
|
+
node.args.each {|a| a.options = @options}
|
83
|
+
node.keywords.each {|_k, v| v.options = @options}
|
84
|
+
node.splat.options = @options if node.splat
|
85
|
+
node.kwarg_splat.options = @options if node.kwarg_splat
|
86
|
+
yield
|
87
|
+
end
|
88
|
+
|
89
|
+
def visit_prop(node)
|
90
|
+
node.name.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
|
91
|
+
node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
|
92
|
+
yield
|
93
|
+
end
|
94
|
+
|
95
|
+
def visit_return(node)
|
96
|
+
node.expr.options = @options
|
97
|
+
yield
|
98
|
+
end
|
99
|
+
|
100
|
+
def visit_rule(node)
|
101
|
+
node.rule.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
|
102
|
+
yield
|
103
|
+
end
|
104
|
+
|
105
|
+
def visit_variable(node)
|
106
|
+
node.expr.options = @options
|
107
|
+
yield
|
108
|
+
end
|
109
|
+
|
110
|
+
def visit_warn(node)
|
111
|
+
node.expr.options = @options
|
112
|
+
yield
|
113
|
+
end
|
114
|
+
|
115
|
+
def visit_while(node)
|
116
|
+
node.expr.options = @options
|
117
|
+
yield
|
118
|
+
end
|
119
|
+
|
120
|
+
def visit_directive(node)
|
121
|
+
node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
|
122
|
+
yield
|
123
|
+
end
|
124
|
+
|
125
|
+
def visit_media(node)
|
126
|
+
node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
|
127
|
+
yield
|
128
|
+
end
|
129
|
+
|
130
|
+
def visit_cssimport(node)
|
131
|
+
node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)} if node.query
|
132
|
+
yield
|
133
|
+
end
|
134
|
+
|
135
|
+
def visit_supports(node)
|
136
|
+
node.condition.options = @options
|
137
|
+
yield
|
138
|
+
end
|
139
|
+
end
|