Skip to content

Commit 8806c79

Browse files
committed
Merge branch 'release/092'
2 parents 9668794 + d71dd19 commit 8806c79

6 files changed

Lines changed: 80 additions & 4 deletions

File tree

CHANGELOG.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
#### 0.9.2 Sun Jan 11 10:50:34 PST 2026
4+
5+
* Fix namespace class definition lookup (Foo::Bar)
6+
37
#### 0.9.1 Tue Jan 6 19:32:44 PST 2026
48

59
* Method parameters

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ ENV RUBY_LANGUAGE_SERVER_PROJECT_ROOT=/project/
3131
COPY Gemfile* ruby_language_server.gemspec ./
3232
COPY lib/ruby_language_server/version.rb lib/ruby_language_server/version.rb
3333

34-
RUN bundle install -j 8
34+
RUN bundle install
3535

3636
COPY . ./
3737

Gemfile.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ DEPENDENCIES
210210
rubocop-rspec
211211
ruby_language_server!
212212
simplecov
213+
simplecov_json_formatter
213214

214215
CHECKSUMS
215216
activemodel (8.1.1) sha256=8b7e2496b9e333ced06248c16a43217b950192c98e0fe3aa117eee21501c6fbd

lib/ruby_language_server/project_manager.rb

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,15 @@ def possible_definitions(uri, position)
167167

168168
context = context_at_location(uri, position)
169169

170-
# If context has more than one element it's a method call on a receiver
170+
# If context has more than one element it could be a method call or namespace reference
171171
if context.length > 1
172+
# Check if this is a namespace reference (Foo::Bar) vs method call (Foo.bar)
173+
if namespace_reference?(uri, position, context)
174+
# Join the context with :: to form the full class/module name
175+
full_name = context.join('::')
176+
return project_definitions_for(full_name)
177+
end
178+
172179
receiver = context.first
173180
# Determine if it's a class method call (Foo.method) or instance method call (foo.method)
174181
class_method_filter = name != 'initialize'
@@ -206,7 +213,13 @@ def scope_definitions_for(name, scope, uri)
206213
end
207214

208215
def project_definitions_for(name, class_method_filter = nil)
209-
scopes = RubyLanguageServer::ScopeData::Scope.where(name:)
216+
# Check if name contains namespace separator (e.g., "Foo::Bar")
217+
# If so, search by path instead of name
218+
scopes = if name.include?('::')
219+
RubyLanguageServer::ScopeData::Scope.where(path: name)
220+
else
221+
RubyLanguageServer::ScopeData::Scope.where(name:)
222+
end
210223

211224
# Filter by class_method attribute if specified
212225
scopes = scopes.where(class_method: class_method_filter) unless class_method_filter.nil?
@@ -219,6 +232,16 @@ def project_definitions_for(name, class_method_filter = nil)
219232

220233
private
221234

235+
# Check if the context represents a namespace reference (Foo::Bar) rather than a method call (Foo.bar)
236+
# Class/module lookups always start with uppercase letters, method calls never do
237+
def namespace_reference?(_uri, _position, context)
238+
return false if context.length < 2
239+
240+
# If all parts start with uppercase, it's a namespace reference (Foo::Bar)
241+
# If first part is lowercase, it's a method call (foo.bar)
242+
context.all? { |part| /\A[A-Z]/.match?(part) }
243+
end
244+
222245
# Guess if a receiver name is likely a class name based on idiomatic Ruby conventions.
223246
# This is a heuristic and not 100% accurate (e.g., FOO could be a constant holding an instance).
224247
# Returns true for names like "Foo" or "FooBar" (starts with uppercase, has lowercase letters).
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module RubyLanguageServer
4-
VERSION = '0.9.1'
4+
VERSION = '0.9.2'
55
end

spec/lib/ruby_language_server/project_manager_spec.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,54 @@ def other_method
241241
assert_equal 0, other_class_result[:range][:start][:line]
242242
end
243243

244+
describe 'namespaced class lookup' do
245+
let(:file_with_namespaced_class) do
246+
<<~CODE_FILE
247+
module Foo
248+
class Bar
249+
def import
250+
puts "import method"
251+
end
252+
end
253+
end
254+
255+
# In a spec file:
256+
describe Foo::Bar, "#import" do
257+
# When clicking on Bar in Foo::Bar
258+
end
259+
CODE_FILE
260+
end
261+
262+
before(:each) do
263+
project_manager.update_document_content('namespace_uri', file_with_namespaced_class)
264+
project_manager.tags_for_uri('namespace_uri') # Force load of tags
265+
end
266+
267+
it 'finds namespaced class definition when clicking on the class name' do
268+
# Position on "Bar" in "Foo::Bar" (line 9, around character 16-18)
269+
# The line is: "describe Foo::Bar, \"#import\" do"
270+
# Character 16 is on 'B' of Bar
271+
position = OpenStruct.new(line: 9, character: 16)
272+
results = project_manager.possible_definitions('namespace_uri', position)
273+
274+
# Should find the Bar class definition on line 1 (0-indexed)
275+
assert_equal 1, results.length
276+
assert_equal 'namespace_uri', results.first[:uri]
277+
assert_equal 1, results.first[:range][:start][:line]
278+
end
279+
280+
it 'finds namespaced class definition when clicking on the module name' do
281+
# Position on "Foo" in "Foo::Bar" (line 9, around character 11)
282+
position = OpenStruct.new(line: 9, character: 11)
283+
results = project_manager.possible_definitions('namespace_uri', position)
284+
285+
# Should find the Foo module definition on line 0
286+
assert_equal 1, results.length
287+
assert_equal 'namespace_uri', results.first[:uri]
288+
assert_equal 0, results.first[:range][:start][:line]
289+
end
290+
end
291+
244292
describe 'parameter vs method name resolution' do
245293
let(:file_with_param_shadowing) do
246294
<<~CODE_FILE

0 commit comments

Comments
 (0)