blob: 06932f559c2c9b25ae6387dbd5f4cc32b126e9fa [file] [log] [blame]
module Sass
# A namespace for nodes in the Sass parse tree.
#
# The Sass parse tree has three states: dynamic, static Sass, and static CSS.
#
# When it's first parsed, a Sass document is in the dynamic state.
# It has nodes for mixin definitions and `@for` loops and so forth,
# in addition to nodes for CSS rules and properties.
# Nodes that only appear in this state are called **dynamic nodes**.
#
# {Tree::Visitors::Perform} creates a static Sass tree, which is
# different. It still has nodes for CSS rules and properties but it
# doesn't have any dynamic-generation-related nodes. The nodes in
# this state are in a similar structure to the Sass document: rules
# and properties are nested beneath one another, although the
# {Tree::RuleNode} selectors are already in their final state. Nodes
# that can be in this state or in the dynamic state are called
# **static nodes**; nodes that can only be in this state are called
# **solely static nodes**.
#
# {Tree::Visitors::Cssize} is then used to create a static CSS tree.
# This is like a static Sass tree,
# but the structure exactly mirrors that of the generated CSS.
# Rules and properties can't be nested beneath one another in this state.
#
# Finally, {Tree::Visitors::ToCss} can be called on a static CSS tree
# to get the actual CSS code as a string.
module Tree
# The abstract superclass of all parse-tree nodes.
class Node
include Enumerable
def self.inherited(base)
node_name = base.name.gsub(/.*::(.*?)Node$/, '\\1').downcase
base.instance_eval <<-METHODS
# @return [Symbol] The name that is used for this node when visiting.
def node_name
:#{node_name}
end
# @return [Symbol] The method that is used on the visitor to visit nodes of this type.
def visit_method
:visit_#{node_name}
end
# @return [Symbol] The method name that determines if the parent is invalid.
def invalid_child_method_name
:"invalid_#{node_name}_child?"
end
# @return [Symbol] The method name that determines if the node is an invalid parent.
def invalid_parent_method_name
:"invalid_#{node_name}_parent?"
end
METHODS
end
# The child nodes of this node.
#
# @return [Array<Tree::Node>]
attr_reader :children
# Whether or not this node has child nodes.
# This may be true even when \{#children} is empty,
# in which case this node has an empty block (e.g. `{}`).
#
# @return [Boolean]
attr_accessor :has_children
# The line of the document on which this node appeared.
#
# @return [Integer]
attr_accessor :line
# The source range in the document on which this node appeared.
#
# @return [Sass::Source::Range]
attr_accessor :source_range
# The name of the document on which this node appeared.
#
# @return [String]
attr_writer :filename
# The options hash for the node.
# See {file:SASS_REFERENCE.md#Options the Sass options documentation}.
#
# @return [{Symbol => Object}]
attr_reader :options
def initialize
@children = []
@filename = nil
@options = nil
end
# Sets the options hash for the node and all its children.
#
# @param options [{Symbol => Object}] The options
# @see #options
def options=(options)
Sass::Tree::Visitors::SetOptions.visit(self, options)
end
# @private
def children=(children)
self.has_children ||= !children.empty?
@children = children
end
# The name of the document on which this node appeared.
#
# @return [String]
def filename
@filename || (@options && @options[:filename])
end
# Appends a child to the node.
#
# @param child [Tree::Node, Array<Tree::Node>] The child node or nodes
# @raise [Sass::SyntaxError] if `child` is invalid
def <<(child)
return if child.nil?
if child.is_a?(Array)
child.each {|c| self << c}
else
self.has_children = true
@children << child
end
end
# Compares this node and another object (only other {Tree::Node}s will be equal).
# This does a structural comparison;
# if the contents of the nodes and all the child nodes are equivalent,
# then the nodes are as well.
#
# Only static nodes need to override this.
#
# @param other [Object] The object to compare with
# @return [Boolean] Whether or not this node and the other object
# are the same
# @see Sass::Tree
def ==(other)
self.class == other.class && other.children == children
end
# True if \{#to\_s} will return `nil`;
# that is, if the node shouldn't be rendered.
# Should only be called in a static tree.
#
# @return [Boolean]
def invisible?; false; end
# The output style. See {file:SASS_REFERENCE.md#Options the Sass options documentation}.
#
# @return [Symbol]
def style
@options[:style]
end
# Computes the CSS corresponding to this static CSS tree.
#
# @return [String] The resulting CSS
# @see Sass::Tree
def css
Sass::Tree::Visitors::ToCss.new.visit(self)
end
# Computes the CSS corresponding to this static CSS tree, along with
# the respective source map.
#
# @return [(String, Sass::Source::Map)] The resulting CSS and the source map
# @see Sass::Tree
def css_with_sourcemap
visitor = Sass::Tree::Visitors::ToCss.new(:build_source_mapping)
result = visitor.visit(self)
return result, visitor.source_mapping
end
# Returns a representation of the node for debugging purposes.
#
# @return [String]
def inspect
return self.class.to_s unless has_children
"(#{self.class} #{children.map {|c| c.inspect}.join(' ')})"
end
# Iterates through each node in the tree rooted at this node
# in a pre-order walk.
#
# @yield node
# @yieldparam node [Node] a node in the tree
def each
yield self
children.each {|c| c.each {|n| yield n}}
end
# Converts a node to Sass code that will generate it.
#
# @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
# @return [String] The Sass code corresponding to the node
def to_sass(options = {})
Sass::Tree::Visitors::Convert.visit(self, options, :sass)
end
# Converts a node to SCSS code that will generate it.
#
# @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
# @return [String] The Sass code corresponding to the node
def to_scss(options = {})
Sass::Tree::Visitors::Convert.visit(self, options, :scss)
end
# Return a deep clone of this node.
# The child nodes are cloned, but options are not.
#
# @return [Node]
def deep_copy
Sass::Tree::Visitors::DeepCopy.visit(self)
end
# Whether or not this node bubbles up through RuleNodes.
#
# @return [Boolean]
def bubbles?
false
end
protected
# @see Sass::Shared.balance
# @raise [Sass::SyntaxError] if the brackets aren't balanced
def balance(*args)
res = Sass::Shared.balance(*args)
return res if res
raise Sass::SyntaxError.new("Unbalanced brackets.", :line => line)
end
end
end
end