Skip to content

Commit

Permalink
Add experimental/gotoRelevantFile lsp method
Browse files Browse the repository at this point in the history
Define and implement the experimental/gotoRelevantFile lsp method. This
request takes in a textDcoument param, and retusn an array of locations
that are related to the input file path.

Currently, the related file paths are
- test files, if the input file is a source file
- source files, if the input file is a test file

Basically it goes between source file <> test file.

This is implemented as a experimental method until something more
official comes along.
  • Loading branch information
jenny-codes committed Feb 19, 2025
1 parent 841e92c commit 30962b6
Show file tree
Hide file tree
Showing 5 changed files with 467 additions and 0 deletions.
203 changes: 203 additions & 0 deletions goto-benchmark.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@

workspace: /Users/jennyshih/src/github.com/Shopify/shopify/areas/core/shopify
path: /components/shopify_payments/app/services/shopify_payments/payments_processor/processor.rb
algorithm: recursive
result: 0.635863 2.821835 3.457698 ( 3.768692)

workspace: /Users/jennyshih/src/github.com/Shopify/shopify/areas/core/shopify
path: /components/shopify_payments/test/unit/services/shopify_payments/payments_processor/processor_test.rb
algorithm: recursive
result: 0.456094 2.042988 2.499082 ( 2.499641)

workspace: /Users/jennyshih/src/github.com/Shopify/shopify/areas/core/shopify
path: /components/shopify_payments/app/services/shopify_payments/payments_processor/processor.rb
algorithm: jaccard
result: 0.430905 0.775094 1.205999 ( 1.606421)

workspace: /Users/jennyshih/src/github.com/Shopify/shopify/areas/core/shopify
path: /components/shopify_payments/test/unit/services/shopify_payments/payments_processor/processor_test.rb
algorithm: jaccard
result: 0.151545 0.681743 0.833288 ( 0.855169)

workspace: /Users/jennyshih/src/github.com/Shopify/shopify/areas/core/shopify
path: /components/shopify_payments/app/models/payments/charge.rb
algorithm: jaccard
result: 0.396473 0.744343 1.140816 ( 1.245711)

workspace: /Users/jennyshih/src/github.com/Shopify/shopify/areas/core/shopify
path: /components/shopify_payments/test/unit/payments/charge_test.rb
algorithm: jaccard
result: 0.145461 0.657387 0.802848 ( 0.804873)

workspace: /Users/jennyshih/src/github.com/Shopify/shopify/areas/core/shopify
path: /components/shopify_payments/app/models/payments/charge.rb
algorithm: jaccard
result: 0.404753 0.735576 1.140329 ( 1.437993)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/definition.rb
algorithm: jaccard
result: 0.055285 0.060584 0.115869 ( 0.116187)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /test/requests/definition_expectations_test.rb
algorithm: jaccard
result: 0.017593 0.070182 0.087775 ( 0.087766)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /test/requests/definition_expectations_test.rb
algorithm: jaccard
result: 0.018597 0.075378 0.093975 ( 0.094015)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /test/requests/definition_expectations_test.rb
algorithm: jaccard
result: 0.015376 0.061609 0.076985 ( 0.083124)

