-
Notifications
You must be signed in to change notification settings - Fork 183
/
Copy pathdefinition.rb
124 lines (113 loc) · 4.05 KB
/
definition.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# typed: strict
# frozen_string_literal: true
require "ruby_lsp/listeners/definition"
module RubyLsp
module Requests
# 
#
# The [definition
# request](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) jumps to the
# definition of the symbol under the cursor.
#
# Currently supported targets:
#
# - Classes
# - Modules
# - Constants
# - Require paths
# - Methods invoked on self only and on receivers where the type is unknown
# - Instance variables
#
# # Example
#
# ```ruby
# require "some_gem/file" # <- Request go to definition on this string will take you to the file
# Product.new # <- Request go to definition on this class name will take you to its declaration.
# ```
class Definition < Request
extend T::Sig
extend T::Generic
SPECIAL_METHOD_CALLS = [
:require,
:require_relative,
:autoload,
].freeze
sig do
params(
document: T.any(RubyDocument, ERBDocument),
global_state: GlobalState,
position: T::Hash[Symbol, T.untyped],
dispatcher: Prism::Dispatcher,
sorbet_level: RubyDocument::SorbetLevel,
).void
end
def initialize(document, global_state, position, dispatcher, sorbet_level)
super()
@response_builder = T.let(
ResponseBuilders::CollectionResponseBuilder[T.any(Interface::Location, Interface::LocationLink)].new,
ResponseBuilders::CollectionResponseBuilder[T.any(Interface::Location, Interface::LocationLink)],
)
@dispatcher = dispatcher
node_context = document.locate_node(
position,
node_types: [
Prism::CallNode,
Prism::ConstantReadNode,
Prism::ConstantPathNode,
Prism::BlockArgumentNode,
Prism::InstanceVariableReadNode,
Prism::InstanceVariableAndWriteNode,
Prism::InstanceVariableOperatorWriteNode,
Prism::InstanceVariableOrWriteNode,
Prism::InstanceVariableTargetNode,
Prism::InstanceVariableWriteNode,
Prism::SymbolNode,
Prism::StringNode,
Prism::SuperNode,
Prism::ForwardingSuperNode,
],
)
target = node_context.node
parent = node_context.parent
if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
# If the target is part of a constant path node, we need to find the exact portion of the constant that the
# user is requesting to go to definition for
target = determine_target(
target,
parent,
position,
)
elsif target.is_a?(Prism::CallNode) && !SPECIAL_METHOD_CALLS.include?(target.message) && !covers_position?(
target.message_loc, position
)
# If the target is a method call, we need to ensure that the requested position is exactly on top of the
# method identifier. Otherwise, we risk showing definitions for unrelated things
target = nil
# For methods with block arguments using symbol-to-proc
elsif target.is_a?(Prism::SymbolNode) && parent.is_a?(Prism::BlockArgumentNode)
target = parent
end
if target
Listeners::Definition.new(
@response_builder,
global_state,
document.language_id,
document.uri,
node_context,
dispatcher,
sorbet_level,
)
Addon.addons.each do |addon|
addon.create_definition_listener(@response_builder, document.uri, node_context, dispatcher)
end
end
@target = T.let(target, T.nilable(Prism::Node))
end
sig { override.returns(T::Array[T.any(Interface::Location, Interface::LocationLink)]) }
def perform
@dispatcher.dispatch_once(@target) if @target
@response_builder.response
end
end
end
end