haml-edge 2.3.209 → 2.3.210

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 (82) hide show
  1. data/.yardopts +2 -0
  2. data/EDGE_GEM_VERSION +1 -1
  3. data/Rakefile +24 -2
  4. data/VERSION +1 -1
  5. data/lib/haml/exec.rb +11 -4
  6. data/lib/haml/filters.rb +3 -0
  7. data/lib/haml/helpers/action_view_extensions.rb +4 -2
  8. data/lib/haml/helpers/action_view_mods.rb +6 -4
  9. data/lib/haml/helpers.rb +2 -10
  10. data/lib/haml/html.rb +0 -1
  11. data/lib/haml/precompiler.rb +37 -30
  12. data/lib/haml/railtie.rb +6 -2
  13. data/lib/haml/root.rb +4 -0
  14. data/lib/haml/template.rb +2 -0
  15. data/lib/haml/util/subset_map.rb +101 -0
  16. data/lib/haml/util.rb +74 -0
  17. data/lib/haml.rb +5 -2
  18. data/lib/sass/engine.rb +36 -31
  19. data/lib/sass/files.rb +1 -1
  20. data/lib/sass/plugin/staleness_checker.rb +9 -9
  21. data/lib/sass/plugin.rb +21 -0
  22. data/lib/sass/script/color.rb +4 -3
  23. data/lib/sass/script/css_lexer.rb +11 -1
  24. data/lib/sass/script/css_parser.rb +4 -1
  25. data/lib/sass/script/funcall.rb +9 -0
  26. data/lib/sass/script/interpolation.rb +21 -0
  27. data/lib/sass/script/lexer.rb +30 -13
  28. data/lib/sass/script/node.rb +1 -1
  29. data/lib/sass/script/number.rb +4 -5
  30. data/lib/sass/script/parser.rb +13 -14
  31. data/lib/sass/script/string.rb +8 -2
  32. data/lib/sass/script/string_interpolation.rb +27 -4
  33. data/lib/sass/script.rb +1 -2
  34. data/lib/sass/scss/css_parser.rb +5 -3
  35. data/lib/sass/scss/parser.rb +146 -64
  36. data/lib/sass/scss/rx.rb +9 -1
  37. data/lib/sass/scss/sass_parser.rb +11 -0
  38. data/lib/sass/scss/script_lexer.rb +2 -0
  39. data/lib/sass/scss/static_parser.rb +48 -0
  40. data/lib/sass/scss.rb +3 -0
  41. data/lib/sass/selector/abstract_sequence.rb +40 -0
  42. data/lib/sass/selector/comma_sequence.rb +80 -0
  43. data/lib/sass/selector/sequence.rb +194 -0
  44. data/lib/sass/selector/simple.rb +107 -0
  45. data/lib/sass/selector/simple_sequence.rb +161 -0
  46. data/lib/sass/selector.rb +353 -0
  47. data/lib/sass/tree/comment_node.rb +1 -0
  48. data/lib/sass/tree/debug_node.rb +1 -0
  49. data/lib/sass/tree/directive_node.rb +1 -0
  50. data/lib/sass/tree/extend_node.rb +60 -0
  51. data/lib/sass/tree/for_node.rb +1 -0
  52. data/lib/sass/tree/if_node.rb +2 -0
  53. data/lib/sass/tree/import_node.rb +2 -0
  54. data/lib/sass/tree/mixin_def_node.rb +1 -0
  55. data/lib/sass/tree/mixin_node.rb +21 -5
  56. data/lib/sass/tree/node.rb +59 -12
  57. data/lib/sass/tree/prop_node.rb +20 -21
  58. data/lib/sass/tree/root_node.rb +8 -17
  59. data/lib/sass/tree/rule_node.rb +49 -100
  60. data/lib/sass/tree/variable_node.rb +1 -0
  61. data/lib/sass/tree/warn_node.rb +1 -0
  62. data/lib/sass/tree/while_node.rb +1 -0
  63. data/lib/sass.rb +1 -0
  64. data/test/haml/engine_test.rb +185 -3
  65. data/test/haml/helper_test.rb +25 -2
  66. data/test/haml/template_test.rb +2 -2
  67. data/test/haml/templates/helpers.haml +13 -0
  68. data/test/haml/util/subset_map_test.rb +91 -0
  69. data/test/haml/util_test.rb +25 -0
  70. data/test/sass/conversion_test.rb +23 -3
  71. data/test/sass/engine_test.rb +50 -7
  72. data/test/sass/extend_test.rb +1045 -0
  73. data/test/sass/results/complex.css +0 -1
  74. data/test/sass/results/script.css +1 -1
  75. data/test/sass/script_conversion_test.rb +16 -0
  76. data/test/sass/script_test.rb +37 -4
  77. data/test/sass/scss/css_test.rb +17 -3
  78. data/test/sass/scss/rx_test.rb +1 -1
  79. data/test/sass/scss/scss_test.rb +30 -0
  80. data/test/sass/templates/complex.sass +0 -2
  81. data/test/test_helper.rb +5 -0
  82. metadata +17 -3
