Class: Sass::Selector::SimpleSequence

Inherits:
AbstractSequence show all
Defined in:
lib/sass/selector/simple_sequence.rb

Overview

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].

Instance Attribute Summary collapse

Attributes inherited from AbstractSequence

#filename, #line

Instance Method Summary collapse

Methods inherited from AbstractSequence

#_specificity, #eql?, #has_placeholder?, #hash, #specificity

Constructor Details

#initialize(selectors, subject, source_range = nil) ⇒ SimpleSequence



74
75
76
77
78
79
# File 'lib/sass/selector/simple_sequence.rb', line 74

def initialize(selectors, subject, source_range = nil)
  @members = selectors
  @subject = subject
  @sources = Set.new
  @source_range = source_range
end

Instance Attribute Details

#membersArray<Simple>

The array of individual selectors.



11
12
13
# File 'lib/sass/selector/simple_sequence.rb', line 11

def members
  @members
end

#source_rangeSass::Source::Range

This sequence source range.



32
33
34
# File 'lib/sass/selector/simple_sequence.rb', line 32

def source_range
  @source_range
end

#sourcesSet<Sequence>

The extending selectors that caused this selector sequence to be generated. For example:

a.foo { ... }
b.bar {@extend a}
c.baz {@extend b}

The generated selector b.foo.bar has {b.bar} as its sources set, and the generated selector c.foo.bar.baz has {b.bar, c.baz} as its sources set.

This is populated during the Sass::Selector::Sequence#do_extend process.



27
28
29
# File 'lib/sass/selector/simple_sequence.rb', line 27

def sources
  @sources
end

#subject=(value) (writeonly)

See Also:

  • Sass::Selector::SimpleSequence.\{\{#subject?}


35
36
37
# File 'lib/sass/selector/simple_sequence.rb', line 35

def subject=(value)
  @subject = value
end

Instance Method Details

#baseElement, ...

Returns the element or universal selector in this sequence, if it exists.



41
42
43
# File 'lib/sass/selector/simple_sequence.rb', line 41

def base
  @base ||= (members.first if members.first.is_a?(Element) || members.first.is_a?(Universal))
end

#do_extend(extends, parent_directives, replace, seen) ⇒ Array<Sequence>

Non-destructively extends this selector with the extensions specified in a hash (which should come from Tree::Visitors::Cssize).

The extensions to perform on this selector



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/sass/selector/simple_sequence.rb', line 157

def do_extend(extends, parent_directives, replace, seen)
  seen_with_pseudo_selectors = seen.dup

  modified_original = false
  members = Sass::Util.enum_with_index(self.members).map do |sel, i|
    next sel unless sel.is_a?(Pseudo) && sel.selector
    next sel if seen.include?([sel])
    extended = sel.selector.do_extend(extends, parent_directives, replace, seen, !:original)
    next sel if extended == sel.selector
    extended.members.reject! {|seq| seq.has_placeholder?}

    # For `:not()`, we usually want to get rid of any complex
    # selectors becuase that will cause the selector to fail to
    # parse on all browsers at time of writing. We can keep them
    # if either the original selector had a complex selector, or
    # the result of extending has only complex selectors,
    # because either way we aren't breaking anything that isn't
    # already broken.
    if sel.normalized_name == 'not' &&
        (sel.selector.members.none? {|seq| seq.members.length > 1} &&
         extended.members.any? {|seq| seq.members.length == 1})
      extended.members.reject! {|seq| seq.members.length > 1}
    end

    modified_original = true
    result = sel.with_selector(extended)
    result.each {|new_sel| seen_with_pseudo_selectors << [new_sel]}
    result
  end.flatten

  groups = Sass::Util.group_by_to_a(extends[members.to_set]) {|ex| ex.extender}
  groups.map! do |seq, group|
    sels = group.map {|e| e.target}.flatten
    # If A {@extend B} and C {...},
    # seq is A, sels is B, and self is C

    self_without_sel = Sass::Util.array_minus(members, sels)
    group.each {|e| e.result = :failed_to_unify unless e.result == :succeeded}
    unified = seq.members.last.unify(SimpleSequence.new(self_without_sel, subject?))
    next unless unified
    group.each {|e| e.result = :succeeded}
    group.each {|e| check_directives_match!(e, parent_directives)}
    new_seq = Sequence.new(seq.members[0...-1] + [unified])
    new_seq.add_sources!(sources + [seq])
    [sels, new_seq]
  end
  groups.compact!
  groups.map! do |sels, seq|
    next [] if seen.include?(sels)
    seq.do_extend(
      extends, parent_directives, !:replace, seen_with_pseudo_selectors + [sels], !:original)
  end
  groups.flatten!

  if modified_original || !replace || groups.empty?
    # First Law of Extend: the result of extending a selector should
    # (almost) always contain the base selector.
    #
    # See https://github.com/nex3/sass/issues/324.
    original = Sequence.new([SimpleSequence.new(members, @subject, source_range)])
    original.add_sources! sources
    groups.unshift original
  end
  groups.uniq!
  groups
end

#inspectString

Returns a string representation of the sequence. This is basically the selector string.



298
299
300
301
302
# File 'lib/sass/selector/simple_sequence.rb', line 298

def inspect
  res = members.map {|m| m.inspect}.join
  res << '!' if subject?
  res
end

#pseudo_elements



45
46
47
# File 'lib/sass/selector/simple_sequence.rb', line 45

def pseudo_elements
  @pseudo_elements ||= members.select {|sel| sel.is_a?(Pseudo) && sel.type == :element}
end

#resolve_parent_refs(super_cseq) ⇒ CommaSequence

Resolves the Parent selectors within this selector by replacing them with the given parent selector, handling commas appropriately.

Raises:



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
# File 'lib/sass/selector/simple_sequence.rb', line 88

def resolve_parent_refs(super_cseq)
  resolved_members = @members.map do |sel|
    next sel unless sel.is_a?(Pseudo) && sel.selector
    sel.with_selector(sel.selector.resolve_parent_refs(super_cseq, !:implicit_parent))
  end.flatten

  # Parent selector only appears as the first selector in the sequence
  unless (parent = resolved_members.first).is_a?(Parent)
    return CommaSequence.new([Sequence.new([SimpleSequence.new(resolved_members, subject?)])])
  end

  return super_cseq if @members.size == 1 && parent.suffix.nil?

  CommaSequence.new(super_cseq.members.map do |super_seq|
    members = super_seq.members.dup
    newline = members.pop if members.last == "\n"
    unless members.last.is_a?(SimpleSequence)
      raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" +
        super_seq.to_s + '"')
    end

    parent_sub = members.last.members
    unless parent.suffix.nil?
      parent_sub = parent_sub.dup
      parent_sub[-1] = parent_sub.last.dup
      case parent_sub.last
      when Sass::Selector::Class, Sass::Selector::Id, Sass::Selector::Placeholder
        parent_sub[-1] = parent_sub.last.class.new(parent_sub.last.name + parent.suffix)
      when Sass::Selector::Element
        parent_sub[-1] = parent_sub.last.class.new(
          parent_sub.last.name + parent.suffix,
          parent_sub.last.namespace)
      when Sass::Selector::Pseudo
        if parent_sub.last.arg || parent_sub.last.selector
          raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" +
            super_seq.to_s + '"')
        end
        parent_sub[-1] = Sass::Selector::Pseudo.new(
          parent_sub.last.type,
          parent_sub.last.name + parent.suffix,
          nil, nil)
      else
        raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" +
          super_seq.to_s + '"')
      end
    end

    Sequence.new(members[0...-1] +
      [SimpleSequence.new(parent_sub + @members[1..-1], subject?)] +
      [newline].compact)
  end)
