/usr/lib/ruby/vendor_ruby/sass/script/tree/funcall.rb is in ruby-sass 3.4.6-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | require 'sass/script/functions'
require 'sass/util/normalized_map'
module Sass::Script::Tree
# A SassScript parse node representing a function call.
#
# A function call either calls one of the functions in
# {Sass::Script::Functions}, or if no function with the given name exists it
# returns a string representation of the function call.
class Funcall < Node
# The name of the function.
#
# @return [String]
attr_reader :name
# The arguments to the function.
#
# @return [Array<Node>]
attr_reader :args
# The keyword arguments to the function.
#
# @return [Sass::Util::NormalizedMap<Node>]
attr_reader :keywords
# The first splat argument for this function, if one exists.
#
# This could be a list of positional arguments, a map of keyword
# arguments, or an arglist containing both.
#
# @return [Node?]
attr_accessor :splat
# The second splat argument for this function, if one exists.
#
# If this exists, it's always a map of keyword arguments, and
# \{#splat} is always either a list or an arglist.
#
# @return [Node?]
attr_accessor :kwarg_splat
# @param name [String] See \{#name}
# @param args [Array<Node>] See \{#args}
# @param keywords [Sass::Util::NormalizedMap<Node>] See \{#keywords}
# @param splat [Node] See \{#splat}
# @param kwarg_splat [Node] See \{#kwarg_splat}
def initialize(name, args, keywords, splat, kwarg_splat)
@name = name
@args = args
@keywords = keywords
@splat = splat
@kwarg_splat = kwarg_splat
super()
end
# @return [String] A string representation of the function call
def inspect
args = @args.map {|a| a.inspect}.join(', ')
keywords = Sass::Util.hash_to_a(@keywords.as_stored).
map {|k, v| "$#{k}: #{v.inspect}"}.join(', ')
# rubocop:disable RedundantSelf
if self.splat
splat = args.empty? && keywords.empty? ? "" : ", "
splat = "#{splat}#{self.splat.inspect}..."
splat = "#{splat}, #{kwarg_splat.inspect}..." if kwarg_splat
end
# rubocop:enable RedundantSelf
"#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
end
# @see Node#to_sass
def to_sass(opts = {})
arg_to_sass = lambda do |arg|
sass = arg.to_sass(opts)
sass = "(#{sass})" if arg.is_a?(Sass::Script::Tree::ListLiteral) && arg.separator == :comma
sass
end
args = @args.map(&arg_to_sass)
keywords = Sass::Util.hash_to_a(@keywords.as_stored).
map {|k, v| "$#{dasherize(k, opts)}: #{arg_to_sass[v]}"}
# rubocop:disable RedundantSelf
if self.splat
splat = "#{arg_to_sass[self.splat]}..."
kwarg_splat = "#{arg_to_sass[self.kwarg_splat]}..." if self.kwarg_splat
end
# rubocop:enable RedundantSelf
arglist = [args, splat, keywords, kwarg_splat].flatten.compact.join(', ')
"#{dasherize(name, opts)}(#{arglist})"
end
# Returns the arguments to the function.
#
# @return [Array<Node>]
# @see Node#children
def children
res = @args + @keywords.values
res << @splat if @splat
res << @kwarg_splat if @kwarg_splat
res
end
# @see Node#deep_copy
def deep_copy
node = dup
node.instance_variable_set('@args', args.map {|a| a.deep_copy})
copied_keywords = Sass::Util::NormalizedMap.new
@keywords.as_stored.each {|k, v| copied_keywords[k] = v.deep_copy}
node.instance_variable_set('@keywords', copied_keywords)
node
end
protected
# Evaluates the function call.
#
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
# @return [Sass::Script::Value] The SassScript object that is the value of the function call
# @raise [Sass::SyntaxError] if the function call raises an ArgumentError
def _perform(environment)
args = Sass::Util.enum_with_index(@args).
map {|a, i| perform_arg(a, environment, signature && signature.args[i])}
keywords = Sass::Util.map_hash(@keywords) do |k, v|
[k, perform_arg(v, environment, k.tr('-', '_'))]
end
splat = Sass::Tree::Visitors::Perform.perform_splat(
@splat, keywords, @kwarg_splat, environment)
if (fn = environment.function(@name))
return without_original(perform_sass_fn(fn, args, splat, environment))
end
args = construct_ruby_args(ruby_name, args, splat, environment)
if Sass::Script::Functions.callable?(ruby_name)
local_environment = Sass::Environment.new(environment.global_env, environment.options)
local_environment.caller = Sass::ReadOnlyEnvironment.new(environment, environment.options)
result = opts(Sass::Script::Functions::EvaluationContext.new(
local_environment).send(ruby_name, *args))
without_original(result)
else
opts(to_literal(args))
end
rescue ArgumentError => e
reformat_argument_error(e)
end
# Compass historically overrode this before it changed name to {Funcall#to_value}.
# We should get rid of it in the future.
def to_literal(args)
to_value(args)
end
# This method is factored out from `_perform` so that compass can override
# it with a cross-browser implementation for functions that require vendor prefixes
# in the generated css.
def to_value(args)
Sass::Script::Value::String.new("#{name}(#{args.join(', ')})")
end
private
def ruby_name
@ruby_name ||= @name.tr('-', '_')
end
def perform_arg(argument, environment, name)
return argument if signature && signature.delayed_args.include?(name)
argument.perform(environment)
end
def signature
@signature ||= Sass::Script::Functions.signature(name.to_sym, @args.size, @keywords.size)
end
def without_original(value)
return value unless value.is_a?(Sass::Script::Value::Number)
value = value.dup
value.original = nil
value
end
def construct_ruby_args(name, args, splat, environment)
args += splat.to_a if splat
# All keywords are contained in splat.keywords for consistency,
# even if there were no splats passed in.
old_keywords_accessed = splat.keywords_accessed
keywords = splat.keywords
splat.keywords_accessed = old_keywords_accessed
unless (signature = Sass::Script::Functions.signature(name.to_sym, args.size, keywords.size))
return args if keywords.empty?
raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments")
end
# If the user passes more non-keyword args than the function expects,
# but it does expect keyword args, Ruby's arg handling won't raise an error.
# Since we don't want to make functions think about this,
# we'll handle it for them here.
if signature.var_kwargs && !signature.var_args && args.size > signature.args.size
raise Sass::SyntaxError.new(
"#{args[signature.args.size].inspect} is not a keyword argument for `#{name}'")
elsif keywords.empty?
return args
end
argnames = signature.args[args.size..-1] || []
deprecated_argnames = (signature.deprecated && signature.deprecated[args.size..-1]) || []
args = args + argnames.zip(deprecated_argnames).map do |(argname, deprecated_argname)|
if keywords.has_key?(argname)
keywords.delete(argname)
elsif deprecated_argname && keywords.has_key?(deprecated_argname)
deprecated_argname = keywords.denormalize(deprecated_argname)
Sass::Util.sass_warn("DEPRECATION WARNING: The `$#{deprecated_argname}' argument for " +
"`#{@name}()' has been renamed to `$#{argname}'.")
keywords.delete(deprecated_argname)
else
raise Sass::SyntaxError.new("Function #{name} requires an argument named $#{argname}")
end
end
if keywords.size > 0
if signature.var_kwargs
# Don't pass a NormalizedMap to a Ruby function.
args << keywords.to_hash
else
argname = keywords.keys.sort.first
if signature.args.include?(argname)
raise Sass::SyntaxError.new(
"Function #{name} was passed argument $#{argname} both by position and by name")
else
raise Sass::SyntaxError.new(
"Function #{name} doesn't have an argument named $#{argname}")
end
end
end
args
end
def perform_sass_fn(function, args, splat, environment)
Sass::Tree::Visitors::Perform.perform_arguments(function, args, splat, environment) do |env|
env.caller = Sass::Environment.new(environment)
val = catch :_sass_return do
function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, env)}
raise Sass::SyntaxError.new("Function #{@name} finished without @return")
end
val
end
end
def reformat_argument_error(e)
message = e.message
# If this is a legitimate Ruby-raised argument error, re-raise it.
# Otherwise, it's an error in the user's stylesheet, so wrap it.
if Sass::Util.rbx?
# Rubinius has a different error report string than vanilla Ruby. It
# also doesn't put the actual method for which the argument error was
# thrown in the backtrace, nor does it include `send`, so we look for
# `_perform`.
if e.message =~ /^method '([^']+)': given (\d+), expected (\d+)/
error_name, given, expected = $1, $2, $3
raise e if error_name != ruby_name || e.backtrace[0] !~ /:in `_perform'$/
message = "wrong number of arguments (#{given} for #{expected})"
end
elsif Sass::Util.jruby?
if Sass::Util.jruby1_6?
should_maybe_raise = e.message =~ /^wrong number of arguments \((\d+) for (\d+)\)/ &&
# The one case where JRuby does include the Ruby name of the function
# is manually-thrown ArgumentErrors, which are indistinguishable from
# legitimate ArgumentErrors. We treat both of these as
# Sass::SyntaxErrors even though it can hide Ruby errors.
e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/
else
should_maybe_raise =
e.message =~ /^wrong number of arguments calling `[^`]+` \((\d+) for (\d+)\)/
given, expected = $1, $2
end
if should_maybe_raise
# JRuby 1.7 includes __send__ before send and _perform.
trace = e.backtrace.dup
raise e if !Sass::Util.jruby1_6? && trace.shift !~ /:in `__send__'$/
# JRuby (as of 1.7.2) doesn't put the actual method
# for which the argument error was thrown in the backtrace, so we
# detect whether our send threw an argument error.
if !(trace[0] =~ /:in `send'$/ && trace[1] =~ /:in `_perform'$/)
raise e
elsif !Sass::Util.jruby1_6?
# JRuby 1.7 doesn't use standard formatting for its ArgumentErrors.
message = "wrong number of arguments (#{given} for #{expected})"
end
end
elsif e.message =~ /^wrong number of arguments \(\d+ for \d+\)/ &&
e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/
raise e
end
raise Sass::SyntaxError.new("#{message} for `#{name}'")
end
end
end
|