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,362 @@
1
+ # A visitor for converting a static Sass tree into a static CSS tree.
2
+ class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base
3
+ # @param root [Tree::Node] The root node of the tree to visit.
4
+ # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
5
+ # *and* the extensions defined for this tree
6
+ def self.visit(root); super; end
7
+
8
+ protected
9
+
10
+ # Returns the immediate parent of the current node.
11
+ # @return [Tree::Node]
12
+ def parent
13
+ @parents.last
14
+ end
15
+
16
+ def initialize
17
+ @parents = []
18
+ @extends = Sass::Util::SubsetMap.new
19
+ end
20
+
21
+ # If an exception is raised, this adds proper metadata to the backtrace.
22
+ def visit(node)
23
+ super(node)
24
+ rescue Sass::SyntaxError => e
25
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
26
+ raise e
27
+ end
28
+
29
+ # Keeps track of the current parent node.
30
+ def visit_children(parent)
31
+ with_parent parent do
32
+ parent.children = visit_children_without_parent(parent)
33
+ parent
34
+ end
35
+ end
36
+
37
+ # Like {#visit\_children}, but doesn't set {#parent}.
38
+ #
39
+ # @param node [Sass::Tree::Node]
40
+ # @return [Array<Sass::Tree::Node>] the flattened results of
41
+ # visiting all the children of `node`
42
+ def visit_children_without_parent(node)
43
+ node.children.map {|c| visit(c)}.flatten
44
+ end
45
+
46
+ # Runs a block of code with the current parent node
47
+ # replaced with the given node.
48
+ #
49
+ # @param parent [Tree::Node] The new parent for the duration of the block.
50
+ # @yield A block in which the parent is set to `parent`.
51
+ # @return [Object] The return value of the block.
52
+ def with_parent(parent)
53
+ @parents.push parent
54
+ yield
55
+ ensure
56
+ @parents.pop
57
+ end
58
+
59
+ # Converts the entire document to CSS.
60
+ #
61
+ # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes
62
+ # *and* the extensions defined for this tree
63
+ def visit_root(node)
64
+ yield
65
+
66
+ if parent.nil?
67
+ imports_to_move = []
68
+ import_limit = nil
69
+ i = -1
70
+ node.children.reject! do |n|
71
+ i += 1
72
+ if import_limit
73
+ next false unless n.is_a?(Sass::Tree::CssImportNode)
74
+ imports_to_move << n
75
+ next true
76
+ end
77
+
78
+ if !n.is_a?(Sass::Tree::CommentNode) &&
79
+ !n.is_a?(Sass::Tree::CharsetNode) &&
80
+ !n.is_a?(Sass::Tree::CssImportNode)
81
+ import_limit = i
82
+ end
83
+
84
+ false
85
+ end
86
+
87
+ if import_limit
88
+ node.children = node.children[0...import_limit] + imports_to_move +
89
+ node.children[import_limit..-1]
90
+ end
91
+ end
92
+
93
+ return node, @extends
94
+ rescue Sass::SyntaxError => e
95
+ e.sass_template ||= node.template
96
+ raise e
97
+ end
98
+
99
+ # A simple struct wrapping up information about a single `@extend` instance. A
100
+ # single {ExtendNode} can have multiple Extends if either the parent node or
101
+ # the extended selector is a comma sequence.
102
+ #
103
+ # @attr extender [Sass::Selector::Sequence]
104
+ # The selector of the CSS rule containing the `@extend`.
105
+ # @attr target [Array<Sass::Selector::Simple>] The selector being `@extend`ed.
106
+ # @attr node [Sass::Tree::ExtendNode] The node that produced this extend.
107
+ # @attr directives [Array<Sass::Tree::DirectiveNode>]
108
+ # The directives containing the `@extend`.
109
+ # @attr success [Boolean]
110
+ # Whether this extend successfully matched a selector.
111
+ Extend = Struct.new(:extender, :target, :node, :directives, :success)
112
+
113
+ # Registers an extension in the `@extends` subset map.
114
+ def visit_extend(node)
115
+ parent.resolved_rules.populate_extends(@extends, node.resolved_selector, node,
116
+ @parents.select {|p| p.is_a?(Sass::Tree::DirectiveNode)})
117
+ []
118
+ end
119
+
120
+ # Modifies exception backtraces to include the imported file.
121
+ def visit_import(node)
122
+ visit_children_without_parent(node)
123
+ rescue Sass::SyntaxError => e
124
+ e.modify_backtrace(:filename => node.children.first.filename)
125
+ e.add_backtrace(:filename => node.filename, :line => node.line)
126
+ raise e
127
+ end
128
+
129
+ # Asserts that all the traced children are valid in their new location.
130
+ def visit_trace(node)
131
+ visit_children_without_parent(node)
132
+ rescue Sass::SyntaxError => e
133
+ e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line)
134
+ e.add_backtrace(:filename => node.filename, :line => node.line)
135
+ raise e
136
+ end
137
+
138
+ # Converts nested properties into flat properties
139
+ # and updates the indentation of the prop node based on the nesting level.
140
+ def visit_prop(node)
141
+ if parent.is_a?(Sass::Tree::PropNode)
142
+ node.resolved_name = "#{parent.resolved_name}-#{node.resolved_name}"
143
+ node.tabs = parent.tabs + (parent.resolved_value.empty? ? 0 : 1) if node.style == :nested
144
+ end
145
+
146
+ yield
147
+
148
+ result = node.children.dup
149
+ if !node.resolved_value.empty? || node.children.empty?
150
+ node.send(:check!)
151
+ result.unshift(node)
152
+ end
153
+
154
+ result
155
+ end
156
+
157
+ def visit_atroot(node)
158
+ # If there aren't any more directives or rules that this @at-root needs to
159
+ # exclude, we can get rid of it and just evaluate the children.
160
+ if @parents.none? {|n| node.exclude_node?(n)}
161
+ results = visit_children_without_parent(node)
162
+ results.each {|c| c.tabs += node.tabs if bubblable?(c)}
163
+ if !results.empty? && bubblable?(results.last)
164
+ results.last.group_end = node.group_end
165
+ end
166
+ return results
167
+ end
168
+
169
+ # If this @at-root excludes the immediate parent, return it as-is so that it
170
+ # can be bubbled up by the parent node.
171
+ return Bubble.new(node) if node.exclude_node?(parent)
172
+
173
+ # Otherwise, duplicate the current parent and move it into the @at-root
174
+ # node. As above, returning an @at-root node signals to the parent directive
175
+ # that it should be bubbled upwards.
176
+ bubble(node)
177
+ end
178
+
179
+ # The following directives are visible and have children. This means they need
180
+ # to be able to handle bubbling up nodes such as @at-root and @media.
181
+
182
+ # Updates the indentation of the rule node based on the nesting
183
+ # level. The selectors were resolved in {Perform}.
184
+ def visit_rule(node)
185
+ yield
186
+
187
+ rules = node.children.select {|c| bubblable?(c)}
188
+ props = node.children.reject {|c| bubblable?(c) || c.invisible?}
189
+
190
+ unless props.empty?
191
+ node.children = props
192
+ rules.each {|r| r.tabs += 1} if node.style == :nested
193
+ rules.unshift(node)
194
+ end
195
+
196
+ rules = debubble(rules)
197
+ unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty? || !bubblable?(rules.last)
198
+ rules.last.group_end = true
199
+ end
200
+ rules
201
+ end
202
+
203
+ def visit_keyframerule(node)
204
+ return node unless node.has_children
205
+
206
+ yield
207
+
208
+ debubble(node.children, node)
209
+ end
210
+
211
+ # Bubbles a directive up through RuleNodes.
212
+ def visit_directive(node)
213
+ return node unless node.has_children
214
+ if parent.is_a?(Sass::Tree::RuleNode)
215
+ # @keyframes shouldn't include the rule nodes, so we manually create a
216
+ # bubble that doesn't have the parent's contents for them.
217
+ return node.normalized_name == '@keyframes' ? Bubble.new(node) : bubble(node)
218
+ end
219
+
220
+ yield
221
+
222
+ # Since we don't know if the mere presence of an unknown directive may be
223
+ # important, we should keep an empty version around even if all the contents
224
+ # are removed via @at-root. However, if the contents are just bubbled out,
225
+ # we don't need to do so.
226
+ directive_exists = node.children.any? do |child|
227
+ next true unless child.is_a?(Bubble)
228
+ next false unless child.node.is_a?(Sass::Tree::DirectiveNode)
229
+ child.node.resolved_value == node.resolved_value
230
+ end
231
+
232
+ # We know empty @keyframes directives do nothing.
233
+ if directive_exists || node.name == '@keyframes'
234
+ []
235
+ else
236
+ empty_node = node.dup
237
+ empty_node.children = []
238
+ [empty_node]
239
+ end + debubble(node.children, node)
240
+ end
241
+
242
+ # Bubbles the `@media` directive up through RuleNodes
243
+ # and merges it with other `@media` directives.
244
+ def visit_media(node)
245
+ return bubble(node) if parent.is_a?(Sass::Tree::RuleNode)
246
+ return Bubble.new(node) if parent.is_a?(Sass::Tree::MediaNode)
247
+
248
+ yield
249
+
250
+ debubble(node.children, node) do |child|
251
+ next child unless child.is_a?(Sass::Tree::MediaNode)
252
+ # Copies of `node` can be bubbled, and we don't want to merge it with its
253
+ # own query.
254
+ next child if child.resolved_query == node.resolved_query
255
+ next child if child.resolved_query = child.resolved_query.merge(node.resolved_query)
256
+ end
257
+ end
258
+
259
+ # Bubbles the `@supports` directive up through RuleNodes.
260
+ def visit_supports(node)
261
+ return node unless node.has_children
262
+ return bubble(node) if parent.is_a?(Sass::Tree::RuleNode)
263
+
264
+ yield
265
+
266
+ debubble(node.children, node)
267
+ end
268
+
269
+ private
270
+
271
+ # "Bubbles" `node` one level by copying the parent and wrapping `node`'s
272
+ # children with it.
273
+ #
274
+ # @param node [Sass::Tree::Node].
275
+ # @return [Bubble]
276
+ def bubble(node)
277
+ new_rule = parent.dup
278
+ new_rule.children = node.children
279
+ node.children = [new_rule]
280
+ Bubble.new(node)
281
+ end
282
+
283
+ # Pops all bubbles in `children` and intersperses the results with the other
284
+ # values.
285
+ #
286
+ # If `parent` is passed, it's copied and used as the parent node for the
287
+ # nested portions of `children`.
288
+ #
289
+ # @param children [List<Sass::Tree::Node, Bubble>]
290
+ # @param parent [Sass::Tree::Node]
291
+ # @yield [node] An optional block for processing bubbled nodes. Each bubbled
292
+ # node will be passed to this block.
293
+ # @yieldparam node [Sass::Tree::Node] A bubbled node.
294
+ # @yieldreturn [Sass::Tree::Node?] A node to use in place of the bubbled node.
295
+ # This can be the node itself, or `nil` to indicate that the node should be
296
+ # omitted.
297
+ # @return [List<Sass::Tree::Node, Bubble>]
298
+ def debubble(children, parent = nil)
299
+ # Keep track of the previous parent so that we don't divide `parent`
300
+ # unnecessarily if the `@at-root` doesn't produce any new nodes (e.g.
301
+ # `@at-root {@extend %foo}`).
302
+ previous_parent = nil
303
+
304
+ Sass::Util.slice_by(children) {|c| c.is_a?(Bubble)}.map do |(is_bubble, slice)|
305
+ unless is_bubble
306
+ next slice unless parent
307
+ if previous_parent
308
+ previous_parent.children.push(*slice)
309
+ next []
310
+ else
311
+ previous_parent = new_parent = parent.dup
312
+ new_parent.children = slice
313
+ next new_parent
314
+ end
315
+ end
316
+
317
+ slice.map do |bubble|
318
+ next unless (node = block_given? ? yield(bubble.node) : bubble.node)
319
+ node.tabs += bubble.tabs
320
+ node.group_end = bubble.group_end
321
+ results = [visit(node)].flatten
322
+ previous_parent = nil unless results.empty?
323
+ results
324
+ end.compact
325
+ end.flatten
326
+ end
327
+
328
+ # Returns whether or not a node can be bubbled up through the syntax tree.
329
+ #
330
+ # @param node [Sass::Tree::Node]
331
+ # @return [Boolean]
332
+ def bubblable?(node)
333
+ node.is_a?(Sass::Tree::RuleNode) || node.bubbles?
334
+ end
335
+
336
+ # A wrapper class for a node that indicates to the parent that it should
337
+ # treat the wrapped node as a sibling rather than a child.
338
+ #
339
+ # Nodes should be wrapped before they're passed to \{Cssize.visit}. They will
340
+ # be automatically visited upon calling \{#pop}.
341
+ #
342
+ # This duck types as a [Sass::Tree::Node] for the purposes of
343
+ # tree-manipulation operations.
344
+ class Bubble
345
+ attr_accessor :node
346
+ attr_accessor :tabs
347
+ attr_accessor :group_end
348
+
349
+ def initialize(node)
350
+ @node = node
351
+ @tabs = 0
352
+ end
353
+
354
+ def bubbles?
355
+ true
356
+ end
357
+
358
+ def inspect
359
+ "(Bubble #{node.inspect})"
360
+ end
361
+ end
362
+ end
@@ -0,0 +1,107 @@
1
+ # A visitor for copying the full structure of a Sass tree.
2
+ class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base
3
+ protected
4
+
5
+ def visit(node)
6
+ super(node.dup)
7
+ end
8
+
9
+ def visit_children(parent)
10
+ parent.children = parent.children.map {|c| visit(c)}
11
+ parent
12
+ end
13
+
14
+ def visit_debug(node)
15
+ node.expr = node.expr.deep_copy
16
+ yield
17
+ end
18
+
19
+ def visit_error(node)
20
+ node.expr = node.expr.deep_copy
21
+ yield
22
+ end
23
+
24
+ def visit_each(node)
25
+ node.list = node.list.deep_copy
26
+ yield
27
+ end
28
+
29
+ def visit_extend(node)
30
+ node.selector = node.selector.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
31
+ yield
32
+ end
33
+
34
+ def visit_for(node)
35
+ node.from = node.from.deep_copy
36
+ node.to = node.to.deep_copy
37
+ yield
38
+ end
39
+
40
+ def visit_function(node)
41
+ node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
42
+ yield
43
+ end
44
+
45
+ def visit_if(node)
46
+ node.expr = node.expr.deep_copy if node.expr
47
+ node.else = visit(node.else) if node.else
48
+ yield
49
+ end
50
+
51
+ def visit_mixindef(node)
52
+ node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]}
53
+ yield
54
+ end
55
+
56
+ def visit_mixin(node)
57
+ node.args = node.args.map {|a| a.deep_copy}
58
+ node.keywords = Sass::Util::NormalizedMap.new(Hash[node.keywords.map {|k, v| [k, v.deep_copy]}])
59
+ yield
60
+ end
61
+
62
+ def visit_prop(node)
63
+ node.name = node.name.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
64
+ node.value = node.value.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
65
+ yield
66
+ end
67
+
68
+ def visit_return(node)
69
+ node.expr = node.expr.deep_copy
70
+ yield
71
+ end
72
+
73
+ def visit_rule(node)
74
+ node.rule = node.rule.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
75
+ yield
76
+ end
77
+
78
+ def visit_variable(node)
79
+ node.expr = node.expr.deep_copy
80
+ yield
81
+ end
82
+
83
+ def visit_warn(node)
84
+ node.expr = node.expr.deep_copy
85
+ yield
86
+ end
87
+
88
+ def visit_while(node)
89
+ node.expr = node.expr.deep_copy
90
+ yield
91
+ end
92
+
93
+ def visit_directive(node)
94
+ node.value = node.value.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
95
+ yield
96
+ end
97
+
98
+ def visit_media(node)
99
+ node.query = node.query.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}
100
+ yield
101
+ end
102
+
103
+ def visit_supports(node)
104
+ node.condition = node.condition.deep_copy
105
+ yield
106
+ end
107
+ end
@@ -0,0 +1,64 @@
1
+ # A visitor for performing selector inheritance on a static CSS tree.
2
+ #
3
+ # Destructively modifies the tree.
4
+ class Sass::Tree::Visitors::Extend < Sass::Tree::Visitors::Base
5
+ # Performs the given extensions on the static CSS tree based in `root`, then
6
+ # validates that all extends matched some selector.
7
+ #
8
+ # @param root [Tree::Node] The root node of the tree to visit.
9
+ # @param extends [Sass::Util::SubsetMap{Selector::Simple =>
10
+ # Sass::Tree::Visitors::Cssize::Extend}]
11
+ # The extensions to perform on this tree.
12
+ # @return [Object] The return value of \{#visit} for the root node.
13
+ def self.visit(root, extends)
14
+ return if extends.empty?
15
+ new(extends).send(:visit, root)
16
+ check_extends_fired! extends
17
+ end
18
+
19
+ protected
20
+
21
+ def initialize(extends)
22
+ @parent_directives = []
23
+ @extends = extends
24
+ end
25
+
26
+ # If an exception is raised, this adds proper metadata to the backtrace.
27
+ def visit(node)
28
+ super(node)
29
+ rescue Sass::SyntaxError => e
30
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
31
+ raise e
32
+ end
33
+
34
+ # Keeps track of the current parent directives.
35
+ def visit_children(parent)
36
+ @parent_directives.push parent if parent.is_a?(Sass::Tree::DirectiveNode)
37
+ super
38
+ ensure
39
+ @parent_directives.pop if parent.is_a?(Sass::Tree::DirectiveNode)
40
+ end
41
+
42
+ # Applies the extend to a single rule's selector.
43
+ def visit_rule(node)
44
+ node.resolved_rules = node.resolved_rules.do_extend(@extends, @parent_directives)
45
+ end
46
+
47
+ class << self
48
+ private
49
+
50
+ def check_extends_fired!(extends)
51
+ extends.each_value do |ex|
52
+ next if ex.success || ex.node.optional?
53
+ message = "\"#{ex.extender}\" failed to @extend \"#{ex.target.join}\"."
54
+
55
+ # TODO(nweiz): this should use the Sass stack trace of the extend node.
56
+ raise Sass::SyntaxError.new(<<MESSAGE, :filename => ex.node.filename, :line => ex.node.line)
57
+ #{message}
58
+ The selector "#{ex.target.join}" was not found.
59
+ Use "@extend #{ex.target.join} !optional" if the extend should be able to fail.
60
+ MESSAGE
61
+ end
62
+ end
63
+ end
64
+ end