workspace: /Users/jennyshih/src/github.com/Shopify/shopify/areas/core/shopify
path: /components/shopify_payments/app/models/payments/charge.rb
algorithm: jaccard
result: 0.354486 0.737402 1.091888 ( 1.095194)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /Users/jennyshih/test/requests/goto_relevant_file_test.rb
algorithm: jaccard
result: 0.013733 0.041814 0.055547 ( 0.055547)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /Users/jennyshih/test/requests/goto_relevant_file_test.rb
algorithm: jaccard
result: 0.012587 0.051676 0.064263 ( 0.064629)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /Users/jennyshih/test/requests/goto_relevant_file_test.rb
algorithm: jaccard
result: 0.012543 0.050800 0.063343 ( 0.063347)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /Users/jennyshih/test/requests/goto_relevant_file_test.rb
algorithm: jaccard
result: 0.012556 0.050881 0.063437 ( 0.063444)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /Users/jennyshih/lib/ruby_lsp/requests/goto_relevant_file.rb
algorithm: jaccard
result: 0.037421 0.052222 0.089643 ( 0.091287)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /Users/jennyshih/test/requests/goto_relevant_file_test.rb
algorithm: jaccard
result: 0.012653 0.040534 0.053187 ( 0.053193)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /test/requests/goto_relevant_file_test.rb
algorithm: jaccard
result: 0.018579 0.054580 0.073159 ( 0.078505)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/goto_relevant_file.rb
algorithm: jaccard
result: 0.050857 0.067798 0.118655 ( 0.118625)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /test/requests/goto_relevant_file_test.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/lib/ruby_lsp/requests/goto_relevant_file.rb"]
algorithm: jaccard
benchmark: 0.012562 0.041259 0.053821 ( 0.053824)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /test/requests/goto_relevant_file_test.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/lib/ruby_lsp/requests/goto_relevant_file.rb"]
algorithm: jaccard
benchmark: 0.012651 0.041844 0.054495 ( 0.054500)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/goto_relevant_file.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/test/requests/goto_relevant_file_test.rb"]
algorithm: jaccard
benchmark: 0.037979 0.041264 0.079243 ( 0.079245)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/goto_relevant_file.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/test/requests/goto_relevant_file_test.rb"]
algorithm: jaccard
benchmark: 0.038095 0.042829 0.080924 ( 0.081500)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /test/requests/goto_relevant_file_test.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/lib/ruby_lsp/requests/goto_relevant_file.rb"]
algorithm: jaccard
benchmark: 0.012594 0.040656 0.053250 ( 0.053256)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /test/requests/goto_relevant_file_test.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/lib/ruby_lsp/requests/goto_relevant_file.rb"]
algorithm: jaccard
benchmark: 0.015564 0.043131 0.058695 ( 0.058743)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/goto_relevant_file.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/test/requests/goto_relevant_file_test.rb"]
algorithm: jaccard
benchmark: 0.040471 0.057309 0.097780 ( 0.098086)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/goto_relevant_file.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/test/requests/goto_relevant_file_test.rb"]
algorithm: jaccard
benchmark: 0.040048 0.055300 0.095348 ( 0.095679)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/goto_relevant_file.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/test/requests/goto_relevant_file_test.rb"]
algorithm: jaccard
benchmark: 0.050783 0.054876 0.105659 ( 0.105619)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /test/requests/goto_relevant_file_test.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/lib/ruby_lsp/requests/goto_relevant_file.rb"]
algorithm: jaccard
benchmark: 0.512373 0.105817 0.618190 ( 0.598842)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/goto_relevant_file.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/test/requests/goto_relevant_file_test.rb"]
algorithm: jaccard
benchmark: 0.040427 0.045368 0.085795 ( 0.086113)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /test/requests/goto_relevant_file_test.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/lib/ruby_lsp/requests/goto_relevant_file.rb"]
algorithm: jaccard
benchmark: 0.013609 0.056012 0.069621 ( 0.071070)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/goto_relevant_file.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/test/requests/goto_relevant_file_test.rb"]
algorithm: jaccard
benchmark: 0.040316 0.042764 0.083080 ( 0.083219)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/goto_relevant_file.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/test/requests/goto_relevant_file_test.rb"]
algorithm: jaccard
benchmark: 0.038456 0.051550 0.090006 ( 0.090602)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /test/requests/goto_relevant_file_test.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/lib/ruby_lsp/requests/goto_relevant_file.rb"]
algorithm: jaccard
benchmark: 0.012630 0.037974 0.050604 ( 0.050971)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/goto_relevant_file.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/test/requests/goto_relevant_file_test.rb"]
algorithm: jaccard
benchmark: 0.046277 0.067478 0.113755 ( 0.174708)

workspace: /Users/jennyshih/src/github.com/Shopify/ruby-lsp
path: /lib/ruby_lsp/requests/goto_relevant_file.rb
result: ["/Users/jennyshih/src/github.com/Shopify/ruby-lsp/test/requests/goto_relevant_file_test.rb"]
algorithm: jaccard
benchmark: 0.048515 0.079934 0.128449 ( 0.191184)

1 change: 1 addition & 0 deletions lib/ruby_lsp/internal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
require "ruby_lsp/requests/document_symbol"
require "ruby_lsp/requests/folding_ranges"
require "ruby_lsp/requests/formatting"
require "ruby_lsp/requests/goto_relevant_file"
require "ruby_lsp/requests/hover"
require "ruby_lsp/requests/inlay_hints"
require "ruby_lsp/requests/on_type_formatting"
Expand Down
83 changes: 83 additions & 0 deletions lib/ruby_lsp/requests/goto_relevant_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# typed: strict
# frozen_string_literal: true

