haml 3.0.0.beta.3 → 3.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (82) hide show
  1. data/.yardopts +2 -0
  2. data/REMEMBER +4 -11
  3. data/Rakefile +24 -2
  4. data/VERSION +1 -1
  5. data/lib/haml.rb +5 -2
  6. data/lib/haml/exec.rb +11 -4
  7. data/lib/haml/filters.rb +3 -0
  8. data/lib/haml/helpers.rb +2 -10
  9. data/lib/haml/helpers/action_view_extensions.rb +4 -2
  10. data/lib/haml/helpers/action_view_mods.rb +6 -4
  11. data/lib/haml/html.rb +0 -1
  12. data/lib/haml/precompiler.rb +37 -30
  13. data/lib/haml/railtie.rb +6 -2
  14. data/lib/haml/root.rb +4 -0
  15. data/lib/haml/template.rb +2 -0
  16. data/lib/haml/util.rb +74 -0
  17. data/lib/haml/util/subset_map.rb +101 -0
  18. data/lib/sass.rb +1 -0
  19. data/lib/sass/engine.rb +36 -31
  20. data/lib/sass/files.rb +1 -1
  21. data/lib/sass/plugin.rb +21 -0
  22. data/lib/sass/plugin/staleness_checker.rb +9 -9
  23. data/lib/sass/script.rb +1 -2
  24. data/lib/sass/script/color.rb +4 -3
  25. data/lib/sass/script/css_lexer.rb +11 -1
  26. data/lib/sass/script/css_parser.rb +4 -1
  27. data/lib/sass/script/funcall.rb +9 -0
  28. data/lib/sass/script/interpolation.rb +21 -0
  29. data/lib/sass/script/lexer.rb +30 -13
  30. data/lib/sass/script/node.rb +1 -1
  31. data/lib/sass/script/number.rb +4 -5
  32. data/lib/sass/script/parser.rb +13 -14
  33. data/lib/sass/script/string.rb +8 -2
  34. data/lib/sass/script/string_interpolation.rb +27 -4
  35. data/lib/sass/scss.rb +3 -0
  36. data/lib/sass/scss/css_parser.rb +5 -3
  37. data/lib/sass/scss/parser.rb +146 -64
  38. data/lib/sass/scss/rx.rb +9 -1
  39. data/lib/sass/scss/sass_parser.rb +11 -0
  40. data/lib/sass/scss/script_lexer.rb +2 -0
  41. data/lib/sass/scss/static_parser.rb +48 -0
  42. data/lib/sass/selector.rb +353 -0
  43. data/lib/sass/selector/abstract_sequence.rb +40 -0
  44. data/lib/sass/selector/comma_sequence.rb +80 -0
  45. data/lib/sass/selector/sequence.rb +194 -0
  46. data/lib/sass/selector/simple.rb +107 -0
  47. data/lib/sass/selector/simple_sequence.rb +161 -0
  48. data/lib/sass/tree/comment_node.rb +1 -0
  49. data/lib/sass/tree/debug_node.rb +1 -0
  50. data/lib/sass/tree/directive_node.rb +1 -0
  51. data/lib/sass/tree/extend_node.rb +60 -0
  52. data/lib/sass/tree/for_node.rb +1 -0
  53. data/lib/sass/tree/if_node.rb +2 -0
  54. data/lib/sass/tree/import_node.rb +2 -0
  55. data/lib/sass/tree/mixin_def_node.rb +1 -0
  56. data/lib/sass/tree/mixin_node.rb +21 -5
  57. data/lib/sass/tree/node.rb +59 -12
  58. data/lib/sass/tree/prop_node.rb +20 -21
  59. data/lib/sass/tree/root_node.rb +8 -17
  60. data/lib/sass/tree/rule_node.rb +49 -100
  61. data/lib/sass/tree/variable_node.rb +1 -0
  62. data/lib/sass/tree/warn_node.rb +1 -0
  63. data/lib/sass/tree/while_node.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 +18 -4
