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.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +13 -0
  3. data/AGENTS.md +534 -0
  4. data/CODE_OF_CONDUCT.md +10 -0
  5. data/CONTRIBUTING.md +148 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +242 -0
  8. data/VERSION +1 -0
  9. data/VERSION_NAME +1 -0
  10. data/bin/sass +13 -0
  11. data/bin/sass-convert +12 -0
  12. data/bin/scss +13 -0
  13. data/extra/sass-spec-ref.sh +40 -0
  14. data/extra/update_watch.rb +13 -0
  15. data/init.rb +18 -0
  16. data/lib/sass/cache_stores/base.rb +88 -0
  17. data/lib/sass/cache_stores/chain.rb +34 -0
  18. data/lib/sass/cache_stores/filesystem.rb +60 -0
  19. data/lib/sass/cache_stores/memory.rb +46 -0
  20. data/lib/sass/cache_stores/null.rb +25 -0
  21. data/lib/sass/cache_stores.rb +15 -0
  22. data/lib/sass/callbacks.rb +67 -0
  23. data/lib/sass/css.rb +407 -0
  24. data/lib/sass/deprecation.rb +55 -0
  25. data/lib/sass/engine.rb +1236 -0
  26. data/lib/sass/environment.rb +236 -0
  27. data/lib/sass/error.rb +198 -0
  28. data/lib/sass/exec/base.rb +188 -0
  29. data/lib/sass/exec/sass_convert.rb +283 -0
  30. data/lib/sass/exec/sass_scss.rb +436 -0
  31. data/lib/sass/exec.rb +9 -0
  32. data/lib/sass/features.rb +48 -0
  33. data/lib/sass/importers/base.rb +182 -0
  34. data/lib/sass/importers/deprecated_path.rb +51 -0
  35. data/lib/sass/importers/filesystem.rb +221 -0
  36. data/lib/sass/importers.rb +23 -0
  37. data/lib/sass/logger/base.rb +47 -0
  38. data/lib/sass/logger/delayed.rb +50 -0
  39. data/lib/sass/logger/log_level.rb +45 -0
  40. data/lib/sass/logger.rb +17 -0
  41. data/lib/sass/media.rb +210 -0
  42. data/lib/sass/plugin/compiler.rb +552 -0
  43. data/lib/sass/plugin/configuration.rb +134 -0
  44. data/lib/sass/plugin/generic.rb +15 -0
  45. data/lib/sass/plugin/merb.rb +48 -0
  46. data/lib/sass/plugin/rack.rb +60 -0
  47. data/lib/sass/plugin/rails.rb +47 -0
  48. data/lib/sass/plugin/staleness_checker.rb +199 -0
  49. data/lib/sass/plugin.rb +134 -0
  50. data/lib/sass/railtie.rb +10 -0
  51. data/lib/sass/repl.rb +57 -0
  52. data/lib/sass/root.rb +7 -0
  53. data/lib/sass/script/css_lexer.rb +33 -0
  54. data/lib/sass/script/css_parser.rb +36 -0
  55. data/lib/sass/script/functions.rb +3103 -0
  56. data/lib/sass/script/lexer.rb +518 -0
  57. data/lib/sass/script/parser.rb +1164 -0
  58. data/lib/sass/script/tree/funcall.rb +314 -0
  59. data/lib/sass/script/tree/interpolation.rb +220 -0
  60. data/lib/sass/script/tree/list_literal.rb +119 -0
  61. data/lib/sass/script/tree/literal.rb +49 -0
  62. data/lib/sass/script/tree/map_literal.rb +64 -0
  63. data/lib/sass/script/tree/node.rb +119 -0
  64. data/lib/sass/script/tree/operation.rb +149 -0
  65. data/lib/sass/script/tree/selector.rb +26 -0
  66. data/lib/sass/script/tree/string_interpolation.rb +125 -0
  67. data/lib/sass/script/tree/unary_operation.rb +69 -0
  68. data/lib/sass/script/tree/variable.rb +57 -0
  69. data/lib/sass/script/tree.rb +16 -0
  70. data/lib/sass/script/value/arg_list.rb +36 -0
  71. data/lib/sass/script/value/base.rb +258 -0
  72. data/lib/sass/script/value/bool.rb +35 -0
  73. data/lib/sass/script/value/callable.rb +25 -0
  74. data/lib/sass/script/value/color.rb +704 -0
  75. data/lib/sass/script/value/function.rb +19 -0
  76. data/lib/sass/script/value/helpers.rb +298 -0
  77. data/lib/sass/script/value/list.rb +135 -0
  78. data/lib/sass/script/value/map.rb +70 -0
  79. data/lib/sass/script/value/null.rb +44 -0
  80. data/lib/sass/script/value/number.rb +564 -0
  81. data/lib/sass/script/value/string.rb +138 -0
  82. data/lib/sass/script/value.rb +13 -0
  83. data/lib/sass/script.rb +66 -0
  84. data/lib/sass/scss/css_parser.rb +61 -0
  85. data/lib/sass/scss/parser.rb +1343 -0
  86. data/lib/sass/scss/rx.rb +134 -0
  87. data/lib/sass/scss/static_parser.rb +351 -0
  88. data/lib/sass/scss.rb +14 -0
  89. data/lib/sass/selector/abstract_sequence.rb +112 -0
  90. data/lib/sass/selector/comma_sequence.rb +195 -0
  91. data/lib/sass/selector/pseudo.rb +291 -0
  92. data/lib/sass/selector/sequence.rb +661 -0
  93. data/lib/sass/selector/simple.rb +124 -0
  94. data/lib/sass/selector/simple_sequence.rb +348 -0
  95. data/lib/sass/selector.rb +327 -0
  96. data/lib/sass/shared.rb +76 -0
  97. data/lib/sass/source/map.rb +209 -0
  98. data/lib/sass/source/position.rb +39 -0
  99. data/lib/sass/source/range.rb +41 -0
  100. data/lib/sass/stack.rb +140 -0
  101. data/lib/sass/supports.rb +225 -0
  102. data/lib/sass/tree/at_root_node.rb +83 -0
  103. data/lib/sass/tree/charset_node.rb +22 -0
  104. data/lib/sass/tree/comment_node.rb +82 -0
  105. data/lib/sass/tree/content_node.rb +9 -0
  106. data/lib/sass/tree/css_import_node.rb +68 -0
  107. data/lib/sass/tree/debug_node.rb +18 -0
  108. data/lib/sass/tree/directive_node.rb +59 -0
  109. data/lib/sass/tree/each_node.rb +24 -0
  110. data/lib/sass/tree/error_node.rb +18 -0
  111. data/lib/sass/tree/extend_node.rb +43 -0
  112. data/lib/sass/tree/for_node.rb +36 -0
  113. data/lib/sass/tree/function_node.rb +44 -0
  114. data/lib/sass/tree/if_node.rb +52 -0
  115. data/lib/sass/tree/import_node.rb +75 -0
  116. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  117. data/lib/sass/tree/media_node.rb +48 -0
  118. data/lib/sass/tree/mixin_def_node.rb +38 -0
  119. data/lib/sass/tree/mixin_node.rb +52 -0
  120. data/lib/sass/tree/node.rb +240 -0
  121. data/lib/sass/tree/prop_node.rb +162 -0
  122. data/lib/sass/tree/return_node.rb +19 -0
  123. data/lib/sass/tree/root_node.rb +44 -0
  124. data/lib/sass/tree/rule_node.rb +153 -0
  125. data/lib/sass/tree/supports_node.rb +38 -0
  126. data/lib/sass/tree/trace_node.rb +33 -0
  127. data/lib/sass/tree/variable_node.rb +36 -0
  128. data/lib/sass/tree/visitors/base.rb +72 -0
  129. data/lib/sass/tree/visitors/check_nesting.rb +173 -0
  130. data/lib/sass/tree/visitors/convert.rb +350 -0
  131. data/lib/sass/tree/visitors/cssize.rb +362 -0
  132. data/lib/sass/tree/visitors/deep_copy.rb +107 -0
  133. data/lib/sass/tree/visitors/extend.rb +64 -0
  134. data/lib/sass/tree/visitors/perform.rb +572 -0
  135. data/lib/sass/tree/visitors/set_options.rb +139 -0
  136. data/lib/sass/tree/visitors/to_css.rb +440 -0
  137. data/lib/sass/tree/warn_node.rb +18 -0
  138. data/lib/sass/tree/while_node.rb +18 -0
  139. data/lib/sass/util/multibyte_string_scanner.rb +151 -0
  140. data/lib/sass/util/normalized_map.rb +122 -0
  141. data/lib/sass/util/subset_map.rb +109 -0
  142. data/lib/sass/util/test.rb +9 -0
  143. data/lib/sass/util.rb +1137 -0
  144. data/lib/sass/version.rb +120 -0
  145. data/lib/sass.rb +102 -0
  146. data/rails/init.rb +1 -0
  147. 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