Skip to content

Commit

Permalink
Implement Go To Type Definition
Browse files Browse the repository at this point in the history
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_typeDefinition

All the necessary pieces were in place, nothing fancy changed,
mostly just exposing the functionality for LanguageServer.

The only "functionality" added is `ComplexType#namespaces` which also
utilizes existing implementation.
  • Loading branch information
lekemula committed May 27, 2024
1 parent 524c94e commit cfa126d
Show file tree
Hide file tree
Showing 15 changed files with 147 additions and 2 deletions.
7 changes: 7 additions & 0 deletions lib/solargraph/complex_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,18 @@ def [](index)
def select &block
@items.select &block
end

# @return [String]
def namespace
# cache this attr for high frequency call
@namespace ||= method_missing(:namespace).to_s
end

# @return [Array<String>]
def namespaces
@items.map(&:namespace)
end

def method_missing name, *args, &block
return if @items.first.nil?
return @items.first.send(name, *args, &block) if respond_to_missing?(name)
Expand Down
10 changes: 10 additions & 0 deletions lib/solargraph/language_server/host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,15 @@ def definitions_at uri, line, column
library.definitions_at(uri_to_file(uri), line, column)
end

# @param uri [String]
# @param line [Integer]
# @param column [Integer]
# @return [Array<Solargraph::Pin::Base>]
def type_definitions_at uri, line, column
library = library_for(uri)
library.type_definitions_at(uri_to_file(uri), line, column)
end

# @param uri [String]
# @param line [Integer]
# @param column [Integer]
Expand Down Expand Up @@ -630,6 +639,7 @@ def default_configuration
'hover' => true,
'symbols' => true,
'definitions' => true,
'typeDefinitions' => true,
'rename' => true,
'references' => true,
'autoformat' => false,
Expand Down
1 change: 1 addition & 0 deletions lib/solargraph/language_server/message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def method_map
register 'textDocument/didClose', TextDocument::DidClose
register 'textDocument/hover', TextDocument::Hover
register 'textDocument/definition', TextDocument::Definition
register 'textDocument/typeDefinition', TextDocument::TypeDefinition
register 'textDocument/formatting', TextDocument::Formatting
register 'textDocument/onTypeFormatting', TextDocument::OnTypeFormatting
register 'textDocument/documentSymbol', TextDocument::DocumentSymbol
Expand Down
8 changes: 8 additions & 0 deletions lib/solargraph/language_server/message/initialize.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def process
result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting')
result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol')
result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition')
result[:capabilities].merge! static_type_definitions unless dynamic_registration_for?('textDocument', 'typeDefinition')
result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename')
result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references')
result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol')
Expand Down Expand Up @@ -121,6 +122,13 @@ def static_definitions
}
end

def static_type_definitions
return {} unless host.options['type_definitions']
{
definitionProvider: true
}
end

def static_rename
{
renameProvider: {prepareProvider: true}
Expand Down
1 change: 1 addition & 0 deletions lib/solargraph/language_server/message/initialized.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def process
textDocument/formatting
textDocument/documentSymbol
textDocument/definition
textDocument/typeDefinition
textDocument/references
textDocument/rename
textDocument/prepareRename
Expand Down
1 change: 1 addition & 0 deletions lib/solargraph/language_server/message/text_document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module TextDocument
autoload :DiagnosticsQueue, 'solargraph/language_server/message/text_document/diagnostics_queue'
autoload :OnTypeFormatting, 'solargraph/language_server/message/text_document/on_type_formatting'
autoload :Definition, 'solargraph/language_server/message/text_document/definition'
autoload :TypeDefinition, 'solargraph/language_server/message/text_document/type_definition'
autoload :DocumentSymbol, 'solargraph/language_server/message/text_document/document_symbol'
autoload :Formatting, 'solargraph/language_server/message/text_document/formatting'
autoload :References, 'solargraph/language_server/message/text_document/references'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module Solargraph::LanguageServer::Message::TextDocument
class TypeDefinition < Base
def process
@line = params['position']['line']
@column = params['position']['character']
set_result(code_location || [])
end

private

def code_location
suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column)
return nil if suggestions.empty?
suggestions.reject { |pin| pin.location.nil? || pin.location.filename.nil? }.map do |pin|
{
uri: file_to_uri(pin.location.filename),
range: pin.location.range.to_hash
}
end
end
end
end
16 changes: 16 additions & 0 deletions lib/solargraph/library.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,22 @@ def definitions_at filename, line, column
handle_file_not_found(filename, e)
end

