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