Skip to content

Commit

Permalink
Extract shared behaviour of test disco listeners
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcrocha committed Mar 11, 2025
1 parent a205912 commit 3474d77
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 86 deletions.
89 changes: 89 additions & 0 deletions lib/ruby_lsp/listeners/discover_tests.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# typed: strict
# frozen_string_literal: true

module RubyLsp
module Listeners
class DiscoverTests
extend T::Helpers
abstract!

include Requests::Support::Common

DYNAMIC_REFERENCE_MARKER = "<dynamic_reference>"

#: (ResponseBuilders::TestCollection response_builder, GlobalState global_state, Prism::Dispatcher dispatcher, URI::Generic uri) -> void
def initialize(response_builder, global_state, dispatcher, uri)
@response_builder = response_builder
@uri = uri
@index = T.let(global_state.index, RubyIndexer::Index)
@visibility_stack = T.let([:public], T::Array[Symbol])
@nesting = T.let([], T::Array[String])

@fully_qualified_name = T.let("", String)
@attached_ancestors = T.let([], T::Array[String])
end

#: (Prism::ClassNode node) -> void
def on_class_node_enter(node)
@visibility_stack << :public
name = constant_name(node.constant_path)
name ||= name_with_dynamic_reference(node.constant_path)

@fully_qualified_name = calc_fully_qualified_name(name)
@attached_ancestors = calc_attached_ancestors(node, @fully_qualified_name)

@nesting << name
end

#: (Prism::ModuleNode node) -> void
def on_module_node_enter(node)
@visibility_stack << :public

name = constant_name(node.constant_path)
name ||= name_with_dynamic_reference(node.constant_path)

@nesting << name
end

#: (Prism::ModuleNode node) -> void
def on_module_node_leave(node)
@visibility_stack.pop
@nesting.pop
end

#: (Prism::ClassNode node) -> void
def on_class_node_leave(node)
@visibility_stack.pop
@nesting.pop
end

#: (Prism::CallNode node) -> void
def on_call_node_enter(node); end

#: (Prism::CallNode node) -> void
def on_call_node_leave(node); end

private

#: (String? name) -> String
def calc_fully_qualified_name(name)
RubyIndexer::Index.actual_nesting(@nesting, name).join("::")
end

#: (Prism::ClassNode node, String fully_qualified_name) -> Array[String]
def calc_attached_ancestors(node, fully_qualified_name)
@index.linearized_ancestors_of(fully_qualified_name)
rescue RubyIndexer::Index::NonExistingNamespaceError
# When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still
# provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test
[node.superclass&.slice].compact
end

#: (Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode node) -> String
def name_with_dynamic_reference(node)
slice = node.slice
slice.gsub(/((?<=::)|^)[a-z]\w*/, DYNAMIC_REFERENCE_MARKER)
end
end
end
end
55 changes: 13 additions & 42 deletions lib/ruby_lsp/listeners/spec_style.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,16 @@

module RubyLsp
module Listeners
class SpecStyle
class SpecStyle < DiscoverTests
extend T::Sig
include Requests::Support::Common

DYNAMIC_REFERENCE_MARKER = "<dynamic_reference>"

#: (response_builder: ResponseBuilders::TestCollection, global_state: GlobalState, dispatcher: Prism::Dispatcher, uri: URI::Generic) -> void
def initialize(response_builder, global_state, dispatcher, uri)
@response_builder = response_builder
@uri = uri
@index = T.let(global_state.index, RubyIndexer::Index)
@visibility_stack = T.let([:public], T::Array[Symbol])
@nesting = T.let([], T::Array[String])
super

@describe_block_nesting = T.let([], T::Array[String])
@spec_class_stack = T.let([], T::Array[T::Boolean])

Expand All @@ -32,47 +29,27 @@ def initialize(response_builder, global_state, dispatcher, uri)

#: (node: Prism::ClassNode) -> void
def on_class_node_enter(node)
@visibility_stack << :public
name = constant_name(node.constant_path)
name ||= name_with_dynamic_reference(node.constant_path)

fully_qualified_name = RubyIndexer::Index.actual_nesting(@nesting, name).join("::")

attached_ancestors = begin
@index.linearized_ancestors_of(fully_qualified_name)
rescue RubyIndexer::Index::NonExistingNamespaceError
# When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still
# provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test
[node.superclass&.slice].compact
end
super

is_spec = attached_ancestors.include?("Minitest::Spec")
is_spec = @attached_ancestors.include?("Minitest::Spec")
@spec_class_stack.push(is_spec)
end

#: (node: Prism::ClassNode) -> void
def on_class_node_leave(node)
super

@nesting << name
@spec_class_stack.pop
end

