ruby-lsp 0.18.3 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/core_ext/uri.rb +9 -4
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +6 -0
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +66 -8
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +56 -32
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +3 -2
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +32 -8
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +262 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +11 -0
- data/lib/ruby_indexer/test/constant_test.rb +8 -0
- data/lib/ruby_indexer/test/enhancements_test.rb +2 -0
- data/lib/ruby_indexer/test/instance_variables_test.rb +12 -0
- data/lib/ruby_indexer/test/method_test.rb +10 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +8 -0
- data/lib/ruby_indexer/test/reference_finder_test.rb +86 -0
- data/lib/ruby_lsp/addon.rb +79 -17
- data/lib/ruby_lsp/base_server.rb +6 -0
- data/lib/ruby_lsp/erb_document.rb +3 -2
- data/lib/ruby_lsp/global_state.rb +8 -0
- data/lib/ruby_lsp/internal.rb +3 -1
- data/lib/ruby_lsp/listeners/completion.rb +1 -1
- data/lib/ruby_lsp/listeners/hover.rb +57 -0
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +24 -21
- data/lib/ruby_lsp/requests/completion_resolve.rb +29 -0
- data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
- data/lib/ruby_lsp/requests/rename.rb +189 -0
- data/lib/ruby_lsp/requests/support/common.rb +2 -2
- data/lib/ruby_lsp/requests/support/source_uri.rb +8 -1
- data/lib/ruby_lsp/scope.rb +47 -0
- data/lib/ruby_lsp/server.rb +64 -32
- data/lib/ruby_lsp/static_docs.rb +15 -0
- data/lib/ruby_lsp/store.rb +12 -0
- data/lib/ruby_lsp/test_helper.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +6 -1
- data/lib/ruby_lsp/utils.rb +3 -6
- data/static_docs/yield.md +81 -0
- metadata +19 -8
- data/lib/ruby_lsp/parameter_scope.rb +0 -33
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -26,7 +26,8 @@ require "ruby_lsp/base_server"
|
|
26
26
|
require "ruby_indexer/ruby_indexer"
|
27
27
|
require "core_ext/uri"
|
28
28
|
require "ruby_lsp/utils"
|
29
|
-
require "ruby_lsp/
|
29
|
+
require "ruby_lsp/static_docs"
|
30
|
+
require "ruby_lsp/scope"
|
30
31
|
require "ruby_lsp/global_state"
|
31
32
|
require "ruby_lsp/server"
|
32
33
|
require "ruby_lsp/type_inferrer"
|
@@ -80,3 +81,4 @@ require "ruby_lsp/requests/show_syntax_tree"
|
|
80
81
|
require "ruby_lsp/requests/signature_help"
|
81
82
|
require "ruby_lsp/requests/type_hierarchy_supertypes"
|
82
83
|
require "ruby_lsp/requests/workspace_symbol"
|
84
|
+
require "ruby_lsp/requests/rename"
|
@@ -21,8 +21,10 @@ module RubyLsp
|
|
21
21
|
Prism::InstanceVariableWriteNode,
|
22
22
|
Prism::SymbolNode,
|
23
23
|
Prism::StringNode,
|
24
|
+
Prism::InterpolatedStringNode,
|
24
25
|
Prism::SuperNode,
|
25
26
|
Prism::ForwardingSuperNode,
|
27
|
+
Prism::YieldNode,
|
26
28
|
],
|
27
29
|
T::Array[T.class_of(Prism::Node)],
|
28
30
|
)
|
@@ -68,9 +70,22 @@ module RubyLsp
|
|
68
70
|
:on_instance_variable_target_node_enter,
|
69
71
|
:on_super_node_enter,
|
70
72
|
:on_forwarding_super_node_enter,
|
73
|
+
:on_string_node_enter,
|
74
|
+
:on_interpolated_string_node_enter,
|
75
|
+
:on_yield_node_enter,
|
71
76
|
)
|
72
77
|
end
|
73
78
|
|
79
|
+
sig { params(node: Prism::StringNode).void }
|
80
|
+
def on_string_node_enter(node)
|
81
|
+
generate_heredoc_hover(node)
|
82
|
+
end
|
83
|
+
|
84
|
+
sig { params(node: Prism::InterpolatedStringNode).void }
|
85
|
+
def on_interpolated_string_node_enter(node)
|
86
|
+
generate_heredoc_hover(node)
|
87
|
+
end
|
88
|
+
|
74
89
|
sig { params(node: Prism::ConstantReadNode).void }
|
75
90
|
def on_constant_read_node_enter(node)
|
76
91
|
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
@@ -153,8 +168,50 @@ module RubyLsp
|
|
153
168
|
handle_super_node_hover
|
154
169
|
end
|
155
170
|
|
171
|
+
sig { params(node: Prism::YieldNode).void }
|
172
|
+
def on_yield_node_enter(node)
|
173
|
+
handle_keyword_documentation(node.keyword)
|
174
|
+
end
|
175
|
+
|
156
176
|
private
|
157
177
|
|
178
|
+
sig { params(node: T.any(Prism::InterpolatedStringNode, Prism::StringNode)).void }
|
179
|
+
def generate_heredoc_hover(node)
|
180
|
+
return unless node.heredoc?
|
181
|
+
|
182
|
+
opening_content = node.opening_loc&.slice
|
183
|
+
return unless opening_content
|
184
|
+
|
185
|
+
match = /(<<(?<type>(-|~)?))(?<quote>['"`]?)(?<delimiter>\w+)\k<quote>/.match(opening_content)
|
186
|
+
return unless match
|
187
|
+
|
188
|
+
heredoc_delimiter = match.named_captures["delimiter"]
|
189
|
+
|
190
|
+
if heredoc_delimiter
|
191
|
+
message = if match["type"] == "~"
|
192
|
+
"This is a squiggly heredoc definition using the `#{heredoc_delimiter}` delimiter. " \
|
193
|
+
"Indentation will be ignored in the resulting string."
|
194
|
+
else
|
195
|
+
"This is a heredoc definition using the `#{heredoc_delimiter}` delimiter. " \
|
196
|
+
"Indentation will be considered part of the string."
|
197
|
+
end
|
198
|
+
|
199
|
+
@response_builder.push(message, category: :documentation)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
sig { params(keyword: String).void }
|
204
|
+
def handle_keyword_documentation(keyword)
|
205
|
+
content = KEYWORD_DOCS[keyword]
|
206
|
+
return unless content
|
207
|
+
|
208
|
+
doc_path = File.join(STATIC_DOCS_PATH, "#{keyword}.md")
|
209
|
+
|
210
|
+
@response_builder.push("```ruby\n#{keyword}\n```", category: :title)
|
211
|
+
@response_builder.push("[Read more](#{doc_path})", category: :links)
|
212
|
+
@response_builder.push(content, category: :documentation)
|
213
|
+
end
|
214
|
+
|
158
215
|
sig { void }
|
159
216
|
def handle_super_node_hover
|
160
217
|
# Sorbet can handle super hover on typed true or higher
|
@@ -27,7 +27,7 @@ module RubyLsp
|
|
27
27
|
def initialize(dispatcher, response_builder)
|
28
28
|
@response_builder = response_builder
|
29
29
|
@special_methods = T.let(nil, T.nilable(T::Array[String]))
|
30
|
-
@current_scope = T.let(
|
30
|
+
@current_scope = T.let(Scope.new, Scope)
|
31
31
|
@inside_regex_capture = T.let(false, T::Boolean)
|
32
32
|
@inside_implicit_node = T.let(false, T::Boolean)
|
33
33
|
|
@@ -102,7 +102,7 @@ module RubyLsp
|
|
102
102
|
|
103
103
|
sig { params(node: Prism::DefNode).void }
|
104
104
|
def on_def_node_enter(node)
|
105
|
-
@current_scope =
|
105
|
+
@current_scope = Scope.new(@current_scope)
|
106
106
|
end
|
107
107
|
|
108
108
|
sig { params(node: Prism::DefNode).void }
|
@@ -112,7 +112,7 @@ module RubyLsp
|
|
112
112
|
|
113
113
|
sig { params(node: Prism::BlockNode).void }
|
114
114
|
def on_block_node_enter(node)
|
115
|
-
@current_scope =
|
115
|
+
@current_scope = Scope.new(@current_scope)
|
116
116
|
end
|
117
117
|
|
118
118
|
sig { params(node: Prism::BlockNode).void }
|
@@ -128,39 +128,39 @@ module RubyLsp
|
|
128
128
|
sig { params(node: Prism::BlockParameterNode).void }
|
129
129
|
def on_block_parameter_node_enter(node)
|
130
130
|
name = node.name
|
131
|
-
@current_scope
|
131
|
+
@current_scope.add(name.to_sym, :parameter) if name
|
132
132
|
end
|
133
133
|
|
134
134
|
sig { params(node: Prism::RequiredKeywordParameterNode).void }
|
135
135
|
def on_required_keyword_parameter_node_enter(node)
|
136
|
-
@current_scope
|
136
|
+
@current_scope.add(node.name, :parameter)
|
137
137
|
end
|
138
138
|
|
139
139
|
sig { params(node: Prism::OptionalKeywordParameterNode).void }
|
140
140
|
def on_optional_keyword_parameter_node_enter(node)
|
141
|
-
@current_scope
|
141
|
+
@current_scope.add(node.name, :parameter)
|
142
142
|
end
|
143
143
|
|
144
144
|
sig { params(node: Prism::KeywordRestParameterNode).void }
|
145
145
|
def on_keyword_rest_parameter_node_enter(node)
|
146
146
|
name = node.name
|
147
|
-
@current_scope
|
147
|
+
@current_scope.add(name.to_sym, :parameter) if name
|
148
148
|
end
|
149
149
|
|
150
150
|
sig { params(node: Prism::OptionalParameterNode).void }
|
151
151
|
def on_optional_parameter_node_enter(node)
|
152
|
-
@current_scope
|
152
|
+
@current_scope.add(node.name, :parameter)
|
153
153
|
end
|
154
154
|
|
155
155
|
sig { params(node: Prism::RequiredParameterNode).void }
|
156
156
|
def on_required_parameter_node_enter(node)
|
157
|
-
@current_scope
|
157
|
+
@current_scope.add(node.name, :parameter)
|
158
158
|
end
|
159
159
|
|
160
160
|
sig { params(node: Prism::RestParameterNode).void }
|
161
161
|
def on_rest_parameter_node_enter(node)
|
162
162
|
name = node.name
|
163
|
-
@current_scope
|
163
|
+
@current_scope.add(name.to_sym, :parameter) if name
|
164
164
|
end
|
165
165
|
|
166
166
|
sig { params(node: Prism::SelfNode).void }
|
@@ -170,8 +170,8 @@ module RubyLsp
|
|
170
170
|
|
171
171
|
sig { params(node: Prism::LocalVariableWriteNode).void }
|
172
172
|
def on_local_variable_write_node_enter(node)
|
173
|
-
|
174
|
-
@response_builder.add_token(node.name_loc,
|
173
|
+
local = @current_scope.lookup(node.name)
|
174
|
+
@response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
|
175
175
|
end
|
176
176
|
|
177
177
|
sig { params(node: Prism::LocalVariableReadNode).void }
|
@@ -184,25 +184,26 @@ module RubyLsp
|
|
184
184
|
return
|
185
185
|
end
|
186
186
|
|
187
|
-
|
187
|
+
local = @current_scope.lookup(node.name)
|
188
|
+
@response_builder.add_token(node.location, local&.type || :variable)
|
188
189
|
end
|
189
190
|
|
190
191
|
sig { params(node: Prism::LocalVariableAndWriteNode).void }
|
191
192
|
def on_local_variable_and_write_node_enter(node)
|
192
|
-
|
193
|
-
@response_builder.add_token(node.name_loc,
|
193
|
+
local = @current_scope.lookup(node.name)
|
194
|
+
@response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
|
194
195
|
end
|
195
196
|
|
196
197
|
sig { params(node: Prism::LocalVariableOperatorWriteNode).void }
|
197
198
|
def on_local_variable_operator_write_node_enter(node)
|
198
|
-
|
199
|
-
@response_builder.add_token(node.name_loc,
|
199
|
+
local = @current_scope.lookup(node.name)
|
200
|
+
@response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
|
200
201
|
end
|
201
202
|
|
202
203
|
sig { params(node: Prism::LocalVariableOrWriteNode).void }
|
203
204
|
def on_local_variable_or_write_node_enter(node)
|
204
|
-
|
205
|
-
@response_builder.add_token(node.name_loc,
|
205
|
+
local = @current_scope.lookup(node.name)
|
206
|
+
@response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
|
206
207
|
end
|
207
208
|
|
208
209
|
sig { params(node: Prism::LocalVariableTargetNode).void }
|
@@ -213,7 +214,8 @@ module RubyLsp
|
|
213
214
|
# prevent pushing local variable target tokens. See https://github.com/ruby/prism/issues/1912
|
214
215
|
return if @inside_regex_capture
|
215
216
|
|
216
|
-
|
217
|
+
local = @current_scope.lookup(node.name)
|
218
|
+
@response_builder.add_token(node.location, local&.type || :variable)
|
217
219
|
end
|
218
220
|
|
219
221
|
sig { params(node: Prism::ClassNode).void }
|
@@ -311,7 +313,8 @@ module RubyLsp
|
|
311
313
|
capture_name_offset = T.must(content.index("(?<#{name}>")) + 3
|
312
314
|
local_var_loc = loc.copy(start_offset: loc.start_offset + capture_name_offset, length: name.length)
|
313
315
|
|
314
|
-
|
316
|
+
local = @current_scope.lookup(name)
|
317
|
+
@response_builder.add_token(local_var_loc, local&.type || :variable)
|
315
318
|
end
|
316
319
|
end
|
317
320
|
end
|
@@ -40,6 +40,8 @@ module RubyLsp
|
|
40
40
|
# For example, forgetting to return the `insertText` included in the original item will make the editor use the
|
41
41
|
# `label` for the text edit instead
|
42
42
|
label = @item[:label].dup
|
43
|
+
return keyword_resolve(@item) if @item.dig(:data, :keyword)
|
44
|
+
|
43
45
|
entries = @index[label] || []
|
44
46
|
|
45
47
|
owner_name = @item.dig(:data, :owner_name)
|
@@ -72,6 +74,33 @@ module RubyLsp
|
|
72
74
|
|
73
75
|
@item
|
74
76
|
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
sig { params(item: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
|
81
|
+
def keyword_resolve(item)
|
82
|
+
keyword = item[:label]
|
83
|
+
content = KEYWORD_DOCS[keyword]
|
84
|
+
|
85
|
+
if content
|
86
|
+
doc_path = File.join(STATIC_DOCS_PATH, "#{keyword}.md")
|
87
|
+
|
88
|
+
@item[:documentation] = Interface::MarkupContent.new(
|
89
|
+
kind: "markdown",
|
90
|
+
value: <<~MARKDOWN.chomp,
|
91
|
+
```ruby
|
92
|
+
#{keyword}
|
93
|
+
```
|
94
|
+
|
95
|
+
[Read more](#{doc_path})
|
96
|
+
|
97
|
+
#{content}
|
98
|
+
MARKDOWN
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
item
|
103
|
+
end
|
75
104
|
end
|
76
105
|
end
|
77
106
|
end
|
@@ -63,7 +63,7 @@ module RubyLsp
|
|
63
63
|
if (comment_match = @previous_line.match(/^#(\s*)/))
|
64
64
|
handle_comment_line(T.must(comment_match[1]))
|
65
65
|
elsif @document.syntax_error?
|
66
|
-
match = /(
|
66
|
+
match = /(<<((-|~)?))(?<quote>['"`]?)(?<delimiter>\w+)\k<quote>/.match(@previous_line)
|
67
67
|
heredoc_delimiter = match && match.named_captures["delimiter"]
|
68
68
|
|
69
69
|
if heredoc_delimiter
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
# The
|
7
|
+
# [rename](https://microsoft.github.io/language-server-protocol/specification#textDocument_rename)
|
8
|
+
# request renames all instances of a symbol in a document.
|
9
|
+
class Rename < Request
|
10
|
+
extend T::Sig
|
11
|
+
include Support::Common
|
12
|
+
|
13
|
+
class InvalidNameError < StandardError; end
|
14
|
+
|
15
|
+
sig do
|
16
|
+
params(
|
17
|
+
global_state: GlobalState,
|
18
|
+
store: Store,
|
19
|
+
document: T.any(RubyDocument, ERBDocument),
|
20
|
+
params: T::Hash[Symbol, T.untyped],
|
21
|
+
).void
|
22
|
+
end
|
23
|
+
def initialize(global_state, store, document, params)
|
24
|
+
super()
|
25
|
+
@global_state = global_state
|
26
|
+
@store = store
|
27
|
+
@document = document
|
28
|
+
@position = T.let(params[:position], T::Hash[Symbol, Integer])
|
29
|
+
@new_name = T.let(params[:newName], String)
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { override.returns(T.nilable(Interface::WorkspaceEdit)) }
|
33
|
+
def perform
|
34
|
+
char_position = @document.create_scanner.find_char_position(@position)
|
35
|
+
|
36
|
+
node_context = RubyDocument.locate(
|
37
|
+
@document.parse_result.value,
|
38
|
+
char_position,
|
39
|
+
node_types: [Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode],
|
40
|
+
)
|
41
|
+
target = node_context.node
|
42
|
+
parent = node_context.parent
|
43
|
+
return if !target || target.is_a?(Prism::ProgramNode)
|
44
|
+
|
45
|
+
if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
|
46
|
+
target = determine_target(
|
47
|
+
target,
|
48
|
+
parent,
|
49
|
+
@position,
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
target = T.cast(
|
54
|
+
target,
|
55
|
+
T.any(Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode),
|
56
|
+
)
|
57
|
+
|
58
|
+
name = constant_name(target)
|
59
|
+
return unless name
|
60
|
+
|
61
|
+
entries = @global_state.index.resolve(name, node_context.nesting)
|
62
|
+
return unless entries
|
63
|
+
|
64
|
+
if (conflict_entries = @global_state.index.resolve(@new_name, node_context.nesting))
|
65
|
+
raise InvalidNameError, "The new name is already in use by #{T.must(conflict_entries.first).name}"
|
66
|
+
end
|
67
|
+
|
68
|
+
fully_qualified_name = T.must(entries.first).name
|
69
|
+
changes = collect_text_edits(fully_qualified_name, name)
|
70
|
+
|
71
|
+
# If the client doesn't support resource operations, such as renaming files, then we can only return the basic
|
72
|
+
# text changes
|
73
|
+
unless @global_state.supported_resource_operations.include?("rename")
|
74
|
+
return Interface::WorkspaceEdit.new(changes: changes)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Text edits must be applied before any resource operations, such as renaming files. Otherwise, the file is
|
78
|
+
# renamed and then the URI associated to the text edit no longer exists, causing it to be dropped
|
79
|
+
document_changes = changes.map do |uri, edits|
|
80
|
+
Interface::TextDocumentEdit.new(
|
81
|
+
text_document: Interface::VersionedTextDocumentIdentifier.new(uri: uri, version: nil),
|
82
|
+
edits: edits,
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
collect_file_renames(fully_qualified_name, document_changes)
|
87
|
+
Interface::WorkspaceEdit.new(document_changes: document_changes)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
sig do
|
93
|
+
params(
|
94
|
+
fully_qualified_name: String,
|
95
|
+
document_changes: T::Array[T.any(Interface::RenameFile, Interface::TextDocumentEdit)],
|
96
|
+
).void
|
97
|
+
end
|
98
|
+
def collect_file_renames(fully_qualified_name, document_changes)
|
99
|
+
# Check if the declarations of the symbol being renamed match the file name. In case they do, we automatically
|
100
|
+
# rename the files for the user.
|
101
|
+
#
|
102
|
+
# We also look for an associated test file and rename it too
|
103
|
+
short_name = T.must(fully_qualified_name.split("::").last)
|
104
|
+
|
105
|
+
T.must(@global_state.index[fully_qualified_name]).each do |entry|
|
106
|
+
# Do not rename files that are not part of the workspace
|
107
|
+
next unless entry.file_path.start_with?(@global_state.workspace_path)
|
108
|
+
|
109
|
+
case entry
|
110
|
+
when RubyIndexer::Entry::Class, RubyIndexer::Entry::Module, RubyIndexer::Entry::Constant,
|
111
|
+
RubyIndexer::Entry::ConstantAlias, RubyIndexer::Entry::UnresolvedConstantAlias
|
112
|
+
|
113
|
+
file_name = file_from_constant_name(short_name)
|
114
|
+
|
115
|
+
if "#{file_name}.rb" == entry.file_name
|
116
|
+
new_file_name = file_from_constant_name(T.must(@new_name.split("::").last))
|
117
|
+
|
118
|
+
old_uri = URI::Generic.from_path(path: entry.file_path).to_s
|
119
|
+
new_uri = URI::Generic.from_path(path: File.join(
|
120
|
+
File.dirname(entry.file_path),
|
121
|
+
"#{new_file_name}.rb",
|
122
|
+
)).to_s
|
123
|
+
|
124
|
+
document_changes << Interface::RenameFile.new(kind: "rename", old_uri: old_uri, new_uri: new_uri)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
sig { params(fully_qualified_name: String, name: String).returns(T::Hash[String, T::Array[Interface::TextEdit]]) }
|
131
|
+
def collect_text_edits(fully_qualified_name, name)
|
132
|
+
changes = {}
|
133
|
+
|
134
|
+
Dir.glob(File.join(@global_state.workspace_path, "**/*.rb")).each do |path|
|
135
|
+
uri = URI::Generic.from_path(path: path)
|
136
|
+
# If the document is being managed by the client, then we should use whatever is present in the store instead
|
137
|
+
# of reading from disk
|
138
|
+
next if @store.key?(uri)
|
139
|
+
|
140
|
+
parse_result = Prism.parse_file(path)
|
141
|
+
edits = collect_changes(fully_qualified_name, parse_result, name, uri)
|
142
|
+
changes[uri.to_s] = edits unless edits.empty?
|
143
|
+
end
|
144
|
+
|
145
|
+
@store.each do |uri, document|
|
146
|
+
edits = collect_changes(fully_qualified_name, document.parse_result, name, document.uri)
|
147
|
+
changes[uri] = edits unless edits.empty?
|
148
|
+
end
|
149
|
+
|
150
|
+
changes
|
151
|
+
end
|
152
|
+
|
153
|
+
sig do
|
154
|
+
params(
|
155
|
+
fully_qualified_name: String,
|
156
|
+
parse_result: Prism::ParseResult,
|
157
|
+
name: String,
|
158
|
+
uri: URI::Generic,
|
159
|
+
).returns(T::Array[Interface::TextEdit])
|
160
|
+
end
|
161
|
+
def collect_changes(fully_qualified_name, parse_result, name, uri)
|
162
|
+
dispatcher = Prism::Dispatcher.new
|
163
|
+
finder = RubyIndexer::ReferenceFinder.new(fully_qualified_name, @global_state.index, dispatcher)
|
164
|
+
dispatcher.visit(parse_result.value)
|
165
|
+
|
166
|
+
finder.references.uniq(&:location).map do |reference|
|
167
|
+
adjust_reference_for_edit(name, reference)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
sig { params(name: String, reference: RubyIndexer::ReferenceFinder::Reference).returns(Interface::TextEdit) }
|
172
|
+
def adjust_reference_for_edit(name, reference)
|
173
|
+
# The reference may include a namespace in front. We need to check if the rename new name includes namespaces
|
174
|
+
# and then adjust both the text and the location to produce the correct edit
|
175
|
+
location = reference.location
|
176
|
+
new_text = reference.name.sub(name, @new_name)
|
177
|
+
|
178
|
+
Interface::TextEdit.new(range: range_from_location(location), new_text: new_text)
|
179
|
+
end
|
180
|
+
|
181
|
+
sig { params(constant_name: String).returns(String) }
|
182
|
+
def file_from_constant_name(constant_name)
|
183
|
+
constant_name
|
184
|
+
.gsub(/([a-z])([A-Z])|([A-Z])([A-Z][a-z])/, '\1\3_\2\4')
|
185
|
+
.downcase
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -5,8 +5,8 @@ module RubyLsp
|
|
5
5
|
module Requests
|
6
6
|
module Support
|
7
7
|
module Common
|
8
|
-
# WARNING: Methods in this class may be used by Ruby LSP
|
9
|
-
# https://github.com/Shopify/ruby-lsp-rails, or
|
8
|
+
# WARNING: Methods in this class may be used by Ruby LSP add-ons such as
|
9
|
+
# https://github.com/Shopify/ruby-lsp-rails, or add-ons by created by developers outside of Shopify, so be
|
10
10
|
# cautious of changing anything.
|
11
11
|
extend T::Sig
|
12
12
|
extend T::Helpers
|
@@ -19,6 +19,13 @@ module URI
|
|
19
19
|
T::Array[Symbol],
|
20
20
|
)
|
21
21
|
|
22
|
+
# `uri` for Ruby 3.4 switched the default parser from RFC2396 to RFC3986. The new parser emits a deprecation
|
23
|
+
# warning on a few methods and delegates them to RFC2396, namely `extract`/`make_regexp`/`escape`/`unescape`.
|
24
|
+
# On earlier versions of the uri gem, the RFC2396_PARSER constant doesn't exist, so it needs some special
|
25
|
+
# handling to select a parser that doesn't emit deprecations. While it was backported to Ruby 3.1, users may
|
26
|
+
# have the uri gem in their own bundle and thus not use a compatible version.
|
27
|
+
PARSER = T.let(const_defined?(:RFC2396_PARSER) ? RFC2396_PARSER : DEFAULT_PARSER, RFC2396_Parser)
|
28
|
+
|
22
29
|
T.unsafe(self).alias_method(:gem_name, :host)
|
23
30
|
T.unsafe(self).alias_method(:line_number, :fragment)
|
24
31
|
|
@@ -41,7 +48,7 @@ module URI
|
|
41
48
|
{
|
42
49
|
scheme: "source",
|
43
50
|
host: gem_name,
|
44
|
-
path:
|
51
|
+
path: PARSER.escape("/#{gem_version}/#{path}"),
|
45
52
|
fragment: line_number,
|
46
53
|
}
|
47
54
|
)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
class Scope
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { returns(T.nilable(Scope)) }
|
9
|
+
attr_reader :parent
|
10
|
+
|
11
|
+
sig { params(parent: T.nilable(Scope)).void }
|
12
|
+
def initialize(parent = nil)
|
13
|
+
@parent = parent
|
14
|
+
|
15
|
+
# A hash of name => type
|
16
|
+
@locals = T.let({}, T::Hash[Symbol, Local])
|
17
|
+
end
|
18
|
+
|
19
|
+
# Add a new local to this scope. The types should only be `:parameter` or `:variable`
|
20
|
+
sig { params(name: T.any(String, Symbol), type: Symbol).void }
|
21
|
+
def add(name, type)
|
22
|
+
@locals[name.to_sym] = Local.new(type)
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(name: T.any(String, Symbol)).returns(T.nilable(Local)) }
|
26
|
+
def lookup(name)
|
27
|
+
sym = name.to_sym
|
28
|
+
entry = @locals[sym]
|
29
|
+
return entry if entry
|
30
|
+
return unless @parent
|
31
|
+
|
32
|
+
@parent.lookup(sym)
|
33
|
+
end
|
34
|
+
|
35
|
+
class Local
|
36
|
+
extend T::Sig
|
37
|
+
|
38
|
+
sig { returns(Symbol) }
|
39
|
+
attr_reader :type
|
40
|
+
|
41
|
+
sig { params(type: Symbol).void }
|
42
|
+
def initialize(type)
|
43
|
+
@type = type
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|