Repository: Shopify/ruby-lsp (Ruby)

Issue:
Cannot find definition when starting with :: top-level namespace
### Operating System

macOS

### Ruby version

3.0.6

### Project has a bundle

- [X] Has bundle

### Ruby version manager being used

rbenv

### Description

I have a Rails app using Zeitwerk. We have files in a custom structure that both Zeitwerk and this extension are understanding just fine. This extension works wonderfully!

However, we have some places where we lookup a constant from the top-level namespace `::`. This LSP cannot find the definition in those cases. When I remove the top-level `::`, the LSP finds the exact definition.

Example, where Zeitwerk collapses the /whatever directory
`app/whatever/my_lovely_modules/pet_petter.rb`
```rb
module MyLovelyModules
  class PetPetter; end
end
```
`app/whatever/our_pets/good_pets.rb`
```rb
module OurPets
  class GoodPets
    def petter_top_level
      # right-clicking PetPetter and selecting "Go to definition" responds with "No definition found for "PetPetter"
      ::MyLovelyModules::PetPetter.new
    end

    def petter
      # right-clicking PetPetter and selecting "Go to definition" jumps to app/whatever/my_lovely_modules/pet_petter.rb
      MyLovelyModules::PetPetter.new
    end
  end
end
```

I looked in the recent changes in `master` and couldn't see anything related, nor any existing issue since "Go to definition" for classes and modules was released.

Thanks for this awesome extension =)
Henry

---

Extension version: v0.4.8 [latest]
RubyLSP gem version:
```Gemfile
# Gemfile.lock
ruby-lsp (0.10.1)
ruby-lsp-rails (0.2.5)
```
VS Code version: 1.82.2 [latest]
<details><summary>VS Code version info copy-paste</summary>

```
Version: 1.82.2
Commit: abd2f3db4bdb28f9e95536dfa84d8479f1eb312d
Date: 2023-09-14T06:00:27.244Z
Electron: 25.8.1
ElectronBuildId: 23779380
Chromium: 114.0.5735.289
Node.js: 18.15.0
V8: 11.4.183.29-electron.0
OS: Darwin arm64 21.6.0
```

</details>


Discussion:
Thank you for the bug report! We indeed had an issue in how we were resolving top level constant references. It should be fixed by the PR linked to this issue.
Amazing, thank you so much for the quick fix!



Patch:
diff --git a/lib/ruby_indexer/lib/ruby_indexer/index.rb b/lib/ruby_indexer/lib/ruby_indexer/index.rb
index 2e76ab0c67..d54fe782b6 100644
--- a/lib/ruby_indexer/lib/ruby_indexer/index.rb
+++ b/lib/ruby_indexer/lib/ruby_indexer/index.rb
@@ -127,6 +127,12 @@ def fuzzy_search(query)
     # 3. Baz
     sig { params(name: String, nesting: T::Array[String]).returns(T.nilable(T::Array[Entry])) }
     def resolve(name, nesting)
+      if name.start_with?("::")
+        name = name.delete_prefix("::")
+        results = @entries[name] || @entries[follow_aliased_namespace(name)]
+        return results.map { |e| e.is_a?(Entry::UnresolvedAlias) ? resolve_alias(e) : e } if results
+      end
+
       nesting.length.downto(0).each do |i|
         namespace = T.must(nesting[0...i]).join("::")
         full_name = namespace.empty? ? name : "#{namespace}::#{name}"


Test patch:
diff --git a/lib/ruby_indexer/test/index_test.rb b/lib/ruby_indexer/test/index_test.rb
index 97c47fbed9..8c75e9228d 100644
--- a/lib/ruby_indexer/test/index_test.rb
+++ b/lib/ruby_indexer/test/index_test.rb
@@ -158,5 +158,25 @@ class Foo::Baz
       results = @index.prefix_search("Ba", ["Foo"]).map { |entries| entries.map(&:name) }
       assert_equal([["Foo::Bar", "Foo::Bar"], ["Foo::Baz"]], results)
     end
+
+    def test_resolve_normalizes_top_level_names
+      @index.index_single(IndexablePath.new("/fake", "/fake/path/foo.rb"), <<~RUBY)
+        class Bar; end
+
+        module Foo
+          class Bar; end
+        end
+      RUBY
+
+      entries = @index.resolve("::Foo::Bar", [])
+      refute_nil(entries)
+
+      assert_equal("Foo::Bar", entries.first.name)
+
+      entries = @index.resolve("::Bar", ["Foo"])
+      refute_nil(entries)
+
+      assert_equal("Bar", entries.first.name)
+    end
   end
 end