lib/sass/compiler/host/function_registry.rb



# frozen_string_literal: true

module Sass
  class Compiler
    class Host
      # The {FunctionRegistry} class.
      #
      # It stores sass custom functions and handles function calls.
      class FunctionRegistry
        attr_reader :global_functions

        def initialize(functions, alert_color:)
          functions = functions.transform_keys(&:to_s)

          @global_functions = functions.keys
          @functions_by_name = functions.transform_keys do |signature|
            index = signature.index('(')
            if index
              signature.slice(0, index)
            else
              signature
            end
          end

          @id = 0
          @functions_by_id = {}.compare_by_identity
          @ids_by_function = {}.compare_by_identity

          @highlight = alert_color
        end

        def register(function)
          @ids_by_function.fetch(function) do |fn|
            id = @id
            @id = id.next

            @functions_by_id[id] = fn
            @ids_by_function[fn] = id
          end
        end

        def function_call(function_call_request)
          oneof = function_call_request.identifier
          identifier = function_call_request.public_send(oneof)
          function = case oneof
                     when :name
                       @functions_by_name[identifier]
                     when :function_id
                       @functions_by_id[identifier]
                     else
                       raise ArgumentError, "Unknown FunctionCallRequest.identifier #{identifier}"
                     end

          arguments = function_call_request.arguments.map do |argument|
            protofier.from_proto(argument)
          end

          success = protofier.to_proto(function.call(arguments))
          accessed_argument_lists = arguments.filter_map do |argument|
            if argument.is_a?(Sass::Value::ArgumentList) && argument.instance_eval { @keywords_accessed }
              argument.instance_eval { @id }
            end
          end

          EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
            id: function_call_request.id,
            success:,
            accessed_argument_lists:
          )
        rescue StandardError => e
          EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
            id: function_call_request.id,
            error: e.full_message(highlight: @highlight, order: :top)
          )
        end

        private

        def protofier
          @protofier ||= Protofier.new(self)
        end
      end

      private_constant :FunctionRegistry
    end
  end
end