blob: 35914b4bd8acfa40e0bb4f4da8864a5a252c93d8 [file] [log] [blame]
module Sass::Script::Tree
# A SassScript object representing `#{}` interpolation outside a string.
#
# @see StringInterpolation
class Interpolation < Node
# @return [Node] The SassScript before the interpolation
attr_reader :before
# @return [Node] The SassScript within the interpolation
attr_reader :mid
# @return [Node] The SassScript after the interpolation
attr_reader :after
# @return [Boolean] Whether there was whitespace between `before` and `#{`
attr_reader :whitespace_before
# @return [Boolean] Whether there was whitespace between `}` and `after`
attr_reader :whitespace_after
# @return [Boolean] Whether the original format of the interpolation was
# plain text, not an interpolation. This is used when converting back to
# SassScript.
attr_reader :originally_text
# @return [Boolean] Whether a color value passed to the interpolation should
# generate a warning.
attr_reader :warn_for_color
# The type of interpolation deprecation for this node.
#
# This can be `:none`, indicating that the node doesn't use deprecated
# interpolation; `:immediate`, indicating that a deprecation warning should
# be emitted as soon as possible; or `:potential`, indicating that a
# deprecation warning should be emitted if the resulting string is used in a
# way that would distinguish it from a list.
#
# @return [Symbol]
attr_reader :deprecation
# Interpolation in a property is of the form `before #{mid} after`.
#
# @param before [Node] See {Interpolation#before}
# @param mid [Node] See {Interpolation#mid}
# @param after [Node] See {Interpolation#after}
# @param wb [Boolean] See {Interpolation#whitespace_before}
# @param wa [Boolean] See {Interpolation#whitespace_after}
# @param originally_text [Boolean] See {Interpolation#originally_text}
# @param warn_for_color [Boolean] See {Interpolation#warn_for_color}
# @comment
# rubocop:disable ParameterLists
def initialize(before, mid, after, wb, wa, opts = {})
# rubocop:enable ParameterLists
@before = before
@mid = mid
@after = after
@whitespace_before = wb
@whitespace_after = wa
@originally_text = opts[:originally_text] || false
@warn_for_color = opts[:warn_for_color] || false
@deprecation = opts[:deprecation] || :none
end
# @return [String] A human-readable s-expression representation of the interpolation
def inspect
"(interpolation #{@before.inspect} #{@mid.inspect} #{@after.inspect})"
end
# @see Node#to_sass
def to_sass(opts = {})
return to_quoted_equivalent.to_sass if deprecation == :immediate
res = ""
res << @before.to_sass(opts) if @before
res << ' ' if @before && @whitespace_before
res << '#{' unless @originally_text
res << @mid.to_sass(opts)
res << '}' unless @originally_text
res << ' ' if @after && @whitespace_after
res << @after.to_sass(opts) if @after
res
end
# Returns an `unquote()` expression that will evaluate to the same value as
# this interpolation.
#
# @return [Sass::Script::Tree::Node]
def to_quoted_equivalent
Funcall.new(
"unquote",
[to_string_interpolation(self)],
Sass::Util::NormalizedMap.new,
nil,
nil)
end
# Returns the three components of the interpolation, `before`, `mid`, and `after`.
#
# @return [Array<Node>]
# @see #initialize
# @see Node#children
def children
[@before, @mid, @after].compact
end
# @see Node#deep_copy
def deep_copy
node = dup
node.instance_variable_set('@before', @before.deep_copy) if @before
node.instance_variable_set('@mid', @mid.deep_copy)
node.instance_variable_set('@after', @after.deep_copy) if @after
node
end
protected
# Converts a script node into a corresponding string interpolation
# expression.
#
# @param node_or_interp [Sass::Script::Tree::Node]
# @return [Sass::Script::Tree::StringInterpolation]
def to_string_interpolation(node_or_interp)
unless node_or_interp.is_a?(Interpolation)
node = node_or_interp
return string_literal(node.value.to_s) if node.is_a?(Literal)
if node.is_a?(StringInterpolation)
return concat(string_literal(node.quote), concat(node, string_literal(node.quote)))
end
return StringInterpolation.new(string_literal(""), node, string_literal(""))
end
interp = node_or_interp
after_string_or_interp =
if interp.after
to_string_interpolation(interp.after)
else
string_literal("")
end
if interp.after && interp.whitespace_after
after_string_or_interp = concat(string_literal(' '), after_string_or_interp)
end
mid_string_or_interp = to_string_interpolation(interp.mid)
before_string_or_interp =
if interp.before
to_string_interpolation(interp.before)
else
string_literal("")
end
if interp.before && interp.whitespace_before
before_string_or_interp = concat(before_string_or_interp, string_literal(' '))
end
concat(before_string_or_interp, concat(mid_string_or_interp, after_string_or_interp))
end
private
# Evaluates the interpolation.
#
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
# @return [Sass::Script::Value::String]
# The SassScript string that is the value of the interpolation
def _perform(environment)
res = ""
res << @before.perform(environment).to_s if @before
res << " " if @before && @whitespace_before
val = @mid.perform(environment)
if @warn_for_color && val.is_a?(Sass::Script::Value::Color) && val.name
alternative = Operation.new(Sass::Script::Value::String.new("", :string), @mid, :plus)
Sass::Util.sass_warn <<MESSAGE
WARNING on line #{line}, column #{source_range.start_pos.offset}#{" of #{filename}" if filename}:
You probably don't mean to use the color value `#{val}' in interpolation here.
It may end up represented as #{val.inspect}, which will likely produce invalid CSS.
Always quote color names when using them as strings (for example, "#{val}").
If you really want to use the color value here, use `#{alternative.to_sass}'.
MESSAGE
end
res << val.to_s(:quote => :none)
res << " " if @after && @whitespace_after
res << @after.perform(environment).to_s if @after
str = Sass::Script::Value::String.new(
res, :identifier,
(to_quoted_equivalent.to_sass if deprecation == :potential))
str.source_range = source_range
opts(str)
end
# Concatenates two string literals or string interpolation expressions.
#
# @param string_or_interp1 [Sass::Script::Tree::Literal|Sass::Script::Tree::StringInterpolation]
# @param string_or_interp2 [Sass::Script::Tree::Literal|Sass::Script::Tree::StringInterpolation]
# @return [Sass::Script::Tree::StringInterpolation]
def concat(string_or_interp1, string_or_interp2)
if string_or_interp1.is_a?(Literal) && string_or_interp2.is_a?(Literal)
return string_literal(string_or_interp1.value.value + string_or_interp2.value.value)
end
if string_or_interp1.is_a?(Literal)
string = string_or_interp1
interp = string_or_interp2
before = string_literal(string.value.value + interp.before.value.value)
return StringInterpolation.new(before, interp.mid, interp.after)
end
StringInterpolation.new(
string_or_interp1.before,
string_or_interp1.mid,
concat(string_or_interp1.after, string_or_interp2))
end
# Returns a string literal with the given contents.
#
# @param string [String]
# @return string [Sass::Script::Tree::Literal]
def string_literal(string)
Literal.new(Sass::Script::Value::String.new(string, :string))
end
end
end