@@ -0,0 +1,353 @@
1
+ require 'sass/selector/simple'
2
+ require 'sass/selector/abstract_sequence'
3
+ require 'sass/selector/comma_sequence'
4
+ require 'sass/selector/sequence'
5
+ require 'sass/selector/simple_sequence'
6
+
7
+ module Sass
8
+ # A namespace for nodes in the parse tree for selectors.
9
+ #
10
+ # {CommaSequence} is the toplevel seelctor,
11
+ # representing a comma-separated sequence of {Sequence}s,
12
+ # such as `foo bar, baz bang`.
13
+ # {Sequence} is the next level,
14
+ # representing {SimpleSequence}s separated by combinators (e.g. descendant or child),
15
+ # such as `foo bar` or `foo > bar baz`.
16
+ # {SimpleSequence} is a sequence of selectors that all apply to a single element,
17
+ # such as `foo.bar[attr=val]`.
18
+ # Finally, {Simple} is the superclass of the simplest selectors,
19
+ # such as `.foo` or `#bar`.
20
+ module Selector
21
+ # A parent-referencing selector (`&` in Sass).
22
+ # The function of this is to be replaced by the parent selector
23
+ # in the nested hierarchy.
24
+ class Parent < Simple
25
+ # @see Selector#to_a
26
+ def to_a
27
+ ["&"]
28
+ end
29
+
30
+ # Always raises an exception.
31
+ #
32
+ # @raise [Sass::SyntaxError] Parent selectors should be resolved before unification
33
+ # @see Selector#unify
34
+ def unify(sels)
35
+ raise Sass::SyntaxError.new("[BUG] Cannot unify parent selectors.")
36
+ end
37
+ end
38
+
39
+ # A class selector (e.g. `.foo`).
40
+ class Class < Simple
41
+ # The class name.
42
+ #
43
+ # @return [Array<String, Sass::Script::Node>]
44
+ attr_reader :name
45
+
46
+ # @param name [Array<String, Sass::Script::Node>] The class name
47
+ def initialize(name)
48
+ @name = name
49
+ end
50
+
51
+ # @see Selector#to_a
52
+ def to_a
53
+ [".", *@name]
54
+ end
55
+ end
56
+
57
+ # An id selector (e.g. `#foo`).
58
+ class Id < Simple
59
+ # The id name.
60
+ #
61
+ # @return [Array<String, Sass::Script::Node>]
62
+ attr_reader :name
63
+
64
+ # @param name [Array<String, Sass::Script::Node>] The id name
65
+ def initialize(name)
66
+ @name = name
67
+ end
68
+
69
+ # @see Selector#to_a
70
+ def to_a
71
+ ["#", *@name]
72
+ end
73
+
74
+ # Returns `nil` if `sels` contains an {Id} selector
75
+ # with a different name than this one.
76
+ #
77
+ # @see Selector#unify
78
+ def unify(sels)
79
+ return if sels.any? {|sel2| sel2.is_a?(Id) && self.name != sel2.name}
80
+ super
81
+ end
82
+ end
83
+
84
+ # A universal selector (`*` in CSS).
85
+ class Universal < Simple
86
+ # The selector namespace.
87
+ # `nil` means the default namespace,
88
+ # `[""]` means no namespace,
89
+ # `["*"]` means any namespace.
90
+ #
91
+ # @return [Array<String, Sass::Script::Node>, nil]
92
+ attr_reader :namespace
93
+
94
+ # @param namespace [Array<String, Sass::Script::Node>, nil] See \{#namespace}
95
+ def initialize(namespace)
96
+ @namespace = namespace
97
+ end
98
+
99
+ # @see Selector#to_a
100
+ def to_a
101
+ @namespace ? @namespace + ["|*"] : ["*"]
102
+ end
103
+
104
+ # Unification of a universal selector is somewhat complicated,
105
+ # especially when a namespace is specified.
106
+ # If there is no namespace specified
107
+ # or any namespace is specified (namespace `"*"`),
108
+ # then `sel` is returned without change
109
+ # (unless it's empty, in which case `"*"` is required).
110
+ #
111
+ # If a namespace is specified
112
+ # but `sel` does not specify a namespace,
113
+ # then the given namespace is applied to `sel`,
114
+ # either by adding this {Universal} selector
115
+ # or applying this namespace to an existing {Element} selector.
116
+ #
117
+ # If both this selector *and* `sel` specify namespaces,
118
+ # those namespaces are unified via {Simple#unify_namespaces}
119
+ # and the unified namespace is used, if possible.
120
+ #
121
+ # @todo There are lots of cases that this documentation specifies;
122
+ # make sure we thoroughly test **all of them**.
123
+ # @todo Keep track of whether a default namespace has been declared
124
+ # and handle namespace-unspecified selectors accordingly.
125
+ # @todo If any branch of a CommaSequence ends up being just `"*"`,
126
+ # then all other branches should be eliminated
127
+ #
128
+ # @see Selector#unify
129
+ def unify(sels)
130
+ name =
131
+ case sels.first
132
+ when Universal; :universal
133
+ when Element; sels.first.name
134
+ else
135
+ return [self] + sels unless namespace.nil? || namespace == ['*']
136
+ return sels unless sels.empty?
137
+ return [self]
138
+ end
139
+
140
+ ns, accept = unify_namespaces(namespace, sels.first.namespace)
141
+ return unless accept
142
+ [name == :universal ? Universal.new(ns) : Element.new(name, ns)] + sels[1..-1]
143
+ end
144
+ end
145
+
146
+ # An element selector (e.g. `h1`).
147
+ class Element < Simple
148
+ # The element name.
149
+ #
150
+ # @return [Array<String, Sass::Script::Node>]
151
+ attr_reader :name
152
+
153
+ # The selector namespace.
154
+ # `nil` means the default namespace,
155
+ # `[""]` means no namespace,
156
+ # `["*"]` means any namespace.
157
+ #
158
+ # @return [Array<String, Sass::Script::Node>, nil]
159
+ attr_reader :namespace
160
+
161
+ # @param name [Array<String, Sass::Script::Node>] The element name
162
+ # @param namespace [Array<String, Sass::Script::Node>, nil] See \{#namespace}
163
+ def initialize(name, namespace)
164
+ @name = name
165
+ @namespace = namespace
166
+ end
167
+
168
+ # @see Selector#to_a
169
+ def to_a
170
+ @namespace ? @namespace + ["|"] + @name : @name
171
+ end
172
+
173
+ # Unification of an element selector is somewhat complicated,
174
+ # especially when a namespace is specified.
175
+ # First, if `sel` contains another {Element} with a different \{#name},
176
+ # then the selectors can't be unified and `nil` is returned.
177
+ #
178
+ # Otherwise, if `sel` doesn't specify a namespace,
179
+ # or it specifies any namespace (via `"*"`),
180
+ # then it's returned with this element selector
181
+ # (e.g. `.foo` becomes `a.foo` or `svg|a.foo`).
182
+ # Similarly, if this selector doesn't specify a namespace,
183
+ # the namespace from `sel` is used.
184
+ #
185
+ # If both this selector *and* `sel` specify namespaces,
186
+ # those namespaces are unified via {Simple#unify_namespaces}
187
+ # and the unified namespace is used, if possible.
188
+ #
189
+ # @todo There are lots of cases that this documentation specifies;
190
+ # make sure we thoroughly test **all of them**.
191
+ # @todo Keep track of whether a default namespace has been declared
192
+ # and handle namespace-unspecified selectors accordingly.
193
+ #
194
+ # @see Selector#unify
195
+ def unify(sels)
196
+ case sels.first
197
+ when Universal;
198
+ when Element; return unless name == sels.first.name
199
+ else return [self] + sels
200
+ end
201
+
202
+ ns, accept = unify_namespaces(namespace, sels.first.namespace)
203
+ return unless accept
204
+ [Element.new(name, ns)] + sels[1..-1]
205
+ end
206
+ end
207
+
208
+ # Selector interpolation (`#{}` in Sass).
209
+ class Interpolation < Simple
210
+ # The script to run.
211
+ #
212
+ # @return [Sass::Script::Node]
213
+ attr_reader :script
214
+
215
+ # @param script [Sass::Script::Node] The script to run
216
+ def initialize(script)
217
+ @script = script
218
+ end
219
+
220
+ # @see Selector#to_a
221
+ def to_a
222
+ [@script]
223
+ end
224
+
225
+ # Always raises an exception.
226
+ #
227
+ # @raise [Sass::SyntaxError] Interpolation selectors should be resolved before unification
228
+ # @see Selector#unify
229
+ def unify(sels)
230
+ raise Sass::SyntaxError.new("[BUG] Cannot unify interpolation selectors.")
231
+ end
232
+ end
233
+
234
+ # An attribute selector (e.g. `[href^="http://"]`).
235
+ class Attribute < Simple
236
+ # The attribute name.
237
+ #
238
+ # @return [Array<String, Sass::Script::Node>]
239
+ attr_reader :name
240
+
241
+ # The attribute namespace.
242
+ # `nil` means the default namespace,
243
+ # `[""]` means no namespace,
244
+ # `["*"]` means any namespace.
245
+ #
246
+ # @return [Array<String, Sass::Script::Node>, nil]
247
+ attr_reader :namespace
248
+
249
+ # The matching operator, e.g. `"="` or `"^="`.
250
+ #
251
+ # @return [String]
252
+ attr_reader :operator
253
+
254
+ # The right-hand side of the operator.
255
+ #
256
+ # @return [Array<String, Sass::Script::Node>]
257
+ attr_reader :value
258
+
259
+ # @param name [Array<String, Sass::Script::Node>] The attribute name
260
+ # @param namespace [Array<String, Sass::Script::Node>, nil] See \{#namespace}
261
+ # @param operator [String] The matching operator, e.g. `"="` or `"^="`
262
+ # @param value [Array<String, Sass::Script::Node>] See \{#value}
263
+ def initialize(name, namespace, operator, value)
264
+ @name = name
265
+ @namespace = namespace
266
+ @operator = operator
267
+ @value = value
268
+ end
269
+
270
+ # @see Selector#to_a
271
+ def to_a
272
+ res = ["["]
273
+ res.concat(@namespace) << "|" if @namespace
274
+ res.concat @name
275
+ (res << @operator).concat @value if @value
276
+ res << "]"
277
+ end
278
+ end
279
+
280
+ # A pseudoclass (e.g. `:visited`) or pseudoelement (e.g. `::first-line`) selector.
281
+ # It can have arguments (e.g. `:nth-child(2n+1)`).
282
+ class Pseudo < Simple
283
+ # The type of the selector.
284
+ # `:class` if this is a pseudoclass selector,
285
+ # `:element` if it's a pseudoelement.
286
+ #
287
+ # @return [Symbol]
288
+ attr_reader :type
289
+
290
+ # The name of the selector.
291
+ #
292
+ # @return [Array<String, Sass::Script::Node>]
293
+ attr_reader :name
294
+
295
+ # The argument to the selector,
296
+ # or `nil` if no argument was given.
297
+ #
298
+ # This may include SassScript nodes that will be run during resolution.
299
+ # Note that this should not include SassScript nodes
300
+ # after resolution has taken place.
301
+ #
302
+ # @return [Array<String, Sass::Script::Node>, nil]
303
+ attr_reader :arg
304
+
305
+ # @param type [Symbol] See \{#type}
306
+ # @param name [Array<String, Sass::Script::Node>] The name of the selector
307
+ # @param arg [nil, Array<String, Sass::Script::Node>] The argument to the selector,
308
+ # or nil if no argument was given
309
+ def initialize(type, name, arg)
310
+ @type = type
311
+ @name = name
312
+ @arg = arg
313
+ end
314
+
315
+ # @see Selector#to_a
316
+ def to_a
317
+ res = [@type == :class ? ":" : "::"] + @name
318
+ (res << "(").concat(Haml::Util.strip_string_array(@arg)) << ")" if @arg
319
+ res
320
+ end
321
+
322
+ # Returns `nil` if this is a pseudoclass selector
323
+ # and `sels` contains a pseudoclass selector different than this one.
324
+ #
325
+ # @see Selector#unify
326
+ def unify(sels)
327
+ return if type == :element && sels.any? do |sel|
328
+ sel.is_a?(Pseudo) && sel.type == :element &&
329
+ (sel.name != self.name || sel.arg != self.arg)
330
+ end
331
+ super
332
+ end
333
+ end
334
+
335
+ # A negation pseudoclass selector (e.g. `:not(.foo)`).
336
+ class Negation < Simple
337
+ # The selector to negate.
338
+ #
339
+ # @return [Selector]
340
+ attr_reader :selector
341
+
342
+ # @param [Selector] The selector to negate
343
+ def initialize(selector)
344
+ @selector = selector
345
+ end
346
+
347
+ # @see Selector#to_a
348
+ def to_a
349
+ [":not("] + @selector.to_a + [")"]
350
+ end
351
+ end
352
+ end
353
+ end
@@ -72,6 +72,7 @@ module Sass::Tree
72
72
  content.rstrip + "\n"