end

#restSet<Simple>

Returns the non-base, non-pseudo-element selectors in this sequence.



58
59
60
# File 'lib/sass/selector/simple_sequence.rb', line 58

def rest
  @rest ||= Set.new(members - [base] - pseudo_elements)
end

#selector_pseudo_classes



49
50
51
52
53
# File 'lib/sass/selector/simple_sequence.rb', line 49

def selector_pseudo_classes
  @selector_pseudo_classes ||= members.
    select {|sel| sel.is_a?(Pseudo) && sel.type == :class && sel.selector}.
    group_by {|sel| sel.normalized_name}
end

#subject?Boolean

Whether or not this compound selector is the subject of the parent selector; that is, whether it is prepended with $ and represents the actual element that will be selected.



67
68
69
# File 'lib/sass/selector/simple_sequence.rb', line 67

def subject?
  @subject
end

#superselector?(their_sseq, parents = []) ⇒ Boolean

Returns whether or not this selector matches all elements that the given selector matches (as well as possibly more).

Examples:

(.foo).superselector?(.foo.bar) #=> true
(.foo).superselector?(.bar) #=> false


255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/sass/selector/simple_sequence.rb', line 255

def superselector?(their_sseq, parents = [])
  return false unless base.nil? || base.eql?(their_sseq.base)
  return false unless pseudo_elements.eql?(their_sseq.pseudo_elements)
  our_spcs = selector_pseudo_classes
  their_spcs = their_sseq.selector_pseudo_classes

  # Some psuedo-selectors can be subselectors of non-pseudo selectors.
  # Pull those out here so we can efficiently check against them below.
  their_subselector_pseudos = %w[matches any nth-child nth-last-child].
    map {|name| their_spcs[name] || []}.flatten

  # If `self`'s non-pseudo simple selectors aren't a subset of `their_sseq`'s,
  # it's definitely not a superselector. This also considers being matched
  # by `:matches` or `:any`.
  return false unless rest.all? do |our_sel|
    next true if our_sel.is_a?(Pseudo) && our_sel.selector
    next true if their_sseq.rest.include?(our_sel)
    their_subselector_pseudos.any? do |their_pseudo|
      their_pseudo.selector.members.all? do |their_seq|
        next false unless their_seq.members.length == 1
        their_sseq = their_seq.members.first
        next false unless their_sseq.is_a?(SimpleSequence)
        their_sseq.rest.include?(our_sel)
      end
    end
  end

  our_spcs.all? do |name, pseudos|
    pseudos.all? {|pseudo| pseudo.superselector?(their_sseq, parents)}
  end
end

#to_s



288
289
290
291
292
# File 'lib/sass/selector/simple_sequence.rb', line 288

def to_s
  res = @members.join
  res << '!' if subject?
  res
end

#unify(other) ⇒ SimpleSequence?

Unifies this selector with another Sass::Selector::SimpleSequence, returning another SimpleSequence that is a subselector of both input selectors.

Raises:

  • (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



237
238
239
240
241
242
243
244
# File 'lib/sass/selector/simple_sequence.rb', line 237

def unify(other)
  sseq = members.inject(other.members) do |member, sel|
    return unless member
    sel.unify(member)
  end
  return unless sseq
  SimpleSequence.new(sseq, other.subject? || subject?)
end

#with_more_sources(sources) ⇒ SimpleSequence

Return a copy of this simple sequence with sources merged into the #sources set.



309
310
311
312
313
314
# File 'lib/sass/selector/simple_sequence.rb', line 309

def with_more_sources(sources)
  sseq = dup
  sseq.members = members.dup
  sseq.sources = self.sources | sources
  sseq
end