forked from sass/sass
-
Notifications
You must be signed in to change notification settings - Fork 0
/
simple_sequence.rb
142 lines (128 loc) · 5.19 KB
/
simple_sequence.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
module Sass
module Selector
# A unseparated sequence of selectors
# that all apply to a single element.
# For example, `.foo#bar[attr=baz]` is a simple sequence
# of the selectors `.foo`, `#bar`, and `[attr=baz]`.
class SimpleSequence < AbstractSequence
# The array of individual selectors.
#
# @return [Array<Simple>]
attr_reader :members
# Returns the element or universal selector in this sequence,
# if it exists.
#
# @return [Element, Universal, nil]
def base
@base ||= (members.first if members.first.is_a?(Element) || members.first.is_a?(Universal))
end
# Returns the non-base selectors in this sequence.
#
# @return [Set<Simple>]
def rest
@rest ||= Set.new(base ? members[1..-1] : members)
end
# @param selectors [Array<Simple>] See \{#members}
def initialize(selectors)
@members = selectors
end
# Resolves the {Parent} selectors within this selector
# by replacing them with the given parent selector,
# handling commas appropriately.
#
# @param super_seq [Sequence] The parent selector sequence
# @return [Array<SimpleSequence>] This selector, with parent references resolved.
# This is an array because the parent selector is itself a {Sequence}
# @raise [Sass::SyntaxError] If a parent selector is invalid
def resolve_parent_refs(super_seq)
# Parent selector only appears as the first selector in the sequence
return [self] unless @members.first.is_a?(Parent)
return super_seq.members if @members.size == 1
unless super_seq.members.last.is_a?(SimpleSequence)
raise Sass::SyntaxError.new("Invalid parent selector: " + super_seq.to_a.join)
end
super_seq.members[0...-1] +
[SimpleSequence.new(super_seq.members.last.members + @members[1..-1])]
end
# Non-destrucively extends this selector
# with the extensions specified in a hash
# (which should be populated via {Sass::Tree::Node#cssize}).
#
# @overload def do_extend(extends)
# @param extends [{Selector::Simple => Selector::Sequence}]
# The extensions to perform on this selector
# @return [Array<Sequence>] A list of selectors generated
# by extending this selector with `extends`.
# @see CommaSequence#do_extend
def do_extend(extends, seen = Set.new)
extends.get(members.to_set).map do |seq, sels|
# If A {@extend B} and C {...},
# seq is A, sels is B, and self is C
self_without_sel = self.members - sels
next unless unified = seq.members.last.unify(self_without_sel)
[sels, seq.members[0...-1] + [unified]]
end.compact.map do |sels, seq|
seq = Sequence.new(seq)
seen.include?(sels) ? [] : seq.do_extend(extends, seen + [sels])
end.flatten.uniq
end
# Unifies this selector with another {SimpleSequence}'s {SimpleSequence#members members array},
# returning another `SimpleSequence`
# that matches both this selector and the input selector.
#
# @param sels [Array<Simple>] A {SimpleSequence}'s {SimpleSequence#members members array}
# @return [SimpleSequence, nil] A {SimpleSequence} matching both `sels` and this selector,
# or `nil` if this is impossible (e.g. unifying `#foo` and `#bar`)
# @raise [Sass::SyntaxError] If this selector cannot be unified.
# This will only ever occur when a dynamic selector,
# such as {Parent} or {Interpolation}, is used in unification.
# Since these selectors should be resolved
# by the time extension and unification happen,
# this exception will only ever be raised as a result of programmer error
def unify(sels)
return unless sseq = members.inject(sels) do |sseq, sel|
return unless sseq
sel.unify(sseq)
end
SimpleSequence.new(sseq)
end
# Returns whether or not this selector matches all elements
# that the given selector matches (as well as possibly more).
#
# @example
# (.foo).superselector?(.foo.bar) #=> true
# (.foo).superselector?(.bar) #=> false
#
# @param sseq [SimpleSequence]
# @return [Boolean]
def superselector?(sseq)
(base.nil? || base.eql?(sseq.base)) && rest.subset?(sseq.rest)
end
# @see Simple#to_a
def to_a
@members.map {|sel| sel.to_a}.flatten
end
# Returns a string representation of the sequence.
# This is basically the selector string.
#
# @return [String]
def inspect
members.map {|m| m.inspect}.join
end
# Returns a hash code for this sequence.
#
# @return [Fixnum]
def hash
[base, Haml::Util.set_hash(rest)].hash
end
# Checks equality between this and another object.
#
# @param other [Object] The object to test equality against
# @return [Boolean] Whether or not this is equal to `other`
def eql?(other)
other.class == self.class && other.base.eql?(self.base) &&
Haml::Util.set_eql?(other.rest, self.rest)
end
end
end
end