73
73
  end
74
74
 
75
+ # @see Node#to_scss
75
76
  def to_scss(tabs, opts = {})
76
77
  spaces = (' ' * [tabs - value[/^ */].size, 0].max)
77
78
  if silent
@@ -12,6 +12,7 @@ module Sass
12
12
 
13
13
  protected
14
14
 
15
+ # @see Node#to_src
15
16
  def to_src(tabs, opts, fmt)
16
17
  "#{' ' * tabs}@debug #{@expr.to_sass(opts)}#{semi fmt}\n"
17
18
  end
@@ -22,6 +22,7 @@ module Sass::Tree
22
22
 
23
23
  protected
24
24
 
25
+ # @see Node#to_src
25
26
  def to_src(tabs, opts, fmt)
26
27
  res = "#{' ' * tabs}#{value}"
27
28
  return res + "#{semi fmt}\n" if children.empty?
@@ -0,0 +1,60 @@
1
+ require 'sass/tree/node'
2
+
3
+ module Sass::Tree
4
+ # A static node reprenting an `@extend` directive.
5
+ #
6
+ # @see Sass::Tree
7
+ class ExtendNode < Node
8
+ # @param selector [Array<String, Sass::Script::Node>]
9
+ # The CSS selector to extend,
10
+ # interspersed with {Sass::Script::Node}s
11
+ # representing `#{}`-interpolation.
12
+ def initialize(selector)
13
+ @selector = selector
14
+ super()
15
+ end
16
+
17
+ # Registers this extension in the `extends` subset map.
18
+ #
19
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
20
+ # The extensions defined for this tree
21
+ # @param parent [RuleNode] The parent node of this node
22
+ # @see Node#cssize
23
+ def cssize(extends, parent)
24
+ @resolved_selector.members.each do |seq|
25
+ if seq.members.size > 1
26
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: can't extend nested selectors")
27
+ end
28
+
29
+ sseq = seq.members.first
30
+ if !sseq.is_a?(Sass::Selector::SimpleSequence)
31
+ raise Sass::SyntaxError.new("Can't extend #{seq.to_a.join}: invalid selector")
32
+ end
33
+
34
+ sel = sseq.members
35
+ parent.resolved_rules.members.each do |seq|
36
+ if !seq.members.last.is_a?(Sass::Selector::SimpleSequence)
37
+ raise Sass::SyntaxError.new("#{seq} can't extend: invalid selector")
38
+ end
39
+
40
+ extends[sel] = seq
41
+ end
42
+ end
43
+
44
+ []
45
+ end
46
+
47
+ protected
48
+
49
+ # Runs SassScript interpolation in the selector,
50
+ # and then parses the result into a {Sass::Selector::CommaSequence}.
51
+ #
52
+ # @param environment [Sass::Environment] The lexical environment containing
53
+ # variable and mixin values
54
+ def perform!(environment)
55
+ @resolved_selector = Sass::SCSS::CssParser.new(run_interp(@selector, environment)).
56
+ parse_selector(self.line, self.filename)
57
+ super
58
+ end
59
+ end
60
+ end
@@ -20,6 +20,7 @@ module Sass::Tree
20
20
 
