class Sass::Selector::SimpleSequence

A unseparated sequence of selectors that all apply to a single element. For example, `.foo#bar` is a simple sequence of the selectors `.foo`, `#bar`, and `[attr=baz]`.

Attributes

members[RW]

The array of individual selectors.

@return [Array<Simple>]

source_range[RW]

This sequence source range.

@return [Sass::Source::Range]

sources[RW]

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 {Sequence#do_extend} process.

@return {Set<Sequence>}

subject[W]

@see {#subject?}

Public Class Methods

new(selectors, subject, source_range = nil) click to toggle source

@param selectors [Array<Simple>] See {#members} @param subject [Boolean] See {#subject?} @param #source_range [Sass::Source::Range]

# 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

Public Instance Methods

base() click to toggle source

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

@return [Element, Universal, nil]

# 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) click to toggle source

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

@param extends [{Selector::Simple =>

               Sass::Tree::Visitors::Cssize::Extend}]
The extensions to perform on this selector

@param parent_directives [Array<Sass::Tree::DirectiveNode>]

The directives containing this selector.

@param seen [Set<Array<Selector::Simple>>]

The set of simple sequences that are currently being replaced.

@param original [Boolean]

Whether this is the original selector being extended, as opposed to
the result of a previous extension that's being re-extended.

@return [Array<Sequence>] A list of selectors generated

by extending this selector with `extends`.

@see Sass::Selector::CommaSequence#do_extend

# 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 because 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
inspect() click to toggle source

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

@return [String]

# 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() click to toggle source
# 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) click to toggle source

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

@param super_cseq [CommaSequence] The parent selector @return [CommaSequence] This selector, with parent references resolved @raise [Sass::SyntaxError] If a parent selector is invalid

# 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 + resolved_members[1..-1], subject?)] +
      [newline].compact)
    end)
end
rest() click to toggle source

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

@return [Set<Simple>]

# File lib/sass/selector/simple_sequence.rb, line 58
def rest
  @rest ||= Set.new(members - [base] - pseudo_elements)
end
selector_pseudo_classes() click to toggle source
# 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?() click to toggle source

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.

@return [Boolean]

# File lib/sass/selector/simple_sequence.rb, line 67
def subject?
  @subject
end
superselector?(their_sseq, parents = []) click to toggle source

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 their_sseq [SimpleSequence] @param parents [Array<SimpleSequence, String>] The parent selectors of `their_sseq`, if any. @return [Boolean]

# 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() click to toggle source

@see Sass::Selector::Simple#to_s

# File lib/sass/selector/simple_sequence.rb, line 288
def to_s
  res = @members.join
  res << '!' if subject?
  res
end
unify(other) click to toggle source

Unifies this selector with another {SimpleSequence}, returning another `SimpleSequence` that is a subselector of both input selectors.

@param other [SimpleSequence] @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
# 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) click to toggle source

Return a copy of this simple sequence with `sources` merged into the {SimpleSequence#sources} set.

@param sources [Set<Sequence>] @return [SimpleSequence]

# 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

Private Instance Methods

_eql?(other) click to toggle source
# File lib/sass/selector/simple_sequence.rb, line 338
def _eql?(other)
  other.base.eql?(base) && other.pseudo_elements == pseudo_elements &&
    other.rest.eql?(rest) && other.subject? == subject?
end
_hash() click to toggle source
# File lib/sass/selector/simple_sequence.rb, line 334
def _hash
  [base, rest.hash].hash
end
check_directives_match!(extend, parent_directives) click to toggle source
# File lib/sass/selector/simple_sequence.rb, line 318
      def check_directives_match!(extend, parent_directives)
        dirs1 = extend.directives.map {|d| d.resolved_value}
        dirs2 = parent_directives.map {|d| d.resolved_value}
        return if Sass::Util.subsequence?(dirs1, dirs2)
        line = extend.node.line
        filename = extend.node.filename

        # TODO(nweiz): this should use the Sass stack trace of the extend node,
        # not the selector.
        raise Sass::SyntaxError.new("You may not @extend an outer selector from within #{extend.directives.last.name}.
You may only @extend selectors within the same directive.
From "@extend #{extend.target.join(', ')}" on line #{line}#{" of #{filename}" if filename}.
")
      end