@@ -0,0 +1,80 @@
1
+ module Sass
2
+ module Selector
3
+ # A comma-separated sequence of selectors.
4
+ class CommaSequence < AbstractSequence
5
+ # The comma-separated selector sequences
6
+ # represented by this class.
7
+ #
8
+ # @return [Array<Sequence>]
9
+ attr_reader :members
10
+
11
+ # @param seqs [Array<Sequence>] See \{#members}
12
+ def initialize(seqs)
13
+ @members = seqs
14
+ end
15
+
16
+ # Resolves the {Parent} selectors within this selector
17
+ # by replacing them with the given parent selector,
18
+ # handling commas appropriately.
19
+ #
20
+ # @param super_cseq [CommaSequence] The parent selector
21
+ # @return [CommaSequence] This selector, with parent references resolved
22
+ # @raise [Sass::SyntaxError] If a parent selector is invalid
23
+ def resolve_parent_refs(super_cseq)
24
+ if super_cseq.nil?
25
+ if @members.any? do |sel|
26
+ sel.members.any? do |sel_or_op|
27
+ sel_or_op.is_a?(SimpleSequence) && sel_or_op.members.any? {|ssel| ssel.is_a?(Parent)}
28
+ end
29
+ end
30
+ raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '&'.")
31
+ end
32
+ return self
33
+ end
34
+
35
+ CommaSequence.new(
36
+ super_cseq.members.map do |super_seq|
37
+ @members.map {|seq| seq.resolve_parent_refs(super_seq)}
38
+ end.flatten)
39
+ end
40
+
41
+ # Non-destrucively extends this selector
42
+ # with the extensions specified in a hash
43
+ # (which should be populated via {Sass::Tree::Node#cssize}).
44
+ #
45
+ # @todo Link this to the reference documentation on `@extend`
46
+ # when such a thing exists.
47
+ #
48
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
49
+ # The extensions to perform on this selector
50
+ # @return [CommaSequence] A copy of this selector,
51
+ # with extensions made according to `extends`
52
+ def do_extend(extends)
53
+ CommaSequence.new(members.map {|seq| seq.do_extend(extends)}.flatten)
54
+ end
55
+
56
+ # Returns a string representation of the sequence.
57
+ # This is basically the selector string.
58
+ #
59
+ # @return [String]
60
+ def inspect
61
+ members.map {|m| m.inspect}.join(", ")
62
+ end
63
+
64
+ # Returns a hash code for this sequence.
65
+ #
66
+ # @return [Fixnum]
67
+ def hash
68
+ members.hash
69
+ end
70
+
71
+ # Checks equality between this and another object.
72
+ #
73
+ # @param other [Object] The object to test equality against
74
+ # @return [Boolean] Whether or not this is equal to `other`
75
+ def eql?(other)
76
+ other.class == self.class && other.members.eql?(self.members)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,194 @@
1
+ module Sass
2
+ module Selector
3
+ # An operator-separated sequence of
4
+ # {SimpleSequence simple selector sequences}.
5
+ class Sequence < AbstractSequence
6
+ # Sets the line of the Sass template on which this selector was declared.
7
+ # This also sets the line for all child selectors.
8
+ #
9
+ # @param line [Fixnum]
10
+ # @return [Fixnum]
11
+ def line=(line)
12
+ members.each {|m| m.line = line if m.is_a?(SimpleSequence)}
13
+ line
14
+ end
15
+
16
+ # Sets the name of the file in which this selector was declared,
17
+ # or `nil` if it was not declared in a file (e.g. on stdin).
18
+ # This also sets the filename for all child selectors.
19
+ #
20
+ # @param filename [String, nil]
21
+ # @return [String, nil]
22
+ def filename=(filename)
23
+ members.each {|m| m.filename = filename if m.is_a?(SimpleSequence)}
24
+ filename
25
+ end
26
+
27
+ # The array of {SimpleSequence simple selector sequences}, operators, and newlines.
28
+ # The operators are strings such as `"+"` and `">"`
29
+ # representing the corresponding CSS operators.
30
+ # Newlines are also newline strings;
31
+ # these aren't semantically relevant,
32
+ # but they do affect formatting.
33
+ #
34
+ # @return [Array<SimpleSequence, String>]
35
+ attr_reader :members
36
+
37
+ # @param seqs_and_ops [Array<SimpleSequence, String>] See \{#members}
38
+ def initialize(seqs_and_ops)
39
+ @members = seqs_and_ops
40
+ end
41
+
42
+ # Resolves the {Parent} selectors within this selector
43
+ # by replacing them with the given parent selector,
44
+ # handling commas appropriately.
45
+ #
46
+ # @param super_seq [Sequence] The parent selector sequence
47
+ # @return [Sequence] This selector, with parent references resolved
48
+ # @raise [Sass::SyntaxError] If a parent selector is invalid
49
+ def resolve_parent_refs(super_seq)
50
+ members = @members
51
+ members.slice!(0) if nl = (members.first == "\n")
52
+ unless members.any? do |seq_or_op|
53
+ seq_or_op.is_a?(SimpleSequence) && seq_or_op.members.first.is_a?(Parent)
54
+ end
55
+ members = []
56
+ members << "\n" if nl
57
+ members << SimpleSequence.new([Parent.new])
58
+ members += @members
59
+ end
60
+
61
+ Sequence.new(
62
+ members.map do |seq_or_op|
63
+ next seq_or_op unless seq_or_op.is_a?(SimpleSequence)
64
+ seq_or_op.resolve_parent_refs(super_seq)
65
+ end.flatten)
66
+ end
67
+
68
+ # Non-destructively extends this selector
69
+ # with the extensions specified in a hash
70
+ # (which should be populated via {Sass::Tree::Node#cssize}).
71
+ #
72
+ # @overload def do_extend(extends)
73
+ # @param extends [Haml::Util::SubsetMap{Selector::Simple => Selector::Sequence}]
74
+ # The extensions to perform on this selector
75
+ # @return [Array<Sequence>] A list of selectors generated
76
+ # by extending this selector with `extends`.
77
+ # These correspond to a {CommaSequence}'s {CommaSequence#members members array}.
78
+ # @see CommaSequence#do_extend
79
+ def do_extend(extends, supers = [])
80
+ Haml::Util.paths(members.map do |sseq_or_op|
81
+ next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)
82
+ [[sseq_or_op], *sseq_or_op.do_extend(extends, supers).map {|seq| seq.members}]
83
+ end).map {|path| weave(path)}.flatten(1).map {|p| Sequence.new(p)}
84
+ end
85
+
86
+ # @see Simple#to_a
87
+ def to_a
88
+ ary = @members.map {|seq_or_op| seq_or_op.is_a?(SimpleSequence) ? seq_or_op.to_a : seq_or_op}
89
+ ary = Haml::Util.intersperse(ary, " ")
90
+ ary = Haml::Util.substitute(ary, [" ", "\n", " "], ["\n"])
91
+ ary.flatten.compact
92
+ end
93
+
94
+ # Returns a string representation of the sequence.
95
+ # This is basically the selector string.
96
+ #
97
+ # @return [String]
98
+ def inspect
99
+ members.map {|m| m.inspect}.join(" ")
100
+ end
101
+
102
+ # Returns a hash code for this sequence.
103
+ #
104
+ # @return [Fixnum]
105
+ def hash
106
+ members.reject {|m| m == "\n"}.hash
107
+ end
108
+
109
+ # Checks equality between this and another object.
110
+ #
111
+ # @param other [Object] The object to test equality against
112
+ # @return [Boolean] Whether or not this is equal to `other`
113
+ def eql?(other)
114
+ other.class == self.class &&
115
+ other.members.reject {|m| m == "\n"}.eql?(self.members.reject {|m| m == "\n"})
116
+ end
117
+
118
+ private
119
+
120
+ # Conceptually, this expands "parenthesized selectors".
121
+ # That is, if we have `.A .B {@extend .C}` and `.D .C {...}`,
122
+ # this conceptually expands into `.D .C, .D (.A .B)`,
123
+ # and this function translates `.D (.A .B)` into `.D .A .B, .A.D .B, .D .A .B`.
124
+ #
125
+ # @param path [Array<Array<SimpleSequence or String>>] A list of parenthesized selector groups.
126
+ # @return [Array<Array<SimpleSequence or String>>] A list of fully-expanded selectors.
127
+ def weave(path)
128
+ befores = [[]]
129
+ afters = path.dup
130
+
131
+ until afters.empty?
132
+ current = afters.shift.dup
133
+ last_current = [current.pop]
134
+ while !current.empty? && last_current.first.is_a?(String) || current.last.is_a?(String)
135
+ last_current.unshift(current.pop)
136
+ end
137
+ befores = befores.map do |before|
138
+ subweave(before, current).map {|seqs| seqs + last_current}
139
+ end.flatten(1)
140
+ return befores if afters.empty?
141
+ end
142
+ end
143
+
144
+ # This interweaves two lists of selectors,
145
+ # returning all possible orderings of them (including using unification)
146
+ # that maintain the relative ordering of the input arrays.
147
+ #
148
+ # For example, given `.foo .bar` and `.baz .bang`,
149
+ # this would return `.foo .bar .baz .bang`, `.foo .bar.baz .bang`,
150
+ # `.foo .baz .bar .bang`, `.foo .baz .bar.bang`, `.foo .baz .bang .bar`,
151
+ # and so on until `.baz .bang .foo .bar`.
152
+ #
153
+ # @overload def subweave(seq1, seq2)
154
+ # @param seq1 [Array<SimpleSequence or String>]
155
+ # @param seq2 [Array<SimpleSequence or String>]
156
+ # @return [Array<Array<SimpleSequence or String>>]
157
+ def subweave(seq1, seq2, cache = {})
158
+ return [seq2] if seq1.empty?
159
+ return [seq1] if seq2.empty?
160
+ cache[[seq1, seq2]] ||=
161
+ begin
162
+ sseq1, rest1 = seq_split(seq1)
163
+ sseq2, rest2 = seq_split(seq2)
164
+
165
+ if sseq1.eql?(sseq2)
166
+ subweave(rest1, rest2, cache).map {|subseq| sseq1 + subseq}
167
+ else
168
+ unified = unify_heads(sseq1, sseq2) || unify_heads(sseq2, sseq1)
169
+ res = []
170
+ subweave(rest1, seq2, cache).each {|subseq| res << sseq1 + subseq}
171
+ subweave(rest1, rest2, cache).each {|subseq| res << unified + subseq} if unified
172
+ subweave(seq1, rest2, cache).each {|subseq| res << sseq2 + subseq}
173
+ res
174
+ end
175
+ end
176
+ end
177
+
178
+ def seq_split(seq)
179
+ tail = seq.dup
180
+ head = []
181
+ begin
182
+ head << tail.shift
183
+ end while !tail.empty? && head.last.is_a?(String) || tail.first.is_a?(String)
184
+ return head, tail
185
+ end
186
+
187
+ def unify_heads(sseq1, sseq2)
188
+ return unless sseq2.size == 1 # Can't unify ".foo > .bar" and ".baz > .bang"
189
+ unified = sseq1.last.unify(sseq2.last.members) unless sseq1.last.is_a?(String) || sseq2.last.is_a?(String)
190
+ sseq1[0...-1] << unified if unified
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,107 @@
1
+ module Sass
2
+ module Selector
3
+ # The abstract superclass for simple selectors
4
+ # (that is, those that don't compose multiple selectors).
5
+ class Simple
6
+ # The line of the Sass template on which this selector was declared.
7
+ #
8
+ # @return [Fixnum]
9
+ attr_accessor :line
10
+
11
+ # The name of the file in which this selector was declared,
12
+ # or `nil` if it was not declared in a file (e.g. on stdin).
13
+ #
14
+ # @return [String, nil]
15
+ attr_accessor :filename
16
+
17
+ # Returns a representation of the node
18
+ # as an array of strings and potentially {Sass::Script::Node}s
19
+ # (if there's interpolation in the selector).
20
+ # When the interpolation is resolved and the strings are joined together,
21
+ # this will be the string representation of this node.
22
+ #
23
+ # @return [Array<String, Sass::Script::Node>]
24
+ def to_a
25
+ raise NotImplementedError.new("All subclasses of Sass::Selector::Simple must override #to_a.")
26
+ end
27
+
28
+ # Returns a string representation of the node.
29
+ # This is basically the selector string.
30
+ #
31
+ # @return [String]
32
+ def inspect
33
+ to_a.map {|e| e.is_a?(Sass::Script::Node) ? "\#{#{e.to_sass}}" : e}.join
34
+ end
35
+
36
+ # Returns a hash code for this selector object.
37
+ #
38
+ # By default, this is based on the value of \{#to\_a},
39
+ # so if that contains information irrelevant to the identity of the selector,
40
+ # this should be overridden.
41
+ #
42
+ # @return [Fixnum]
43
+ def hash
44
+ to_a.hash
45
+ end
46
+
47
+ # Checks equality between this and another object.
48
+ #
49
+ # By default, this is based on the value of \{#to\_a},
50
+ # so if that contains information irrelevant to the identity of the selector,
51
+ # this should be overridden.
52
+ #
53
+ # @param other [Object] The object to test equality against
54
+ # @return [Boolean] Whether or not this is equal to `other`
55
+ def eql?(other)
56
+ other.class == self.class && other.to_a.eql?(to_a)
57
+ end
58
+
59
+ # Unifies this selector with a {SimpleSequence}'s {SimpleSequence#members members array},
60
+ # returning another `SimpleSequence` members array
61
+ # that matches both this selector and the input selector.
62
+ #
63
+ # By default, this just appends this selector to the end of the array
64
+ # (or returns the original array if this selector already exists in it).
65
+ #
66
+ # @param sels [Array<Simple>] A {SimpleSequence}'s {SimpleSequence#members members array}
67
+ # @return [Array<Simple>, nil] A {SimpleSequence} {SimpleSequence#members members array}
68
+ # matching both `sels` and this selector,
69
+ # or `nil` if this is impossible (e.g. unifying `#foo` and `#bar`)
70
+ # @raise [Sass::SyntaxError] If this selector cannot be unified.
71
+ # This will only ever occur when a dynamic selector,
72
+ # such as {Parent} or {Interpolation}, is used in unification.
73
+ # Since these selectors should be resolved
74
+ # by the time extension and unification happen,
75
+ # this exception will only ever be raised as a result of programmer error
76
+ def unify(sels)
77
+ return sels if sels.any? {|sel2| eql?(sel2)}
78
+ if sels.last.is_a?(Pseudo) && sels.last.type == :element
79
+ return sels[0...-1] + [self, sels.last]
80
+ end
81
+ sels + [self]
82
+ end
83
+
84
+ protected
85
+
86
+ # Unifies two namespaces,
87
+ # returning a namespace that works for both of them if possible.
88
+ #
89
+ # @param ns1 [String, nil] The first namespace.
90
+ # `nil` means none specified, e.g. `foo`.
91
+ # The empty string means no namespace specified, e.g. `|foo`.
92
+ # `"*"` means any namespace is allowed, e.g. `*|foo`.
93
+ # @param ns2 [String, nil] The second namespace. See `ns1`.
94
+ # @return [Array(String or nil, Boolean)]
95
+ # The first value is the unified namespace, or `nil` for no namespace.
96
+ # The second value is whether or not a namespace that works for both inputs
97
+ # could be found at all.
98
+ # If the second value is `false`, the first should be ignored.
99
+ def unify_namespaces(ns1, ns2)
100
+ return nil, false unless ns1 == ns2 || ns1.nil? || ns1 == ['*'] || ns2.nil? || ns2 == ['*']
101
+ return ns2, true if ns1 == ['*']
102
+ return ns1, true if ns2 == ['*']
103
+ return ns1 || ns2, true
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,161 @@
1
+ module Sass
2
+ module Selector
3
+ # A unseparated sequence of selectors
4
+ # that all apply to a single element.
5
+ # For example, `.foo#bar[attr=baz]` is a simple sequence
6
+ # of the selectors `.foo`, `#bar`, and `[attr=baz]`.
7
+ class SimpleSequence < AbstractSequence
8
+ # The array of individual selectors.
9
+ #
10
+ # @return [Array<Simple>]
11
+ attr_reader :members
12
+
13
+ # Returns the element or universal selector in this sequence,
14
+ # if it exists.
15
+ #
16
+ # @return [Element, Universal, nil]
17
+ def base
18
+ @base ||= (members.first if members.first.is_a?(Element) || members.first.is_a?(Universal))
19
+ end
20
+
21
+ # Returns the non-base selectors in this sequence.
22
+ #
23
+ # @return [Set<Simple>]
24
+ def rest
25
+ @rest ||= Set.new(base ? members[1..-1] : members)
26
+ end
27
+
28
+ # @param selectors [Array<Simple>] See \{#members}
29
+ def initialize(selectors)
30
+ @members = selectors
31
+ end
32
+
33
+ # Resolves the {Parent} selectors within this selector
34
+ # by replacing them with the given parent selector,
35
+ # handling commas appropriately.
36
+ #
37
+ # @param super_seq [Sequence] The parent selector sequence
38
+ # @return [Array<SimpleSequence>] This selector, with parent references resolved.
39
+ # This is an array because the parent selector is itself a {Sequence}
40
+ # @raise [Sass::SyntaxError] If a parent selector is invalid
41
+ def resolve_parent_refs(super_seq)
42
+ # Parent selector only appears as the first selector in the sequence
43
+ return [self] unless @members.first.is_a?(Parent)
44
+
45
+ return super_seq.members if @members.size == 1
46
+ unless super_seq.members.last.is_a?(SimpleSequence)
47
+ raise Sass::SyntaxError.new("Invalid parent selector: " + super_seq.to_a.join)
48
+ end
49
+
50
+ super_seq.members[0...-1] +
51
+ [SimpleSequence.new(super_seq.members.last.members + @members[1..-1])]
52
+ end
53
+
54
+ # Non-destrucively extends this selector
55
+ # with the extensions specified in a hash
56
+ # (which should be populated via {Sass::Tree::Node#cssize}).
57
+ #
58
+ # @overload def do_extend(extends)
59
+ # @param extends [{Selector::Simple => Selector::Sequence}]
60
+ # The extensions to perform on this selector
61
+ # @return [Array<Sequence>] A list of selectors generated
62
+ # by extending this selector with `extends`.
63
+ # @see CommaSequence#do_extend
64
+ def do_extend(extends, supers = [])
65
+ seqs = extends.get(members.to_set).map do |seq, sels|
66
+ # If A {@extend B} and C {...},
67
+ # seq is A, sels is B, and self is C
68
+
69
+ self_without_sel = self.members - sels
70
+ next unless unified = seq.members.last.unify(self_without_sel)
71
+ [sels, seq.members[0...-1] + [unified]]
72
+ end.compact.map {|sels, seq| [sels, Sequence.new(seq)]}
73
+
74
+ seqs.map {|_, seq| seq}.concat(
75
+ seqs.map do |sels, seq|
76
+ new_seqs = seq.do_extend(extends, supers.unshift(sels))[1..-1]
77
+ supers.shift
78
+ new_seqs
79
+ end.flatten.uniq)
80
+ rescue SystemStackError
81
+ handle_extend_loop(supers)
82
+ end
83
+
84
+ # Unifies this selector with another {SimpleSequence}'s {SimpleSequence#members members array},
85
+ # returning another `SimpleSequence`
86
+ # that matches both this selector and the input selector.
87
+ #
88
+ # @param sels [Array<Simple>] A {SimpleSequence}'s {SimpleSequence#members members array}
89
+ # @return [SimpleSequence, nil] A {SimpleSequence} matching both `sels` and this selector,
90
+ # or `nil` if this is impossible (e.g. unifying `#foo` and `#bar`)
91
+ # @raise [Sass::SyntaxError] If this selector cannot be unified.
92
+ # This will only ever occur when a dynamic selector,
93
+ # such as {Parent} or {Interpolation}, is used in unification.
94
+ # Since these selectors should be resolved
95
+ # by the time extension and unification happen,
96
+ # this exception will only ever be raised as a result of programmer error
97
+ def unify(sels)
98
+ return unless sseq = members.inject(sels) do |sseq, sel|
99
+ return unless sseq
100
+ sel.unify(sseq)
101
+ end
102
+ SimpleSequence.new(sseq)
103
+ end
104
+
105
+ # @see Simple#to_a
106
+ def to_a
107
+ @members.map {|sel| sel.to_a}.flatten
108
+ end
109
+
110
+ # Returns a string representation of the sequence.
111
+ # This is basically the selector string.
112
+ #
113
+ # @return [String]
114
+ def inspect
115
+ members.map {|m| m.inspect}.join
116
+ end
117
+
118
+ # Returns a hash code for this sequence.
119
+ #
120
+ # @return [Fixnum]
121
+ def hash
122
+ [base, rest].hash
123
+ end
124
+
125
+ # Checks equality between this and another object.
126
+ #
127
+ # @param other [Object] The object to test equality against
128
+ # @return [Boolean] Whether or not this is equal to `other`
129
+ def eql?(other)
130
+ other.class == self.class && other.base.eql?(self.base) && other.rest.eql?(self.rest)
131
+ end
132
+
133
+ private
134
+
135
+ # Raise a {Sass::SyntaxError} describing a loop of `@extend` directives.
136
+ #
137
+ # @param supers [Array<Simple>] The stack of selectors that contains the loop,
138
+ # ordered from deepest to most shallow.
139
+ # @raise [Sass::SyntaxError] Describing the loop
140
+ def handle_extend_loop(supers)
141
+ supers.inject([]) do |sseqs, sseq|
142
+ next sseqs.push(sseq) unless sseqs.first.eql?(sseq)
143
+ conses = Haml::Util.enum_cons(sseqs.push(sseq), 2).to_a
144
+ _, i = Haml::Util.enum_with_index(conses).max do |((_, sseq1), _), ((_, sseq2), _)|
145
+ sseq1.first.line <=> sseq2.first.line
146
+ end
147
+ loop = (conses[i..-1] + conses[0...i]).map do |sseq1, sseq2|
148
+ sel1 = SimpleSequence.new(sseq1).inspect
149
+ sel2 = SimpleSequence.new(sseq2).inspect
150
+ str = " #{sel1} extends #{sel2} on line #{sseq2.first.line}"
151
+ str << " of " << sseq2.first.filename if sseq2.first.filename
152
+ str
153
+ end.join(",\n")
154
+ raise Sass::SyntaxError.new("An @extend loop was found:\n#{loop}")
155
+ end
156
+ # Should never get here
157
+ raise Sass::SyntaxError.new("An @extend loop exists, but the exact loop couldn't be found")
158
+ end
159
+ end
160
+ end
161
+ end