21
21
  protected
22
22
 
23
+ # @see Node#to_src
23
24
  def to_src(tabs, opts, fmt)
24
25
  to = @exclusive ? "to" : "through"
25
26
  "#{' ' * tabs}@for $#{dasherize(@var, opts)} from #{@from.to_sass(opts)} #{to} #{@to.to_sass(opts)}" +
@@ -30,6 +30,7 @@ module Sass::Tree
30
30
  @last_else = node
31
31
  end
32
32
 
33
+ # @see Node#options=
33
34
  def options=(options)
34
35
  super
35
36
  self.else.options = options if self.else
@@ -37,6 +38,7 @@ module Sass::Tree
37
38
 
38
39
  protected
39
40
 
41
+ # @see Node#to_src
40
42
  def to_src(tabs, opts, fmt, is_else = false)
41
43
  name =
42
44
  if !is_else; "if"
@@ -28,10 +28,12 @@ module Sass
28
28
  @full_filename ||= import
29
29
  end
30
30
 
31
+ # @see Node#to_sass
31
32
  def to_sass(tabs = 0, opts = {})
32
33
  "#{' ' * tabs}@import #{@imported_filename}\n"
33
34
  end
34
35
 
36
+ # @see Node#to_scss
35
37
  def to_scss(tabs = 0, opts = {})
