ruby-lsp 0.13.2 → 0.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_lsp/check_docs.rb +3 -3
  5. data/lib/ruby_lsp/document.rb +12 -0
  6. data/lib/ruby_lsp/executor.rb +77 -266
  7. data/lib/ruby_lsp/listener.rb +1 -50
  8. data/lib/ruby_lsp/listeners/code_lens.rb +233 -0
  9. data/lib/ruby_lsp/listeners/completion.rb +275 -0
  10. data/lib/ruby_lsp/listeners/definition.rb +158 -0
  11. data/lib/ruby_lsp/listeners/document_highlight.rb +556 -0
  12. data/lib/ruby_lsp/listeners/document_link.rb +162 -0
  13. data/lib/ruby_lsp/listeners/document_symbol.rb +223 -0
  14. data/lib/ruby_lsp/listeners/folding_ranges.rb +271 -0
  15. data/lib/ruby_lsp/listeners/hover.rb +152 -0
  16. data/lib/ruby_lsp/listeners/inlay_hints.rb +80 -0
  17. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +430 -0
  18. data/lib/ruby_lsp/listeners/signature_help.rb +74 -0
  19. data/lib/ruby_lsp/requests/code_action_resolve.rb +4 -4
  20. data/lib/ruby_lsp/requests/code_actions.rb +13 -4
  21. data/lib/ruby_lsp/requests/code_lens.rb +21 -221
  22. data/lib/ruby_lsp/requests/completion.rb +64 -244
  23. data/lib/ruby_lsp/requests/definition.rb +34 -147
  24. data/lib/ruby_lsp/requests/diagnostics.rb +17 -5
  25. data/lib/ruby_lsp/requests/document_highlight.rb +12 -536
  26. data/lib/ruby_lsp/requests/document_link.rb +11 -132
  27. data/lib/ruby_lsp/requests/document_symbol.rb +23 -210
  28. data/lib/ruby_lsp/requests/folding_ranges.rb +16 -252
  29. data/lib/ruby_lsp/requests/formatting.rb +4 -4
  30. data/lib/ruby_lsp/requests/hover.rb +48 -92
  31. data/lib/ruby_lsp/requests/inlay_hints.rb +23 -56
  32. data/lib/ruby_lsp/requests/on_type_formatting.rb +16 -4
  33. data/lib/ruby_lsp/requests/request.rb +17 -0
  34. data/lib/ruby_lsp/requests/selection_ranges.rb +4 -3
  35. data/lib/ruby_lsp/requests/semantic_highlighting.rb +21 -408
  36. data/lib/ruby_lsp/requests/show_syntax_tree.rb +4 -4
  37. data/lib/ruby_lsp/requests/signature_help.rb +43 -51
  38. data/lib/ruby_lsp/requests/support/common.rb +3 -2
  39. data/lib/ruby_lsp/requests/support/dependency_detector.rb +2 -0
  40. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
  41. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  42. data/lib/ruby_lsp/requests.rb +1 -1
  43. data/lib/ruby_lsp/utils.rb +8 -0
  44. metadata +17 -6
  45. data/lib/ruby_lsp/requests/base_request.rb +0 -24
