Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Extract shared behaviour of test disco listeners #3288

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since its now in the parent, we can remove this include from the child classes.


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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need these methods that do nothing but call super.

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
Loading