36
38
  "#{' ' * tabs}@import \"#{@imported_filename}\";\n"
37
39
  end
@@ -16,6 +16,7 @@ module Sass
16
16
 
17
17
  protected
18
18
 
19
+ # @see Node#to_src
19
20
  def to_src(tabs, opts, fmt)
20
21
  args =
21
22
  if @args.empty?
@@ -22,22 +22,38 @@ module Sass::Tree
22
22
  end
23
23
 
24
24
  # @see Node#cssize
25
- def cssize(parent = nil)
26
- _cssize(parent) # Pass on the parent even if it's not a MixinNode
25
+ def cssize(extends, parent = nil)
26
+ _cssize(extends, parent) # Pass on the parent even if it's not a MixinNode
27
27
  end
28
28
 
29
29
  protected
30
30
 
31
+ # Returns an error message if the given child node is invalid,
32
+ # and false otherwise.
33
+ #
34
+ # {ExtendNode}s are valid within {MixinNode}s.
35
+ #
36
+ # @param child [Tree::Node] A potential child node
37
+ # @return [Boolean, String] Whether or not the child node is valid,
38
+ # as well as the error message to display if it is invalid
39
+ def invalid_child?(child)
40
+ super unless child.is_a?(ExtendNode)
41
+ end
42
+
43
+ # @see Node#to_src
31
44
  def to_src(tabs, opts, fmt)
