lib/tasks/helpers/ast_traverser.rb



# frozen_string_literal: true

require "ariadne/view_components/statuses"
require_relative "../../../app/lib/ariadne/view_helper"

# :nodoc:
class AstTraverser
  include RuboCop::AST::Traversal

  attr_reader :stats

  def initialize
    @stats = {}
  end

  def on_send(node)
    return super(node) unless component_node?(node)

    name = component_name(node)
    args = extract_arguments(node, name)

    @stats[name] = { path: node.loc.expression.source_buffer.name }
    @stats[name][:arguments] = args unless args.empty?

    super(node) # recursively iterate over children
  end

  def view_helpers
    @view_helpers ||= ::Ariadne::ViewHelper::HELPERS.keys.map { |key| "ariadne_#{key}".to_sym }
  end

  def component_node?(node)
    view_helpers.include?(node.method_name) || (node.method_name == :new && !node.receiver.nil? && ::Ariadne::ViewComponents::STATUSES.key?(node.receiver.const_name))
  end

  def component_name(node)
    return node.receiver.const_name if node.method_name == :new

    helper_key = node.method_name.to_s.gsub("ariadne_", "").to_sym
    Ariadne::ViewHelper::HELPERS[helper_key]
  end

  def extract_arguments(node, name)
    args = node.arguments
    res = {}

    return res if args.empty?

    kwargs = args.last
    if kwargs.respond_to?(:pairs)
      res = kwargs.pairs.each_with_object({}) do |pair, h|
        h.merge!(extract_values(pair))
      end
    end

    # Heroicon is the only component that accepts positional arguments.
    res[:icon] = args.first.source if name == "Ariadne::HeroiconComponent" && args.size > 1

    res
  end

  def extract_values(pair)
    return { pair.key.value => pair.value.source } unless pair.value.type == :hash

    flatten_pairs(pair, prefix: "#{pair.key.value}-")
  end

  def flatten_pairs(pair, prefix: "")
    pair.value.pairs.each_with_object({}) do |value_pair, h|
      if value_pair.value.type == :hash
        h.merge!(flatten_pairs(value_pair, prefix: "#{prefix}#{value_pair.key.value}-"))
      else
        h["#{prefix}#{value_pair.key.value}"] = value_pair.value.source
      end
    end
  end
end