class RubyIndexer::EnhancementTest

def name

def name
  "TestEnhancement"
end

def name

def name
  "TestEnhancement"
end

def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod

rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
  owner = @listener.current_owner
  return unless owner
  return unless call_node.name == :extend
  arguments = call_node.arguments&.arguments
  return unless arguments
  arguments.each do |node|
    next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
    module_name = node.full_name
    next unless module_name == "ActiveSupport::Concern"
    @listener.register_included_hook do |index, base|
      class_methods_name = "#{owner.name}::ClassMethods"
      if index.indexed?(class_methods_name)
        singleton = index.existing_or_new_singleton_class(base.name)
        singleton.mixin_operations << Entry::Include.new(class_methods_name)
      end
    end
    @listener.add_method(
      "new_method",
      call_node.location,
      [Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
    )
  rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
         Prism::ConstantPathNode::MissingNodesInConstantPathError
    # Do nothing
  end
end

def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod

rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
  return unless @listener.current_owner
  name = node.name
  return unless name == :has_many
  arguments = node.arguments&.arguments
  return unless arguments
  association_name = arguments.first
  return unless association_name.is_a?(Prism::SymbolNode)
  @listener.add_method(
    association_name.value, #: as !nil
    association_name.location,
    [],
  )
end

def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod

rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
  raise "Error"
end

def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod

rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
  owner = @listener.current_owner
  return unless owner
  case call_node.name
  when :class_methods
    @listener.add_module("ClassMethods", call_node.location, call_node.location)
  when :extend
    arguments = call_node.arguments&.arguments
    return unless arguments
    arguments.each do |node|
      next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
      module_name = node.full_name
      next unless module_name == "ActiveSupport::Concern"
      @listener.register_included_hook do |index, base|
        class_methods_name = "#{owner.name}::ClassMethods"
        if index.indexed?(class_methods_name)
          singleton = index.existing_or_new_singleton_class(base.name)
          singleton.mixin_operations << Entry::Include.new(class_methods_name)
        end
      end
    end
  end
end

def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod

rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
  case call_node.name
  when :context
    arguments = call_node.arguments&.arguments
    first_argument = arguments&.first
    return unless first_argument.is_a?(Prism::StringNode)
    @listener.add_class(
      "<RSpec:#{first_argument.content}>",
      call_node.location,
      first_argument.location,
    )
  when :subject
    @listener.add_method("subject", call_node.location, [])
  end
end

def on_call_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod

rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
def on_call_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
  raise "Error"
end

def on_call_node_leave(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod

rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
def on_call_node_leave(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
  return unless call_node.name == :class_methods
  @listener.pop_namespace_stack
end

def on_call_node_leave(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod

rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
def on_call_node_leave(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
  return unless call_node.name == :context
  @listener.pop_namespace_stack
end

def teardown

def teardown
  super
  Enhancement.clear
end

def test_advancing_namespace_stack_from_enhancement

def test_advancing_namespace_stack_from_enhancement
  Class.new(Enhancement) do
    def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
      owner = @listener.current_owner
      return unless owner
      case call_node.name
      when :class_methods
        @listener.add_module("ClassMethods", call_node.location, call_node.location)
      when :extend
        arguments = call_node.arguments&.arguments
        return unless arguments
        arguments.each do |node|
          next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
          module_name = node.full_name
          next unless module_name == "ActiveSupport::Concern"
          @listener.register_included_hook do |index, base|
            class_methods_name = "#{owner.name}::ClassMethods"
            if index.indexed?(class_methods_name)
              singleton = index.existing_or_new_singleton_class(base.name)
              singleton.mixin_operations << Entry::Include.new(class_methods_name)
            end
          end
        end
      end
    end
    def on_call_node_leave(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
      return unless call_node.name == :class_methods
      @listener.pop_namespace_stack
    end
  end
  index(<<~RUBY)
    module ActiveSupport
      module Concern
      end
    end
    module MyConcern
      extend ActiveSupport::Concern
      class_methods do
        def foo; end
      end
    end
    class User
      include MyConcern
    end
  RUBY
  assert_equal(
    [
      "User::<Class:User>",
      "MyConcern::ClassMethods",
      "Object::<Class:Object>",
      "BasicObject::<Class:BasicObject>",
      "Class",
      "Module",
      "Object",
      "Kernel",
      "BasicObject",
    ],
    @index.linearized_ancestors_of("User::<Class:User>"),
  )
  refute_nil(@index.resolve_method("foo", "User::<Class:User>"))
end

def test_creating_anonymous_classes_from_enhancement

def test_creating_anonymous_classes_from_enhancement
  Class.new(Enhancement) do
    def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
      case call_node.name
      when :context
        arguments = call_node.arguments&.arguments
        first_argument = arguments&.first
        return unless first_argument.is_a?(Prism::StringNode)
        @listener.add_class(
          "<RSpec:#{first_argument.content}>",
          call_node.location,
          first_argument.location,
        )
      when :subject
        @listener.add_method("subject", call_node.location, [])
      end
    end
    def on_call_node_leave(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
      return unless call_node.name == :context
      @listener.pop_namespace_stack
    end
  end
  index(<<~RUBY)
    context "does something" do
      subject { call_whatever }
    end
  RUBY
  refute_nil(@index.resolve_method("subject", "<RSpec:does something>"))
end

def test_enhancing_indexing_configuration_dsl

def test_enhancing_indexing_configuration_dsl
  Class.new(Enhancement) do
    def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
      return unless @listener.current_owner
      name = node.name
      return unless name == :has_many
      arguments = node.arguments&.arguments
      return unless arguments
      association_name = arguments.first
      return unless association_name.is_a?(Prism::SymbolNode)
      @listener.add_method(
        association_name.value, #: as !nil
        association_name.location,
        [],
      )
    end
  end
  index(<<~RUBY)
    module ActiveSupport
      module Concern
        def self.extended(base)
          base.class_eval("def new_method(a); end")
        end
      end
    end
    module ActiveRecord
      module Associations
        extend ActiveSupport::Concern
        module ClassMethods
          def belongs_to(something); end
        end
      end
      class Base
        include Associations
      end
    end
    class User < ActiveRecord::Base
      has_many :posts
    end
  RUBY
  assert_entry("posts", Entry::Method, "/fake/path/foo.rb:23-11:23-17")
end

def test_enhancing_indexing_included_hook

def test_enhancing_indexing_included_hook
  Class.new(Enhancement) do
    def on_call_node_enter(call_node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
      owner = @listener.current_owner
      return unless owner
      return unless call_node.name == :extend
      arguments = call_node.arguments&.arguments
      return unless arguments
      arguments.each do |node|
        next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
        module_name = node.full_name
        next unless module_name == "ActiveSupport::Concern"
        @listener.register_included_hook do |index, base|
          class_methods_name = "#{owner.name}::ClassMethods"
          if index.indexed?(class_methods_name)
            singleton = index.existing_or_new_singleton_class(base.name)
            singleton.mixin_operations << Entry::Include.new(class_methods_name)
          end
        end
        @listener.add_method(
          "new_method",
          call_node.location,
          [Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
        )
      rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
             Prism::ConstantPathNode::MissingNodesInConstantPathError
        # Do nothing
      end
    end
  end
  index(<<~RUBY)
    module ActiveSupport
      module Concern
        def self.extended(base)
          base.class_eval("def new_method(a); end")
        end
      end
    end
    module ActiveRecord
      module Associations
        extend ActiveSupport::Concern
        module ClassMethods
          def belongs_to(something); end
        end
      end
      class Base
        include Associations
      end
    end
    class User < ActiveRecord::Base
    end
  RUBY
  assert_equal(
    [
      "User::<Class:User>",
      "ActiveRecord::Base::<Class:Base>",
      "ActiveRecord::Associations::ClassMethods",
      "Object::<Class:Object>",
      "BasicObject::<Class:BasicObject>",
      "Class",
      "Module",
      "Object",
      "Kernel",
      "BasicObject",
    ],
    @index.linearized_ancestors_of("User::<Class:User>"),
  )
  assert_entry("new_method", Entry::Method, "/fake/path/foo.rb:10-4:10-33")
end

def test_error_handling_in_on_call_node_enter_enhancement

def test_error_handling_in_on_call_node_enter_enhancement
  Class.new(Enhancement) do
    def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
      raise "Error"
    end
    class << self
      def name
        "TestEnhancement"
      end
    end
  end
  _stdout, stderr = capture_io do
    index(<<~RUBY)
      module ActiveSupport
        module Concern
          def self.extended(base)
            base.class_eval("def new_method(a); end")
          end
        end
      end
    RUBY
  end
  assert_match(
    %r{Indexing error in file:///fake/path/foo\.rb with 'TestEnhancement' on call node enter enhancement},
    stderr,
  )
  # The module should still be indexed
  assert_entry("ActiveSupport::Concern", Entry::Module, "/fake/path/foo.rb:1-2:5-5")
end

def test_error_handling_in_on_call_node_leave_enhancement

def test_error_handling_in_on_call_node_leave_enhancement
  Class.new(Enhancement) do
    def on_call_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
      raise "Error"
    end
    class << self
      def name
        "TestEnhancement"
      end
    end
  end
  _stdout, stderr = capture_io do
    index(<<~RUBY)
      module ActiveSupport
        module Concern
          def self.extended(base)
            base.class_eval("def new_method(a); end")
          end
        end
      end
    RUBY
  end
  assert_match(
    %r{Indexing error in file:///fake/path/foo\.rb with 'TestEnhancement' on call node leave enhancement},
    stderr,
  )
  # The module should still be indexed
  assert_entry("ActiveSupport::Concern", Entry::Module, "/fake/path/foo.rb:1-2:5-5")
end