32
45
  args = '(' + @args.map {|a| a.to_sass(opts)}.join(", ") + ')' unless @args.empty?
33
46
  "#{' ' * tabs}#{fmt == :sass ? '+' : '@include '}#{dasherize(@name, opts)}#{args}#{semi fmt}\n"
34
47
  end
35
48
 
36
49
  # @see Node#_cssize
37
- def _cssize(parent)
38
- children.map {|c| c.cssize(parent)}.flatten
50
+ def _cssize(extends, parent)
51
+ children.map do |c|
52
+ parent.check_child! c
53
+ c.cssize(extends, parent)
54
+ end.flatten
39
55
  rescue Sass::SyntaxError => e
40
- e.modify_backtrace(:mixin => @name, :line => line)
56
+ e.modify_backtrace(:mixin => @name, :filename => filename, :line => line)
41
57
  e.add_backtrace(:filename => filename, :line => line)
42
58
  raise e
43
59
  end
@@ -89,11 +89,20 @@ module Sass
89
89
  # @see #invalid_child?
90
90
  def <<(child)
91
91
  return if child.nil?
92
+ check_child! child
93
+ self.has_children = true
94
+ @children << child
95
+ end
96
+
97
+ # Raises an error if the given child node is invalid.
98
+ #
99
+ # @param child [Tree::Node] The child node
100
+ # @raise [Sass::SyntaxError] if `child` is invalid
101
+ # @see #invalid_child?
102
+ def check_child!(child)
92
103
  if msg = invalid_child?(child)