#: (node: Prism::ModuleNode) -> void
def on_module_node_enter(node)
@visibility_stack << :public

name = constant_name(node.constant_path)
name ||= name_with_dynamic_reference(node.constant_path)

@nesting << name
super
end

#: (node: Prism::ModuleNode) -> void
def on_module_node_leave(node)
@visibility_stack.pop
@nesting.pop
end

#: (node: Prism::ClassNode) -> void
def on_class_node_leave(node)
@visibility_stack.pop
@nesting.pop
@spec_class_stack.pop
super
end

#: (node: Prism::CallNode) -> void
Expand Down Expand Up @@ -188,12 +165,6 @@ def in_spec_context?

T.must(@spec_class_stack.last)
end

#: (node: Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode) -> String
def name_with_dynamic_reference(node)
slice = node.slice
slice.gsub(/((?<=::)|^)[a-z]\w*/, DYNAMIC_REFERENCE_MARKER)
end
end
end
end
58 changes: 14 additions & 44 deletions lib/ruby_lsp/listeners/test_style.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

module RubyLsp
module Listeners
class TestStyle
class TestStyle < DiscoverTests
class << self
# Resolves the minimal set of commands required to execute the requested tests
#: (Array[Hash[Symbol, untyped]]) -> Array[String]
Expand Down Expand Up @@ -132,11 +132,8 @@ def handle_test_unit_groups(file_path, groups_and_examples)

#: (ResponseBuilders::TestCollection response_builder, GlobalState global_state, Prism::Dispatcher dispatcher, URI::Generic uri) -> void
def initialize(response_builder, global_state, dispatcher, uri)
@response_builder = response_builder
@uri = uri
@index = T.let(global_state.index, RubyIndexer::Index)
@visibility_stack = T.let([:public], T::Array[Symbol])
@nesting = T.let([], T::Array[String])
super

@framework_tag = T.let(:minitest, Symbol)

dispatcher.register(
Expand All @@ -153,55 +150,34 @@ def initialize(response_builder, global_state, dispatcher, uri)

#: (Prism::ClassNode node) -> void
def on_class_node_enter(node)
@visibility_stack << :public
name = constant_name(node.constant_path)
name ||= name_with_dynamic_reference(node.constant_path)

fully_qualified_name = RubyIndexer::Index.actual_nesting(@nesting, name).join("::")

attached_ancestors = begin
@index.linearized_ancestors_of(fully_qualified_name)
rescue RubyIndexer::Index::NonExistingNamespaceError
# When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still
# provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test
[node.superclass&.slice].compact
end
super

@framework_tag = :test_unit if attached_ancestors.include?("Test::Unit::TestCase")
@framework_tag = :test_unit if @attached_ancestors.include?("Test::Unit::TestCase")

if @framework_tag == :test_unit || non_declarative_minitest?(attached_ancestors, fully_qualified_name)
if @framework_tag == :test_unit || non_declarative_minitest?(@attached_ancestors, @fully_qualified_name)
@response_builder.add(Requests::Support::TestItem.new(
fully_qualified_name,
fully_qualified_name,
@fully_qualified_name,
@fully_qualified_name,
@uri,
range_from_node(node),
tags: [@framework_tag],
))
end
end

@nesting << name
#: (Prism::ClassNode node) -> void
def on_class_node_leave(node)
super
end

#: (Prism::ModuleNode node) -> void
def on_module_node_enter(node)
@visibility_stack << :public

name = constant_name(node.constant_path)
name ||= name_with_dynamic_reference(node.constant_path)

@nesting << name
super
end

#: (Prism::ModuleNode node) -> void
def on_module_node_leave(node)
@visibility_stack.pop
@nesting.pop
end

#: (Prism::ClassNode node) -> void
def on_class_node_leave(node)
@visibility_stack.pop
@nesting.pop
super
end

#: (Prism::DefNode node) -> void
Expand Down Expand Up @@ -259,12 +235,6 @@ def non_declarative_minitest?(attached_ancestors, fully_qualified_name)
rescue RubyIndexer::Index::NonExistingNamespaceError
true
end

#: ((Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode) node) -> String
def name_with_dynamic_reference(node)
slice = node.slice
slice.gsub(/((?<=::)|^)[a-z]\w*/, DYNAMIC_REFERENCE_MARKER)
end
end
end
end
1 change: 1 addition & 0 deletions lib/ruby_lsp/requests/discover_tests.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# typed: strict
# frozen_string_literal: true

require "ruby_lsp/listeners/discover_tests"
require "ruby_lsp/listeners/test_style"
require "ruby_lsp/listeners/spec_style"

Expand Down

0 comments on commit 3474d77

Please sign in to comment.