@@ -0,0 +1,233 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "shellwords"
5
+ require_relative "../listener"
6
+
7
+ module RubyLsp
8
+ module Listeners
9
+ class CodeLens < Listener
10
+ extend T::Sig
11
+ extend T::Generic
12
+
13
+ BASE_COMMAND = T.let(
14
+ begin
15
+ Bundler.with_original_env { Bundler.default_lockfile }
16
+ "bundle exec ruby"
17
+ rescue Bundler::GemfileNotFound
18
+ "ruby"
19
+ end + " -Itest ",
20
+ String,
21
+ )
22
+ ACCESS_MODIFIERS = T.let([:public, :private, :protected], T::Array[Symbol])
23
+ SUPPORTED_TEST_LIBRARIES = T.let(["minitest", "test-unit"], T::Array[String])
24
+
25
+ ResponseType = type_member { { fixed: T::Array[Interface::CodeLens] } }
26
+
27
+ sig { override.returns(ResponseType) }
28
+ attr_reader :_response
29
+
30
+ sig do
31
+ params(
32
+ uri: URI::Generic,
33
+ lenses_configuration: RequestConfig,
34
+ dispatcher: Prism::Dispatcher,
35
+ ).void
36
+ end
37
+ def initialize(uri, lenses_configuration, dispatcher)
38
+ @uri = T.let(uri, URI::Generic)
39
+ @_response = T.let([], ResponseType)
40
+ @path = T.let(uri.to_standardized_path, T.nilable(String))
41
+ # visibility_stack is a stack of [current_visibility, previous_visibility]
42
+ @visibility_stack = T.let([[:public, :public]], T::Array[T::Array[T.nilable(Symbol)]])
43
+ @class_stack = T.let([], T::Array[String])
44
+ @group_id = T.let(1, Integer)
45
+ @group_id_stack = T.let([], T::Array[Integer])
46
+ @lenses_configuration = lenses_configuration
47
+
48
+ super(dispatcher)
49
+
50
+ dispatcher.register(
51
+ self,
52
+ :on_class_node_enter,
53
+ :on_class_node_leave,
54
+ :on_def_node_enter,
55
+ :on_call_node_enter,
56
+ :on_call_node_leave,
57
+ )
58
+ end
59
+
60
+ sig { params(node: Prism::ClassNode).void }
61
+ def on_class_node_enter(node)
62
+ @visibility_stack.push([:public, :public])
63
+ class_name = node.constant_path.slice
64
+ @class_stack.push(class_name)
65
+
66
+ if @path && class_name.end_with?("Test")
67
+ add_test_code_lens(
68
+ node,
69
+ name: class_name,
70
+ command: generate_test_command(class_name: class_name),
71
+ kind: :group,
72
+ )
73
+ end
74
+
75
+ @group_id_stack.push(@group_id)
76
+ @group_id += 1
77
+ end
78
+
79
+ sig { params(node: Prism::ClassNode).void }
80
+ def on_class_node_leave(node)
81
+ @visibility_stack.pop
82
+ @class_stack.pop
83
+ @group_id_stack.pop
84
+ end
85
+
86
+ sig { params(node: Prism::DefNode).void }
87
+ def on_def_node_enter(node)
88
+ class_name = @class_stack.last
89
+ return unless class_name&.end_with?("Test")
90
+
91
+ visibility, _ = @visibility_stack.last
92
+ if visibility == :public
93
+ method_name = node.name.to_s
94
+ if @path && method_name.start_with?("test_")
95
+ add_test_code_lens(
96
+ node,
97
+ name: method_name,
98
+ command: generate_test_command(method_name: method_name, class_name: class_name),
99
+ kind: :example,
100
+ )
101
+ end
102
+ end
103
+ end
104
+
105
+ sig { params(node: Prism::CallNode).void }
106
+ def on_call_node_enter(node)
107
+ name = node.name
108
+ arguments = node.arguments
109
+
110
+ # If we found `private` by itself or `private def foo`
111
+ if ACCESS_MODIFIERS.include?(name)
112
+ if arguments.nil?
113
+ @visibility_stack.pop
114
+ @visibility_stack.push([name, name])
115
+ elsif arguments.arguments.first.is_a?(Prism::DefNode)
116
+ visibility, _ = @visibility_stack.pop
117
+ @visibility_stack.push([name, visibility])
118
+ end
119
+
120
+ return
121
+ end
122
+
123
+ if @path&.include?(GEMFILE_NAME) && name == :gem && arguments
124
+ return unless @lenses_configuration.enabled?(:gemfileLinks)
125
+
126
+ first_argument = arguments.arguments.first
127
+ return unless first_argument.is_a?(Prism::StringNode)
128
+
129
+ remote = resolve_gem_remote(first_argument)
130
+ return unless remote
131
+
132
+ add_open_gem_remote_code_lens(node, remote)
133
+ end
134
+ end
135
+
136
+ sig { params(node: Prism::CallNode).void }
137
+ def on_call_node_leave(node)
138
+ _, prev_visibility = @visibility_stack.pop
139
+ @visibility_stack.push([prev_visibility, prev_visibility])
140
+ end
141
+
142
+ private
143
+
144
+ sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
145
+ def add_test_code_lens(node, name:, command:, kind:)
146
+ # don't add code lenses if the test library is not supported or unknown
147
+ return unless SUPPORTED_TEST_LIBRARIES.include?(DependencyDetector.instance.detected_test_library) && @path
148
+
149
+ arguments = [
150
+ @path,
151
+ name,
152
+ command,
153
+ {
154
+ start_line: node.location.start_line - 1,
155
+ start_column: node.location.start_column,
156
+ end_line: node.location.end_line - 1,
157
+ end_column: node.location.end_column,
158
+ },
159
+ ]
160
+
161
+ grouping_data = { group_id: @group_id_stack.last, kind: kind }
162
+ grouping_data[:id] = @group_id if kind == :group
163
+
164
+ @_response << create_code_lens(
165
+ node,
166
+ title: "Run",
167
+ command_name: "rubyLsp.runTest",
168
+ arguments: arguments,
169
+ data: { type: "test", **grouping_data },
170
+ )
171
+
172
+ @_response << create_code_lens(
173
+ node,
174
+ title: "Run In Terminal",
175
+ command_name: "rubyLsp.runTestInTerminal",
176
+ arguments: arguments,
177
+ data: { type: "test_in_terminal", **grouping_data },
178
+ )
179
+
180
+ @_response << create_code_lens(
181
+ node,
182
+ title: "Debug",
183
+ command_name: "rubyLsp.debugTest",
184
+ arguments: arguments,
185
+ data: { type: "debug", **grouping_data },
186
+ )
187
+ end
188
+
189
+ sig { params(gem_name: Prism::StringNode).returns(T.nilable(String)) }
190
+ def resolve_gem_remote(gem_name)
191
+ spec = Gem::Specification.stubs.find { |gem| gem.name == gem_name.content }&.to_spec
192
+ return if spec.nil?
193
+
194
+ [spec.homepage, spec.metadata["source_code_uri"]].compact.find do |page|
195
+ page.start_with?("https://github.com", "https://gitlab.com")
196
+ end
197
+ end
198
+
199
+ sig { params(class_name: String, method_name: T.nilable(String)).returns(String) }
200
+ def generate_test_command(class_name:, method_name: nil)
201
+ command = BASE_COMMAND + T.must(@path)
202
+
203
+ case DependencyDetector.instance.detected_test_library
204
+ when "minitest"
205
+ command += if method_name
206
+ " --name " + "/#{Shellwords.escape(class_name + "#" + method_name)}/"
207
+ else
208
+ " --name " + "/#{Shellwords.escape(class_name)}/"
209
+ end
210
+ when "test-unit"
211
+ command += " --testcase " + "/#{Shellwords.escape(class_name)}/"
212
+
213
+ if method_name
214
+ command += " --name " + Shellwords.escape(method_name)
215
+ end
216
+ end
217
+
218
+ command
219
+ end
220
+
221
+ sig { params(node: Prism::CallNode, remote: String).void }
222
+ def add_open_gem_remote_code_lens(node, remote)
223
+ @_response << create_code_lens(
224
+ node,
225
+ title: "Open remote",
226
+ command_name: "rubyLsp.openLink",
227
+ arguments: [remote],
228
+ data: { type: "link" },
229
+ )
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,275 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Listeners
6
+ class Completion < Listener
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ ResponseType = type_member { { fixed: T::Array[Interface::CompletionItem] } }
11
+
12
+ sig { override.returns(ResponseType) }
13
+ attr_reader :_response
14
+
15
+ sig do
16
+ params(
17
+ index: RubyIndexer::Index,
18
+ nesting: T::Array[String],
19
+ typechecker_enabled: T::Boolean,
20
+ dispatcher: Prism::Dispatcher,
21
+ ).void
22
+ end
23
+ def initialize(index, nesting, typechecker_enabled, dispatcher)
24
+ super(dispatcher)
25
+ @_response = T.let([], ResponseType)
26
+ @index = index
27
+ @nesting = nesting
28
+ @typechecker_enabled = typechecker_enabled
29
+
30
+ dispatcher.register(
31
+ self,
32
+ :on_string_node_enter,
33
+ :on_constant_path_node_enter,
34
+ :on_constant_read_node_enter,
35
+ :on_call_node_enter,
36
+ )
37
+ end
38
+
39
+ sig { params(node: Prism::StringNode).void }
40
+ def on_string_node_enter(node)
41
+ @index.search_require_paths(node.content).map!(&:require_path).sort!.each do |path|
42
+ @_response << build_completion(T.must(path), node)
43
+ end
44
+ end
45
+
46
+ # Handle completion on regular constant references (e.g. `Bar`)
47
+ sig { params(node: Prism::ConstantReadNode).void }
48
+ def on_constant_read_node_enter(node)
49
+ return if DependencyDetector.instance.typechecker
50
+
51
+ name = node.slice
52
+ candidates = @index.prefix_search(name, @nesting)
53
+ candidates.each do |entries|
54
+ complete_name = T.must(entries.first).name
55
+ @_response << build_entry_completion(
56
+ complete_name,
57
+ name,
58
+ node,
59
+ entries,
60
+ top_level?(complete_name),
61
+ )
62
+ end
63
+ end
64
+
65
+ # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
66
+ sig { params(node: Prism::ConstantPathNode).void }
67
+ def on_constant_path_node_enter(node)
68
+ return if DependencyDetector.instance.typechecker
69
+
70
+ name = node.slice
71
+
72
+ top_level_reference = if name.start_with?("::")
73
+ name = name.delete_prefix("::")
74
+ true
75
+ else
76
+ false
77
+ end
78
+
79
+ # If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
80
+ # order to find which possible constants match the desired search
81
+ *namespace, incomplete_name = name.split("::")
82
+ aliased_namespace = T.must(namespace).join("::")
83
+ namespace_entries = @index.resolve(aliased_namespace, @nesting)
84
+ return unless namespace_entries
85
+
86
+ real_namespace = @index.follow_aliased_namespace(T.must(namespace_entries.first).name)
87
+
88
+ candidates = @index.prefix_search("#{real_namespace}::#{incomplete_name}", top_level_reference ? [] : @nesting)
89
+ candidates.each do |entries|
90
+ # The only time we may have a private constant reference from outside of the namespace is if we're dealing
91
+ # with ConstantPath and the entry name doesn't start with the current nesting
92
+ first_entry = T.must(entries.first)
93
+ next if first_entry.visibility == :private && !first_entry.name.start_with?("#{@nesting}::")
94
+
95
+ constant_name = T.must(first_entry.name.split("::").last)
96
+
97
+ full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
98
+
99
+ @_response << build_entry_completion(
100
+ full_name,
101
+ name,
102
+ node,
103
+ entries,
104
+ top_level_reference || top_level?(T.must(entries.first).name),
105
+ )
106
+ end
107
+ end
108
+
109
+ sig { params(node: Prism::CallNode).void }
110
+ def on_call_node_enter(node)
111
+ return if @typechecker_enabled
112
+ return unless self_receiver?(node)
113
+
114
+ name = node.message
115
+ return unless name
116
+
117
+ receiver_entries = @index[@nesting.join("::")]
118
+ return unless receiver_entries
119
+
120
+ receiver = T.must(receiver_entries.first)
121
+
122
+ @index.prefix_search(name).each do |entries|
123
+ entry = entries.find { |e| e.is_a?(RubyIndexer::Entry::Member) && e.owner&.name == receiver.name }
124
+ next unless entry
125
+
126
+ @_response << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ sig do
133
+ params(
134
+ entry: RubyIndexer::Entry::Member,
135
+ node: Prism::CallNode,
136
+ ).returns(Interface::CompletionItem)
137
+ end
138
+ def build_method_completion(entry, node)
139
+ name = entry.name
140
+
141
+ Interface::CompletionItem.new(
142
+ label: name,
143
+ filter_text: name,
144
+ text_edit: Interface::TextEdit.new(range: range_from_node(node), new_text: name),
145
+ kind: Constant::CompletionItemKind::METHOD,
146
+ label_details: Interface::CompletionItemLabelDetails.new(
147
+ detail: "(#{entry.parameters.map(&:decorated_name).join(", ")})",
148
+ description: entry.file_name,
149
+ ),
150
+ documentation: markdown_from_index_entries(name, entry),
151
+ )
152
+ end
153
+
154
+ sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
155
+ def build_completion(label, node)
156
+ # We should use the content location as we only replace the content and not the delimiters of the string
157
+ loc = node.content_loc
158
+
159
+ Interface::CompletionItem.new(
160
+ label: label,
161
+ text_edit: Interface::TextEdit.new(
162
+ range: range_from_location(loc),
163
+ new_text: label,
164
+ ),
165
+ kind: Constant::CompletionItemKind::FILE,
166
+ )
167
+ end
168
+
169
+ sig do
170
+ params(
171
+ real_name: String,
172
+ incomplete_name: String,
173
+ node: Prism::Node,
174
+ entries: T::Array[RubyIndexer::Entry],
175
+ top_level: T::Boolean,
176
+ ).returns(Interface::CompletionItem)
177
+ end
178
+ def build_entry_completion(real_name, incomplete_name, node, entries, top_level)
179
+ first_entry = T.must(entries.first)
180
+ kind = case first_entry
181
+ when RubyIndexer::Entry::Class
182
+ Constant::CompletionItemKind::CLASS
183
+ when RubyIndexer::Entry::Module
184
+ Constant::CompletionItemKind::MODULE
185
+ when RubyIndexer::Entry::Constant
186
+ Constant::CompletionItemKind::CONSTANT
187
+ else
188
+ Constant::CompletionItemKind::REFERENCE
189
+ end
190
+
191
+ insertion_text = real_name.dup
192
+ filter_text = real_name.dup
193
+
194
+ # If we have two entries with the same name inside the current namespace and the user selects the top level
195
+ # option, we have to ensure it's prefixed with `::` or else we're completing the wrong constant. For example:
196
+ # If we have the index with ["Foo::Bar", "Bar"], and we're providing suggestions for `B` inside a `Foo` module,
197
+ # then selecting the `Foo::Bar` option needs to complete to `Bar` and selecting the top level `Bar` option needs
198
+ # to complete to `::Bar`.
199
+ if top_level
200
+ insertion_text.prepend("::")
201
+ filter_text.prepend("::")
202
+ end
203
+
204
+ # If the user is searching for a constant inside the current namespace, then we prefer completing the short name
205
+ # of that constant. E.g.:
206
+ #
207
+ # module Foo
208
+ # class Bar
209
+ # end
210
+ #
211
+ # Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
212
+ # end
213
+ @nesting.each do |namespace|
214
+ prefix = "#{namespace}::"
215
+ shortened_name = insertion_text.delete_prefix(prefix)
216
+
217
+ # If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
218
+ conflict_name = "#{@nesting.join("::")}::#{shortened_name}"
219
+ break if real_name != conflict_name && @index[conflict_name]
220
+
221
+ insertion_text = shortened_name
222
+
223
+ # If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
224
+ # `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in their
225
+ # typing
226
+ filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
227
+ end
228
+
229
+ # When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
230
+ # For these top level references, we need to include the `::` as part of the filter text or else it won't match
231
+ # the right entries in the index
232
+ Interface::CompletionItem.new(
233
+ label: real_name,
234
+ filter_text: filter_text,
235
+ text_edit: Interface::TextEdit.new(
236
+ range: range_from_node(node),
237
+ new_text: insertion_text,
238
+ ),
239
+ kind: kind,
240
+ label_details: Interface::CompletionItemLabelDetails.new(
241
+ description: entries.map(&:file_name).join(","),
242
+ ),
243
+ documentation: markdown_from_index_entries(real_name, entries),
244
+ )
245
+ end
246
+
247
+ # Check if there are any conflicting names for `entry_name`, which would require us to use a top level reference.
248
+ # For example:
249
+ #
250
+ # ```ruby
251
+ # class Bar; end
252
+ #
253
+ # module Foo
254
+ # class Bar; end
255
+ #
256
+ # # in this case, the completion for `Bar` conflicts with `Foo::Bar`, so we can't suggest `Bar` as the
257
+ # # completion, but instead need to suggest `::Bar`
258
+ # B
259
+ # end
260
+ # ```
261
+ sig { params(entry_name: String).returns(T::Boolean) }
262
+ def top_level?(entry_name)
263
+ @nesting.length.downto(0).each do |i|
264
+ prefix = T.must(@nesting[0...i]).join("::")
265
+ full_name = prefix.empty? ? entry_name : "#{prefix}::#{entry_name}"
266
+ next if full_name == entry_name
267
+
268
+ return true if @index[full_name]
269
+ end
270
+
271
+ false
272
+ end
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,158 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Listeners
6
+ class Definition < Listener
7
+ extend T::Sig
8
+ extend T::Generic
9
+
10
+ ResponseType = type_member { { fixed: T.nilable(T.any(T::Array[Interface::Location], Interface::Location)) } }
11
+
12
+ sig { override.returns(ResponseType) }
13
+ attr_reader :_response
14
+
15
+ sig do
16
+ params(
17
+ uri: URI::Generic,
18
+ nesting: T::Array[String],
19
+ index: RubyIndexer::Index,
20
+ dispatcher: Prism::Dispatcher,
21
+ typechecker_enabled: T::Boolean,
22
+ ).void
23
+ end
24
+ def initialize(uri, nesting, index, dispatcher, typechecker_enabled)
25
+ @uri = uri
26
+ @nesting = nesting
27
+ @index = index
28
+ @typechecker_enabled = typechecker_enabled
29
+ @_response = T.let(nil, ResponseType)
30
+
31
+ super(dispatcher)
32
+
33
+ dispatcher.register(
34
+ self,
35
+ :on_call_node_enter,
36
+ :on_constant_read_node_enter,
37
+ :on_constant_path_node_enter,
38
+ )
39
+ end
40
+
41
+ sig { params(node: Prism::CallNode).void }
42
+ def on_call_node_enter(node)
43
+ message = node.name
44
+
45
+ if message == :require || message == :require_relative
46
+ handle_require_definition(node)
47
+ else
48
+ handle_method_definition(node)
49
+ end
50
+ end
51
+
52
+ sig { params(node: Prism::ConstantPathNode).void }
53
+ def on_constant_path_node_enter(node)
54
+ find_in_index(node.slice)
55
+ end
56
+
57
+ sig { params(node: Prism::ConstantReadNode).void }
58
+ def on_constant_read_node_enter(node)
59
+ find_in_index(node.slice)
60
+ end
61
+
62
+ private
63
+
64
+ sig { params(node: Prism::CallNode).void }
65
+ def handle_method_definition(node)
66
+ return unless self_receiver?(node)
67
+
68
+ message = node.message
69
+ return unless message
70
+
71
+ target_method = @index.resolve_method(message, @nesting.join("::"))
72
+ return unless target_method
73
+
74
+ location = target_method.location
75
+ file_path = target_method.file_path
76
+ return if @typechecker_enabled && not_in_dependencies?(file_path)
77
+
78
+ @_response = Interface::Location.new(
79
+ uri: URI::Generic.from_path(path: file_path).to_s,
80
+ range: Interface::Range.new(
81
+ start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
82
+ end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
83
+ ),
84
+ )
85
+ end
86
+
87
+ sig { params(node: Prism::CallNode).void }
88
+ def handle_require_definition(node)
89
+ message = node.name
90
+ arguments = node.arguments
91
+ return unless arguments
92
+
93
+ argument = arguments.arguments.first
94
+ return unless argument.is_a?(Prism::StringNode)
95
+
96
+ case message
97
+ when :require
98
+ entry = @index.search_require_paths(argument.content).find do |indexable_path|
99
+ indexable_path.require_path == argument.content
100
+ end
101
+
102
+ if entry
103
+ candidate = entry.full_path
104
+
105
+ @_response = Interface::Location.new(
106
+ uri: URI::Generic.from_path(path: candidate).to_s,
107
+ range: Interface::Range.new(
108
+ start: Interface::Position.new(line: 0, character: 0),
109
+ end: Interface::Position.new(line: 0, character: 0),
110
+ ),
111
+ )
112
+ end
113
+ when :require_relative
114
+ required_file = "#{argument.content}.rb"
115
+ path = @uri.to_standardized_path
116
+ current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
117
+ candidate = File.expand_path(File.join(current_folder, required_file))
118
+
119
+ @_response = Interface::Location.new(
120
+ uri: URI::Generic.from_path(path: candidate).to_s,
121
+ range: Interface::Range.new(
122
+ start: Interface::Position.new(line: 0, character: 0),
123
+ end: Interface::Position.new(line: 0, character: 0),
124
+ ),
125
+ )
126
+ end
127
+ end
128
+
129
+ sig { params(value: String).void }
130
+ def find_in_index(value)
131
+ entries = @index.resolve(value, @nesting)
132
+ return unless entries
133
+
134
+ # We should only allow jumping to the definition of private constants if the constant is defined in the same
135
+ # namespace as the reference
136
+ first_entry = T.must(entries.first)
137
+ return if first_entry.visibility == :private && first_entry.name != "#{@nesting.join("::")}::#{value}"
138
+
139
+ @_response = entries.filter_map do |entry|
140
+ location = entry.location
141
+ # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
142
+ # additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
143
+ # in the project, even if the files are typed false
144
+ file_path = entry.file_path
145
+ next if @typechecker_enabled && not_in_dependencies?(file_path)
146
+
147
+ Interface::Location.new(
148
+ uri: URI::Generic.from_path(path: file_path).to_s,
149
+ range: Interface::Range.new(
150
+ start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
151
+ end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
152
+ ),
153
+ )
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end