Skip to content

Commit

Permalink
Support go-to-definition for let(!) and subject declaration
Browse files Browse the repository at this point in the history
  • Loading branch information
st0012 committed Oct 6, 2024
1 parent fea0494 commit 4e10a90
Show file tree
Hide file tree
Showing 5 changed files with 372 additions and 5 deletions.
6 changes: 2 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ GEM
diff-lcs (1.5.1)
erubi (1.13.0)
io-console (0.7.2)
irb (1.14.0)
irb (1.14.1)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
json (2.7.2)
Expand Down Expand Up @@ -82,7 +82,6 @@ GEM
sorbet-static (= 0.5.11581)
sorbet-runtime (0.5.11581)
sorbet-static (0.5.11581-universal-darwin)
sorbet-static (0.5.11581-x86_64-linux)
sorbet-static-and-runtime (0.5.11581)
sorbet (= 0.5.11581)
sorbet-runtime (= 0.5.11581)
Expand Down Expand Up @@ -110,7 +109,6 @@ GEM

PLATFORMS
universal-darwin
x86_64-linux

DEPENDENCIES
debug
Expand All @@ -126,4 +124,4 @@ DEPENDENCIES
tapioca (~> 0.11)

BUNDLED WITH
2.5.4
2.5.11
22 changes: 21 additions & 1 deletion lib/ruby_lsp/ruby_lsp_rspec/addon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@

require_relative "code_lens"
require_relative "document_symbol"
require_relative "definition"
require_relative "indexing_enhancement"

module RubyLsp
module RSpec
class Addon < ::RubyLsp::Addon
extend T::Sig

sig { override.params(global_state: GlobalState, message_queue: Thread::Queue).void }
def activate(global_state, message_queue); end
def activate(global_state, message_queue)
@index = T.let(global_state.index, T.nilable(RubyIndexer::Index))
global_state.index.register_enhancement(IndexingEnhancement.new)
end

sig { override.void }
def deactivate; end
Expand Down Expand Up @@ -47,6 +52,21 @@ def create_document_symbol_listener(response_builder, dispatcher)
DocumentSymbol.new(response_builder, dispatcher)
end

sig do
override.params(
response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
Interface::Location,
Interface::LocationLink,
)],
uri: URI::Generic,
node_context: NodeContext,
dispatcher: Prism::Dispatcher,
).void
end
def create_definition_listener(response_builder, uri, node_context, dispatcher)
Definition.new(response_builder, uri, node_context, T.must(@index), dispatcher)
end

sig { override.returns(String) }
def name
"Ruby LSP RSpec"
Expand Down
55 changes: 55 additions & 0 deletions lib/ruby_lsp/ruby_lsp_rspec/definition.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# typed: strict
# frozen_string_literal: true

module RubyLsp
module RSpec
class Definition
extend T::Sig

include ::RubyLsp::Requests::Support::Common

sig do
params(
response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
Interface::Location,
Interface::LocationLink,
)],
uri: URI::Generic,
node_context: NodeContext,
index: RubyIndexer::Index,
dispatcher: Prism::Dispatcher,
).void
end
def initialize(response_builder, uri, node_context, index, dispatcher)
@response_builder = response_builder
@uri = uri
@node_context = node_context
@index = index
dispatcher.register(self, :on_call_node_enter)
end

sig { params(node: Prism::CallNode).void }
def on_call_node_enter(node)
message = node.message
return unless message

return if @node_context.locals_for_scope.include?(message)

entries = @index[message]
return unless entries
return if entries.empty?

entries.each do |entry|
# Technically, let can be defined in a different file, but we're not going to handle that case yet
next unless entry.file_path == @uri.to_standardized_path

@response_builder << Interface::LocationLink.new(
target_uri: URI::Generic.from_path(path: entry.file_path).to_s,
target_range: range_from_location(entry.location),
target_selection_range: range_from_location(entry.name_location),
)
end
end
end
end
end
93 changes: 93 additions & 0 deletions lib/ruby_lsp/ruby_lsp_rspec/indexing_enhancement.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# typed: strict
# frozen_string_literal: true

module RubyLsp
module RSpec
class IndexingEnhancement
extend T::Sig
include RubyIndexer::Enhancement

sig do
override.params(
index: RubyIndexer::Index,
owner: T.nilable(RubyIndexer::Entry::Namespace),
node: Prism::CallNode,
file_path: String,
).void
end
def on_call_node(index, owner, node, file_path)
return if node.receiver

name = node.name

case name
when :let, :let!
block_node = node.block
return unless block_node

arguments = node.arguments
return unless arguments

return if arguments.arguments.count != 1

method_name_node = T.must(arguments.arguments.first)

method_name = case method_name_node
when Prism::StringNode
method_name_node.slice
when Prism::SymbolNode
method_name_node.unescaped
end

return unless method_name

index.add(RubyIndexer::Entry::Method.new(
method_name,
file_path,
block_node.location,
block_node.location,
nil,
index.configuration.encoding,
[RubyIndexer::Entry::Signature.new([])],
RubyIndexer::Entry::Visibility::PUBLIC,
owner,
))
when :subject, :subject!
block_node = node.block
return unless block_node

arguments = node.arguments

if arguments && arguments.arguments.count == 1
method_name_node = T.must(arguments.arguments.first)
end

method_name = if method_name_node
case method_name_node
when Prism::StringNode
method_name_node.slice
when Prism::SymbolNode
method_name_node.unescaped
end
else
"subject"
end

return unless method_name

index.add(RubyIndexer::Entry::Method.new(
method_name,
file_path,
block_node.location,
block_node.location,
nil,
index.configuration.encoding,
[RubyIndexer::Entry::Signature.new([])],
RubyIndexer::Entry::Visibility::PUBLIC,
owner,
))
end
end
end
end
end
Loading

0 comments on commit 4e10a90

Please sign in to comment.