93
104
  raise Sass::SyntaxError.new(msg, :line => child.line)
94
105
  end
95
- self.has_children = true
96
- @children << child
97
106
  end
98
107
 
99
108
  # Compares this node and another object (only other {Tree::Node}s will be equal).
@@ -116,7 +125,10 @@ module Sass
116
125
  # @see #perform
117
126
  # @see #to_s
118
127
  def render
119
- perform(Environment.new).cssize.to_s
128
+ extends = Haml::Util::SubsetMap.new
129
+ result = perform(Environment.new).cssize(extends)
130
+ result = result.do_extend(extends) unless extends.empty?
131
+ result.to_s
120
132
  end
121
133
 
122
134
  # True if \{#to\_s} will return `nil`;
@@ -150,19 +162,42 @@ module Sass
150
162
  raise e
151
163
  end
152
164
 
165
+ # Converts a static CSS tree (e.g. the output of \{#cssize})
166
+ # into another static CSS tree,
167
+ # with the given extensions applied to all relevant {RuleNode}s.
168
+ #
169
+ # @todo Link this to the reference documentation on `@extend`
170
+ # when such a thing exists.
171
+ #
172
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
173
+ # The extensions to perform on this tree
174
+ # @return [Tree::Node] The resulting tree of static CSS nodes.
175
+ # @raise [Sass::SyntaxError] Only if there's a programmer error
176
+ # and this is not a static CSS tree
177
+ def do_extend(extends)
178
+ node = dup
179
+ node.children = children.map {|c| c.do_extend(extends)}
180
+ node
181
+ rescue Sass::SyntaxError => e
182
+ e.modify_backtrace(:filename => filename, :line => line)
183
+ raise e
184
+ end
185
+
153
186
  # Converts a static Sass tree (e.g. the output of \{#perform})
154
187
  # into a static CSS tree.
155
188
  #
156
189
  # \{#cssize} shouldn't be overridden directly;