module RubyLsp
module Requests
# Goto Relevant File is a custom [LSP
# request](https://microsoft.github.io/language-server-protocol/specification#requestMessage)
# that navigates to the relevant file for the current document.
# Currently, it supports source code file <> test file navigation.
class GotoRelevantFile < Request
extend T::Sig

TEST_KEYWORDS = ["test", "spec", "integration_test"]

sig { params(path: String).void }
def initialize(path)
super()

@workspace_path = T.let(Dir.pwd, String)
@path = T.let(path.delete_prefix(@workspace_path), String)
end

sig { override.returns(T::Array[String]) }
def perform
find_relevant_paths(@path)
end

private

sig { params(path: String).returns(T::Array[String]) }
def find_relevant_paths(path)
workspace_path = Dir.pwd
relpath = path.delete_prefix(workspace_path)

candidate_paths = Dir.glob(File.join("**", relevant_filename_pattern(relpath)))
return [] if candidate_paths.empty?

find_most_similar_with_jacaard(relpath, candidate_paths).map { File.join(workspace_path, _1) }
end

sig { params(path: String).returns(String) }
def relevant_filename_pattern(path)
input_basename = File.basename(path, File.extname(path))

test_prefix_pattern = /^(#{TEST_KEYWORDS.join("_|")}_)/
test_suffix_pattern = /(_#{TEST_KEYWORDS.join("|_")})$/
test_pattern = /#{test_prefix_pattern}|#{test_suffix_pattern}/

relevant_basename_pattern =
if input_basename.match?(test_pattern)
input_basename.gsub(test_pattern, "")
else
test_prefix_glob = "#{TEST_KEYWORDS.join("_,")}_"
test_suffix_glob = "_#{TEST_KEYWORDS.join(",_")}"

"{{#{test_prefix_glob}}#{input_basename},#{input_basename}{#{test_suffix_glob}}}"
end

"#{relevant_basename_pattern}#{File.extname(path)}"
end

sig { params(path: String, candidates: T::Array[String]).returns(T::Array[String]) }
def find_most_similar_with_jacaard(path, candidates)
dirs = get_dir_parts(path)

_, results = candidates
.group_by do |other_path|
other_dirs = get_dir_parts(other_path)
# Similarity score between the two directories
(dirs & other_dirs).size.to_f / (dirs | other_dirs).size
end
.max_by(&:first)

results || []
end

sig { params(path: String).returns(T::Set[String]) }
def get_dir_parts(path)
Set.new(File.dirname(path).split(File::SEPARATOR))
end
end
end
end
22 changes: 22 additions & 0 deletions lib/ruby_lsp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ def process_message(message)
diagnose_state(message)
when "rubyLsp/discoverTests"
discover_tests(message)
when "experimental/gotoRelevantFile"
experimental_goto_relevant_file(message)
when "$/cancelRequest"
@global_state.synchronize { @cancelled_requests << message[:params][:id] }
when nil
Expand Down Expand Up @@ -290,6 +292,7 @@ def run_initialize(message)
experimental: {
addon_detection: true,
compose_bundle: true,
goto_relevant_file: true,
},
),
serverInfo: {
Expand Down Expand Up @@ -1123,6 +1126,25 @@ def text_document_show_syntax_tree(message)
send_message(Result.new(id: message[:id], response: response))
end

sig { params(message: T::Hash[Symbol, T.untyped]).void }
def experimental_goto_relevant_file(message)
path = message.dig(:params, :textDocument, :uri).to_standardized_path
unless path.nil? || path.start_with?(@global_state.workspace_path)
send_empty_response(message[:id])
return
end

unless path
send_empty_response(message[:id])
return
end

response = {
locations: Requests::GotoRelevantFile.new(path).perform,
}
send_message(Result.new(id: message[:id], response: response))
end

sig { params(message: T::Hash[Symbol, T.untyped]).void }
def text_document_prepare_type_hierarchy(message)
params = message[:params]
Expand Down
Loading

0 comments on commit 30962b6

Please sign in to comment.