blob: e3a140483fb0e3109baa2a394ecbcb356901261e [file] [log] [blame]
require 'pathname'
module Sass::Tree
# A static node representing a CSS rule.
#
# @see Sass::Tree
class RuleNode < Node
# The character used to include the parent selector
PARENT = '&'
# The CSS selector for this rule,
# interspersed with {Sass::Script::Tree::Node}s
# representing `#{}`-interpolation.
# Any adjacent strings will be merged together.
#
# @return [Array<String, Sass::Script::Tree::Node>]
attr_accessor :rule
# The CSS selector for this rule, without any unresolved
# interpolation but with parent references still intact. It's only
# guaranteed to be set once {Tree::Visitors::Perform} has been
# run, but it may be set before then for optimization reasons.
#
# @return [Selector::CommaSequence]
attr_accessor :parsed_rules
# The CSS selector for this rule, without any unresolved
# interpolation or parent references. It's only set once
# {Tree::Visitors::Perform} has been run.
#
# @return [Selector::CommaSequence]
attr_accessor :resolved_rules
# How deep this rule is indented
# relative to a base-level rule.
# This is only greater than 0 in the case that:
#
# * This node is in a CSS tree
# * The style is :nested
# * This is a child rule of another rule
# * The parent rule has properties, and thus will be rendered
#
# @return [Integer]
attr_accessor :tabs
# The entire selector source range for this rule.
# @return [Sass::Source::Range]
attr_accessor :selector_source_range
# Whether or not this rule is the last rule in a nested group.
# This is only set in a CSS tree.
#
# @return [Boolean]
attr_accessor :group_end
# The stack trace.
# This is only readable in a CSS tree as it is written during the perform step
# and only when the :trace_selectors option is set.
#
# @return [String]
attr_accessor :stack_trace
# @param rule [Array<String, Sass::Script::Tree::Node>, Sass::Selector::CommaSequence]
# The CSS rule, either unparsed or parsed.
# @param selector_source_range [Sass::Source::Range]
def initialize(rule, selector_source_range = nil)
if rule.is_a?(Sass::Selector::CommaSequence)
@rule = [rule.to_s]
@parsed_rules = rule
else
merged = Sass::Util.merge_adjacent_strings(rule)
@rule = Sass::Util.strip_string_array(merged)
try_to_parse_non_interpolated_rules
end
@selector_source_range = selector_source_range
@tabs = 0
super()
end
# If we've precached the parsed selector, set the line on it, too.
def line=(line)
@parsed_rules.line = line if @parsed_rules
super
end
# If we've precached the parsed selector, set the filename on it, too.
def filename=(filename)
@parsed_rules.filename = filename if @parsed_rules
super
end
# Compares the contents of two rules.
#
# @param other [Object] The object to compare with
# @return [Boolean] Whether or not this node and the other object
# are the same
def ==(other)
self.class == other.class && rule == other.rule && super
end
# Adds another {RuleNode}'s rules to this one's.
#
# @param node [RuleNode] The other node
def add_rules(node)
@rule = Sass::Util.strip_string_array(
Sass::Util.merge_adjacent_strings(@rule + ["\n"] + node.rule))
try_to_parse_non_interpolated_rules
end
# @return [Boolean] Whether or not this rule is continued on the next line
def continued?
last = @rule.last
last.is_a?(String) && last[-1] == ?,
end
# A hash that will be associated with this rule in the CSS document
# if the {file:SASS_REFERENCE.md#debug_info-option `:debug_info` option} is enabled.
# This data is used by e.g. [the FireSass Firebug
# extension](https://addons.mozilla.org/en-US/firefox/addon/103988).
#
# @return [{#to_s => #to_s}]
def debug_info
{:filename => filename &&
("file://" + URI::DEFAULT_PARSER.escape(File.expand_path(filename))),
:line => line}
end
# A rule node is invisible if it has only placeholder selectors.
def invisible?
resolved_rules.members.all? {|seq| seq.invisible?}
end
private
def try_to_parse_non_interpolated_rules
@parsed_rules = nil
return unless @rule.all? {|t| t.is_a?(String)}
# We don't use real filename/line info because we don't have it yet.
# When we get it, we'll set it on the parsed rules if possible.
parser = nil
warnings = Sass::Util.silence_warnings do
parser = Sass::SCSS::StaticParser.new(@rule.join.strip, nil, nil, 1)
# rubocop:disable RescueModifier
@parsed_rules = parser.parse_selector rescue nil
# rubocop:enable RescueModifier
$stderr.string
end
# If parsing produces a warning, throw away the result so we can parse
# later with the real filename info.
@parsed_rules = nil unless warnings.empty?
end
end
end