157
190
  # instead, override \{#\_cssize} or \{#cssize!}.
158
191
  #
192
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
193
+ # The extensions defined for this tree
159
194
  # @param parent [Node, nil] The parent node of this node.
160
195
  # This should only be non-nil if the parent is the same class as this node
161
196
  # @return [Tree::Node] The resulting tree of static nodes
162
197
  # @raise [Sass::SyntaxError] if some element of the tree is invalid
163
198
  # @see Sass::Tree
164
- def cssize(parent = nil)
165
- _cssize((parent if parent.class == self.class))
199
+ def cssize(extends, parent = nil)
200
+ _cssize(extends, (parent if parent.class == self.class))
166
201
  rescue Sass::SyntaxError => e
167
202
  e.modify_backtrace(:filename => filename, :line => line)
168
203
  raise e
@@ -237,15 +272,17 @@ module Sass
237
272
  # returning the new node.
238
273
  # This doesn't modify this node or any of its children.
239
274
  #
275
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
276
+ # The extensions defined for this tree
240
277
  # @param parent [Node, nil] The parent node of this node.
241
278
  # This should only be non-nil if the parent is the same class as this node
242
279
  # @return [Tree::Node, Array<Tree::Node>] The resulting static CSS nodes
243
280
  # @raise [Sass::SyntaxError] if some element of the tree is invalid
244
281
  # @see #cssize
245
282
  # @see Sass::Tree
246
- def _cssize(parent)
283
+ def _cssize(extends, parent)
247
284
  node = dup
248
- node.cssize!(parent)
285
+ node.cssize!(extends, parent)
249
286
  node
250
287
  end
251
288
 
@@ -253,11 +290,13 @@ module Sass
253
290
  # This *does* modify this node,
254
291
  # but will be run non-destructively by \{#\_cssize\}.
255
292
  #
293
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
294
+ # The extensions defined for this tree
256
295
  # @param parent [Node, nil] The parent node of this node.
257
296
  # This should only be non-nil if the parent is the same class as this node
258
297
  # @see #cssize
259
- def cssize!(parent)
260
- self.children = children.map {|c| c.cssize(self)}.flatten
298
+ def cssize!(extends, parent)
299
+ self.children = children.map {|c| c.cssize(extends, self)}.flatten
261
300
  end
262
301
 
263
302
  # Runs any dynamic Sass code in this particular node.
@@ -322,8 +361,8 @@ module Sass
322
361
  # Returns an error message if the given child node is invalid,
323
362
  # and false otherwise.
324
363
  #
325
- # By default, all child nodes except those only allowed at root level
326
- # ({Tree::MixinDefNode}, {Tree::ImportNode}) are valid.
364
+ # By default, all child nodes except those only allowed under specific nodes
365
+ # ({Tree::MixinDefNode}, {Tree::ImportNode}, {Tree::ExtendNode}) are valid.
327
366
  # This is expected to be overriden by subclasses
328
367
  # for which some children are invalid.
329
368
  #
@@ -336,6 +375,8 @@ module Sass
336
375
  "Mixins may only be defined at the root of a document."
337
376
  when Tree::ImportNode
338
377
  "Import directives may only be used at the root of a document."
378
+ when Tree::ExtendNode
379
+ "Extend directives may only be used within rules."
339
380
  end
340
381
  end
341
382
 
@@ -366,9 +407,15 @@ module Sass
366
407
  (fmt == :sass ? "\n" : " }\n")
367
408
  end
368
409
 
410
+ # Convert any underscores in a string into hyphens,
411
+ # but only if the `:dasherize` option is set.
412
+ #
413
+ # @param s [String] The string to convert
414
+ # @param opts [{Symbol => Object}] The options hash
415
+ # @return [String] The converted string
369
416
  def dasherize(s, opts)
370
417
  if opts[:dasherize]
371
- s.gsub(/_/,'-')
418
+ s.gsub('_', '-')
372
419
  else
373
420
  s
374
421
  end