diff --git a/lib/ruby_lsp/listeners/discover_tests.rb b/lib/ruby_lsp/listeners/discover_tests.rb new file mode 100644 index 000000000..b6361625a --- /dev/null +++ b/lib/ruby_lsp/listeners/discover_tests.rb @@ -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 = "" + + #: (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 diff --git a/lib/ruby_lsp/listeners/spec_style.rb b/lib/ruby_lsp/listeners/spec_style.rb index 3da6ad4f1..65dcf428d 100644 --- a/lib/ruby_lsp/listeners/spec_style.rb +++ b/lib/ruby_lsp/listeners/spec_style.rb @@ -3,7 +3,7 @@ module RubyLsp module Listeners - class SpecStyle + class SpecStyle < DiscoverTests extend T::Sig include Requests::Support::Common @@ -11,11 +11,8 @@ class SpecStyle #: (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]) @@ -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 @@ -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 diff --git a/lib/ruby_lsp/listeners/test_style.rb b/lib/ruby_lsp/listeners/test_style.rb index a1f71ea79..98c28ce4f 100644 --- a/lib/ruby_lsp/listeners/test_style.rb +++ b/lib/ruby_lsp/listeners/test_style.rb @@ -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] @@ -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( @@ -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 @@ -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 diff --git a/lib/ruby_lsp/requests/discover_tests.rb b/lib/ruby_lsp/requests/discover_tests.rb index 1f2720c60..b9a7b3865 100644 --- a/lib/ruby_lsp/requests/discover_tests.rb +++ b/lib/ruby_lsp/requests/discover_tests.rb @@ -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"