haml-edge 2.3.209 → 2.3.210

Sign up to get free protection for your applications and to get access to all the features.
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,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