From 2a0d13da88a4d929e4c7331c173bdbb91e67394c Mon Sep 17 00:00:00 2001
From: Alex Rocha <alex.rocha@shopify.com>
Date: Mon, 20 Oct 2025 19:27:30 +0100
Subject: [PATCH] Support multiple test file matches in go to relevant file

---
 lib/ruby_lsp/requests/go_to_relevant_file.rb | 42 ++++++++++++-----
 test/requests/go_to_relevant_file_test.rb    | 47 ++++++++++++++++++++
 2 files changed, 77 insertions(+), 12 deletions(-)

diff --git a/lib/ruby_lsp/requests/go_to_relevant_file.rb b/lib/ruby_lsp/requests/go_to_relevant_file.rb
index d28ab99e28..954d613749 100644
--- a/lib/ruby_lsp/requests/go_to_relevant_file.rb
+++ b/lib/ruby_lsp/requests/go_to_relevant_file.rb
@@ -35,8 +35,11 @@ def perform
 
       #: -> Array[String]
       def find_relevant_paths
-        pattern = File.join(search_root, "**", relevant_filename_pattern)
-        candidate_paths = Dir.glob(pattern)
+        patterns = relevant_filename_patterns
+
+        candidate_paths = patterns.flat_map do |pattern|
+          Dir.glob(File.join(search_root, "**", pattern))
+        end
 
         return [] if candidate_paths.empty?
 
@@ -77,18 +80,33 @@ def search_root
         "."
       end
 
-      #: -> String
-      def relevant_filename_pattern
-        input_basename = File.basename(@path, File.extname(@path))
-
-        relevant_basename_pattern =
-          if input_basename.match?(TEST_PATTERN)
-            input_basename.gsub(TEST_PATTERN, "")
+      #: -> Array[String]
+      def relevant_filename_patterns
+        extension = File.extname(@path)
+        input_basename = File.basename(@path, extension)
+
+        if input_basename.match?(TEST_PATTERN)
+          # Test file -> find implementation
+          base = input_basename.gsub(TEST_PATTERN, "")
+          parent_dir = File.basename(File.dirname(@path))
+
+          # If test file is in a directory matching the implementation name
+          # (e.g., go_to_relevant_file/test_go_to_relevant_file_a.rb)
+          # return patterns for both the base file name and the parent directory name
+          if base.include?(parent_dir) && base != parent_dir
+            ["#{base}#{extension}", "#{parent_dir}#{extension}"]
           else
-            "{{#{TEST_PREFIX_GLOB}}#{input_basename},#{input_basename}{#{TEST_SUFFIX_GLOB}}}"
+            ["#{base}#{extension}"]
           end
-
-        "#{relevant_basename_pattern}#{File.extname(@path)}"
+        else
+          # Implementation file -> find tests (including in matching directory)
+          [
+            "{#{TEST_PREFIX_GLOB}}#{input_basename}#{extension}",
+            "#{input_basename}{#{TEST_SUFFIX_GLOB}}#{extension}",
+            "#{input_basename}/{#{TEST_PREFIX_GLOB}}*#{extension}",
+            "#{input_basename}/*{#{TEST_SUFFIX_GLOB}}#{extension}",
+          ]
+        end
       end
 
       # Using the Jaccard algorithm to determine the similarity between the
diff --git a/test/requests/go_to_relevant_file_test.rb b/test/requests/go_to_relevant_file_test.rb
index 8006fa9903..38df60d3f7 100644
--- a/test/requests/go_to_relevant_file_test.rb
+++ b/test/requests/go_to_relevant_file_test.rb
@@ -117,4 +117,51 @@ def test_search_within_implementation_test_root
       )
     end
   end
+
+  def test_finds_tests_in_matching_subdirectory
+    Dir.chdir(@workspace) do
+      lib_dir = File.join(@workspace, "lib")
+      test_root = File.join(@workspace, "test")
+      test_subdir = File.join(test_root, "user")
+
+      FileUtils.mkdir_p(lib_dir)
+      FileUtils.mkdir_p(test_subdir)
+
+      impl_file = File.join(lib_dir, "user.rb")
+      test_file1 = File.join(test_subdir, "create_user_test.rb")
+      test_file2 = File.join(test_subdir, "test_update_user.rb")
+
+      FileUtils.touch(impl_file)
+      FileUtils.touch(test_file1)
+      FileUtils.touch(test_file2)
+
+      result = RubyLsp::Requests::GoToRelevantFile.new(impl_file, @workspace).perform
+
+      assert_equal(
+        [test_file1, test_file2].sort,
+        result.sort,
+      )
+    end
+  end
+
+  def test_finds_implementation_from_nested_test_file
+    Dir.chdir(@workspace) do
+      lib_dir = File.join(@workspace, "lib")
+      test_root = File.join(@workspace, "test")
+      test_subdir = File.join(test_root, "go_to_relevant_file")
+
+      FileUtils.mkdir_p(lib_dir)
+      FileUtils.mkdir_p(test_subdir)
+
+      impl_file = File.join(lib_dir, "go_to_relevant_file.rb")
+      test_file = File.join(test_subdir, "go_to_relevant_file_a_test.rb")
+
+      FileUtils.touch(impl_file)
+      FileUtils.touch(test_file)
+
+      result = RubyLsp::Requests::GoToRelevantFile.new(test_file, @workspace).perform
+
+      assert_equal([impl_file], result)
+    end
+  end
 end