# Get type definition suggestions for the expression at the specified file and
# location.
#
# @param filename [String] The file to analyze
# @param line [Integer] The zero-based line number
# @param column [Integer] The zero-based column number
# @return [Array<Solargraph::Pin::Base>]
# @todo Take filename/position instead of filename/line/column
def type_definitions_at filename, line, column
position = Position.new(line, column)
cursor = Source::Cursor.new(read(filename), position)
api_map.clip(cursor).types
rescue FileNotFoundError => e
handle_file_not_found filename, e
end

# Get signature suggestions for the method at the specified file and
# location.
#
Expand Down
5 changes: 5 additions & 0 deletions lib/solargraph/source_map/clip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ def define
result
end

# @return [Array<Pin::Base>]
def types
infer.namespaces.map { |namespace| api_map.get_path_pins(namespace) }.flatten
end

# @return [Completion]
def complete
return package_completions([]) if !source_map.source.parsed? || cursor.string?
Expand Down
3 changes: 3 additions & 0 deletions spec/complex_type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
expect(types.length).to eq(1)
expect(types.first.namespace).to eq('Foo')
expect(types.first.scope).to eq(:class)
multiple_types = Solargraph::ComplexType.parse 'Module<Foo>, Class<Bar>, String, nil'
expect(multiple_types.length).to eq(4)
expect(multiple_types.namespaces).to eq(['Foo', 'Bar', 'String', 'NilClass'])
end

it "identifies duck types" do
Expand Down
2 changes: 2 additions & 0 deletions spec/fixtures/workspace/lib/something.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Something
end
4 changes: 2 additions & 2 deletions spec/fixtures/workspace/lib/thing.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Thing
def do_thing
end
# @return [Something]
def do_thing; end
end
23 changes: 23 additions & 0 deletions spec/language_server/message/text_document/type_definition_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
describe Solargraph::LanguageServer::Message::TextDocument::TypeDefinition do
it 'finds definitions of methods' do
host = Solargraph::LanguageServer::Host.new
host.prepare('spec/fixtures/workspace')
sleep 0.1 until host.libraries.all?(&:mapped?)
host.catalog
file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/other.rb'))
something_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/something.rb'))
message = Solargraph::LanguageServer::Message::TextDocument::TypeDefinition.new(host, {
'params' => {
'textDocument' => {
'uri' => file_uri
},
'position' => {
'line' => 4,
'character' => 10
}
}
})
message.process
expect(message.result.first[:uri]).to eq(something_uri)
end
end
16 changes: 16 additions & 0 deletions spec/library_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ def bar
expect(paths).to include('Foo#bar')
end

it "gets type definitions from a file" do
library = Solargraph::Library.new
src = Solargraph::Source.load_string %(
class Bar; end
class Foo
# @return [Bar]
def self.bar
end
end
Foo.bar
), 'file.rb', 0
library.attach src
paths = library.type_definitions_at('file.rb', 7, 13).map(&:path)
expect(paths).to include('Bar')
end

it "signifies method arguments" do
library = Solargraph::Library.new
src = Solargraph::Source.load_string %(
Expand Down
28 changes: 28 additions & 0 deletions spec/source_map/clip_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,34 @@ def bar_method; end
expect(clip.complete.pins.map(&:path)).to include('Bar#bar_method')
end

it 'finds inferred type definitions' do
source = Solargraph::Source.load_string(%(
class OtherNamespace::MyClass; end
module SomeNamespace
class Foo
# @return [self]
def self.make; end
end
class Bar < Foo
# @return [Class<Foo>, Bar, OtherNamespace::MyClass]
def foo_method;end
def bar_method
local_variable = Foo.new
other_variable = local_variable
end
end
end
SomeNamespace::Bar.make.foo_method
), 'test.rb')
api_map = Solargraph::ApiMap.new
api_map.map source
clip = api_map.clip_at('test.rb', [13, 33])
expect(clip.types.map(&:path)).to eq(['SomeNamespace::Foo']) # other_variable
clip = api_map.clip_at('test.rb', [17, 33])
expect(clip.types.map(&:path)).to eq(['SomeNamespace::Foo', 'SomeNamespace::Bar', 'OtherNamespace::MyClass'])
end

it 'infers Hash value types' do
source = Solargraph::Source.load_string(%(
# @type [Hash{String => File}]
Expand Down

0 comments on commit cfa126d

Please sign in to comment.