ruby-lsp 0.23.1 → 0.23.5
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/exe/ruby-lsp-launcher +18 -9
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +89 -58
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +21 -10
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +36 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/index_test.rb +2 -1
- data/lib/ruby_indexer/test/instance_variables_test.rb +20 -0
- data/lib/ruby_indexer/test/method_test.rb +86 -8
- data/lib/ruby_lsp/base_server.rb +11 -21
- data/lib/ruby_lsp/document.rb +62 -9
- data/lib/ruby_lsp/erb_document.rb +5 -3
- data/lib/ruby_lsp/global_state.rb +9 -0
- data/lib/ruby_lsp/listeners/definition.rb +7 -2
- data/lib/ruby_lsp/rbs_document.rb +2 -2
- data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -6
- data/lib/ruby_lsp/requests/completion.rb +2 -1
- data/lib/ruby_lsp/requests/definition.rb +1 -1
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +1 -1
- data/lib/ruby_lsp/requests/rename.rb +3 -3
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -4
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +3 -3
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -13
- data/lib/ruby_lsp/requests/workspace_symbol.rb +2 -2
- data/lib/ruby_lsp/ruby_document.rb +75 -6
- data/lib/ruby_lsp/server.rb +69 -49
- data/lib/ruby_lsp/store.rb +7 -7
- data/lib/ruby_lsp/type_inferrer.rb +39 -17
- data/lib/ruby_lsp/utils.rb +43 -0
- metadata +3 -2
data/lib/ruby_lsp/base_server.rb
CHANGED
@@ -18,22 +18,21 @@ module RubyLsp
|
|
18
18
|
@incoming_queue = T.let(Thread::Queue.new, Thread::Queue)
|
19
19
|
@outgoing_queue = T.let(Thread::Queue.new, Thread::Queue)
|
20
20
|
@cancelled_requests = T.let([], T::Array[Integer])
|
21
|
-
@mutex = T.let(Mutex.new, Mutex)
|
22
21
|
@worker = T.let(new_worker, Thread)
|
23
22
|
@current_request_id = T.let(1, Integer)
|
24
|
-
@
|
23
|
+
@global_state = T.let(GlobalState.new, GlobalState)
|
24
|
+
@store = T.let(Store.new(@global_state), Store)
|
25
25
|
@outgoing_dispatcher = T.let(
|
26
26
|
Thread.new do
|
27
27
|
unless @test_mode
|
28
28
|
while (message = @outgoing_queue.pop)
|
29
|
-
@
|
29
|
+
@global_state.synchronize { @writer.write(message.to_hash) }
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end,
|
33
33
|
Thread,
|
34
34
|
)
|
35
35
|
|
36
|
-
@global_state = T.let(GlobalState.new, GlobalState)
|
37
36
|
Thread.main.priority = 1
|
38
37
|
|
39
38
|
# We read the initialize request in `exe/ruby-lsp` to be able to determine the workspace URI where Bundler should
|
@@ -51,7 +50,7 @@ module RubyLsp
|
|
51
50
|
# source. Altering the source reference during parsing will put the parser in an invalid internal state, since
|
52
51
|
# it started parsing with one source but then it changed in the middle. We don't want to do this for text
|
53
52
|
# synchronization notifications
|
54
|
-
@
|
53
|
+
@global_state.synchronize do
|
55
54
|
uri = message.dig(:params, :textDocument, :uri)
|
56
55
|
|
57
56
|
if uri
|
@@ -91,22 +90,17 @@ module RubyLsp
|
|
91
90
|
# The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
|
92
91
|
# else is pushed into the incoming queue
|
93
92
|
case method
|
94
|
-
when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
|
95
|
-
"$/cancelRequest"
|
93
|
+
when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
|
96
94
|
process_message(message)
|
97
95
|
when "shutdown"
|
98
|
-
@
|
96
|
+
@global_state.synchronize do
|
99
97
|
send_log_message("Shutting down Ruby LSP...")
|
100
98
|
shutdown
|
101
99
|
run_shutdown
|
102
100
|
@writer.write(Result.new(id: message[:id], response: nil).to_hash)
|
103
101
|
end
|
104
102
|
when "exit"
|
105
|
-
@
|
106
|
-
status = @incoming_queue.closed? ? 0 : 1
|
107
|
-
send_log_message("Shutdown complete with status #{status}")
|
108
|
-
exit(status)
|
109
|
-
end
|
103
|
+
@global_state.synchronize { exit(@incoming_queue.closed? ? 0 : 1) }
|
110
104
|
else
|
111
105
|
@incoming_queue << message
|
112
106
|
end
|
@@ -121,8 +115,8 @@ module RubyLsp
|
|
121
115
|
@outgoing_queue.close
|
122
116
|
@cancelled_requests.clear
|
123
117
|
|
124
|
-
@worker.
|
125
|
-
@outgoing_dispatcher.
|
118
|
+
@worker.terminate
|
119
|
+
@outgoing_dispatcher.terminate
|
126
120
|
@store.clear
|
127
121
|
end
|
128
122
|
|
@@ -157,13 +151,9 @@ module RubyLsp
|
|
157
151
|
id = message[:id]
|
158
152
|
|
159
153
|
# Check if the request was cancelled before trying to process it
|
160
|
-
@
|
154
|
+
@global_state.synchronize do
|
161
155
|
if id && @cancelled_requests.include?(id)
|
162
|
-
send_message(
|
163
|
-
id: id,
|
164
|
-
code: Constant::ErrorCodes::REQUEST_CANCELLED,
|
165
|
-
message: "Request #{id} was cancelled",
|
166
|
-
))
|
156
|
+
send_message(Result.new(id: id, response: nil))
|
167
157
|
@cancelled_requests.delete(id)
|
168
158
|
next
|
169
159
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -40,19 +40,24 @@ module RubyLsp
|
|
40
40
|
sig { returns(Encoding) }
|
41
41
|
attr_reader :encoding
|
42
42
|
|
43
|
+
sig { returns(T.nilable(Edit)) }
|
44
|
+
attr_reader :last_edit
|
45
|
+
|
43
46
|
sig { returns(T.any(Interface::SemanticTokens, Object)) }
|
44
47
|
attr_accessor :semantic_tokens
|
45
48
|
|
46
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
47
|
-
def initialize(source:, version:, uri:,
|
49
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
|
50
|
+
def initialize(source:, version:, uri:, global_state:)
|
51
|
+
@source = source
|
52
|
+
@version = version
|
53
|
+
@global_state = global_state
|
48
54
|
@cache = T.let(Hash.new(EMPTY_CACHE), T::Hash[String, T.untyped])
|
49
55
|
@semantic_tokens = T.let(EMPTY_CACHE, T.any(Interface::SemanticTokens, Object))
|
50
|
-
@encoding = T.let(encoding, Encoding)
|
51
|
-
@source = T.let(source, String)
|
52
|
-
@version = T.let(version, Integer)
|
56
|
+
@encoding = T.let(global_state.encoding, Encoding)
|
53
57
|
@uri = T.let(uri, URI::Generic)
|
54
58
|
@needs_parsing = T.let(true, T::Boolean)
|
55
59
|
@parse_result = T.let(T.unsafe(nil), ParseResultType)
|
60
|
+
@last_edit = T.let(nil, T.nilable(Edit))
|
56
61
|
parse!
|
57
62
|
end
|
58
63
|
|
@@ -64,7 +69,6 @@ module RubyLsp
|
|
64
69
|
sig { abstract.returns(LanguageId) }
|
65
70
|
def language_id; end
|
66
71
|
|
67
|
-
# TODO: remove this method once all non-positional requests have been migrated to the listener pattern
|
68
72
|
sig do
|
69
73
|
type_parameters(:T)
|
70
74
|
.params(
|
@@ -106,6 +110,19 @@ module RubyLsp
|
|
106
110
|
@version = version
|
107
111
|
@needs_parsing = true
|
108
112
|
@cache.clear
|
113
|
+
|
114
|
+
last_edit = edits.last
|
115
|
+
return unless last_edit
|
116
|
+
|
117
|
+
last_edit_range = last_edit[:range]
|
118
|
+
|
119
|
+
@last_edit = if last_edit_range[:start] == last_edit_range[:end]
|
120
|
+
Insert.new(last_edit_range)
|
121
|
+
elsif last_edit[:text].empty?
|
122
|
+
Delete.new(last_edit_range)
|
123
|
+
else
|
124
|
+
Replace.new(last_edit_range)
|
125
|
+
end
|
109
126
|
end
|
110
127
|
|
111
128
|
# Returns `true` if the document was parsed and `false` if nothing needed parsing
|
@@ -115,16 +132,52 @@ module RubyLsp
|
|
115
132
|
sig { abstract.returns(T::Boolean) }
|
116
133
|
def syntax_error?; end
|
117
134
|
|
135
|
+
sig { returns(T::Boolean) }
|
136
|
+
def past_expensive_limit?
|
137
|
+
@source.length > MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES
|
138
|
+
end
|
139
|
+
|
140
|
+
sig do
|
141
|
+
params(
|
142
|
+
start_pos: T::Hash[Symbol, T.untyped],
|
143
|
+
end_pos: T.nilable(T::Hash[Symbol, T.untyped]),
|
144
|
+
).returns([Integer, T.nilable(Integer)])
|
145
|
+
end
|
146
|
+
def find_index_by_position(start_pos, end_pos = nil)
|
147
|
+
@global_state.synchronize do
|
148
|
+
scanner = create_scanner
|
149
|
+
start_index = scanner.find_char_position(start_pos)
|
150
|
+
end_index = scanner.find_char_position(end_pos) if end_pos
|
151
|
+
[start_index, end_index]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
118
157
|
sig { returns(Scanner) }
|
119
158
|
def create_scanner
|
120
159
|
Scanner.new(@source, @encoding)
|
121
160
|
end
|
122
161
|
|
123
|
-
|
124
|
-
|
125
|
-
|
162
|
+
class Edit
|
163
|
+
extend T::Sig
|
164
|
+
extend T::Helpers
|
165
|
+
|
166
|
+
abstract!
|
167
|
+
|
168
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
169
|
+
attr_reader :range
|
170
|
+
|
171
|
+
sig { params(range: T::Hash[Symbol, T.untyped]).void }
|
172
|
+
def initialize(range)
|
173
|
+
@range = range
|
174
|
+
end
|
126
175
|
end
|
127
176
|
|
177
|
+
class Insert < Edit; end
|
178
|
+
class Replace < Edit; end
|
179
|
+
class Delete < Edit; end
|
180
|
+
|
128
181
|
class Scanner
|
129
182
|
extend T::Sig
|
130
183
|
|
@@ -19,8 +19,8 @@ module RubyLsp
|
|
19
19
|
end
|
20
20
|
attr_reader :code_units_cache
|
21
21
|
|
22
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
23
|
-
def initialize(source:, version:, uri:,
|
22
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
|
23
|
+
def initialize(source:, version:, uri:, global_state:)
|
24
24
|
# This has to be initialized before calling super because we call `parse` in the parent constructor, which
|
25
25
|
# overrides this with the proper virtual host language source
|
26
26
|
@host_language_source = T.let("", String)
|
@@ -63,9 +63,11 @@ module RubyLsp
|
|
63
63
|
).returns(NodeContext)
|
64
64
|
end
|
65
65
|
def locate_node(position, node_types: [])
|
66
|
+
char_position, _ = find_index_by_position(position)
|
67
|
+
|
66
68
|
RubyDocument.locate(
|
67
69
|
@parse_result.value,
|
68
|
-
|
70
|
+
char_position,
|
69
71
|
code_units_cache: @code_units_cache,
|
70
72
|
node_types: node_types,
|
71
73
|
)
|
@@ -29,6 +29,9 @@ module RubyLsp
|
|
29
29
|
sig { returns(ClientCapabilities) }
|
30
30
|
attr_reader :client_capabilities
|
31
31
|
|
32
|
+
sig { returns(URI::Generic) }
|
33
|
+
attr_reader :workspace_uri
|
34
|
+
|
32
35
|
sig { void }
|
33
36
|
def initialize
|
34
37
|
@workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
|
@@ -53,6 +56,12 @@ module RubyLsp
|
|
53
56
|
)
|
54
57
|
@client_capabilities = T.let(ClientCapabilities.new, ClientCapabilities)
|
55
58
|
@enabled_feature_flags = T.let({}, T::Hash[Symbol, T::Boolean])
|
59
|
+
@mutex = T.let(Mutex.new, Mutex)
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) }
|
63
|
+
def synchronize(&block)
|
64
|
+
@mutex.synchronize(&block)
|
56
65
|
end
|
57
66
|
|
58
67
|
sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
@@ -330,7 +330,8 @@ module RubyLsp
|
|
330
330
|
|
331
331
|
methods.each do |target_method|
|
332
332
|
uri = target_method.uri
|
333
|
-
|
333
|
+
full_path = uri.full_path
|
334
|
+
next if sorbet_level_true_or_higher?(@sorbet_level) && (!full_path || not_in_dependencies?(full_path))
|
334
335
|
|
335
336
|
@response_builder << Interface::LocationLink.new(
|
336
337
|
target_uri: uri.to_s,
|
@@ -403,7 +404,11 @@ module RubyLsp
|
|
403
404
|
# additional behavior on top of jumping to RBIs. The only sigil where Sorbet cannot handle constants is typed
|
404
405
|
# ignore
|
405
406
|
uri = entry.uri
|
406
|
-
|
407
|
+
full_path = uri.full_path
|
408
|
+
|
409
|
+
if @sorbet_level != RubyDocument::SorbetLevel::Ignore && (!full_path || not_in_dependencies?(full_path))
|
410
|
+
next
|
411
|
+
end
|
407
412
|
|
408
413
|
@response_builder << Interface::LocationLink.new(
|
409
414
|
target_uri: uri.to_s,
|
@@ -8,8 +8,8 @@ module RubyLsp
|
|
8
8
|
|
9
9
|
ParseResultType = type_member { { fixed: T::Array[RBS::AST::Declarations::Base] } }
|
10
10
|
|
11
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
12
|
-
def initialize(source:, version:, uri:,
|
11
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
|
12
|
+
def initialize(source:, version:, uri:, global_state:)
|
13
13
|
@syntax_error = T.let(false, T::Boolean)
|
14
14
|
super
|
15
15
|
end
|
@@ -92,9 +92,7 @@ module RubyLsp
|
|
92
92
|
source_range = @code_action.dig(:data, :range)
|
93
93
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
94
94
|
|
95
|
-
|
96
|
-
start_index = scanner.find_char_position(source_range[:start])
|
97
|
-
end_index = scanner.find_char_position(source_range[:end])
|
95
|
+
start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
|
98
96
|
extracted_source = T.must(@document.source[start_index...end_index])
|
99
97
|
|
100
98
|
# Find the closest statements node, so that we place the refactor in a valid position
|
@@ -192,9 +190,7 @@ module RubyLsp
|
|
192
190
|
source_range = @code_action.dig(:data, :range)
|
193
191
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
194
192
|
|
195
|
-
|
196
|
-
start_index = scanner.find_char_position(source_range[:start])
|
197
|
-
end_index = scanner.find_char_position(source_range[:end])
|
193
|
+
start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
|
198
194
|
extracted_source = T.must(@document.source[start_index...end_index])
|
199
195
|
|
200
196
|
# Find the closest method declaration node, so that we place the refactor in a valid position
|
@@ -40,7 +40,8 @@ module RubyLsp
|
|
40
40
|
@dispatcher = dispatcher
|
41
41
|
# Completion always receives the position immediately after the character that was just typed. Here we adjust it
|
42
42
|
# back by 1, so that we find the right node
|
43
|
-
char_position = document.
|
43
|
+
char_position, _ = document.find_index_by_position(params[:position])
|
44
|
+
char_position -= 1
|
44
45
|
delegate_request_if_needed!(global_state, document, char_position)
|
45
46
|
|
46
47
|
node_context = RubyDocument.locate(
|
@@ -29,7 +29,7 @@ module RubyLsp
|
|
29
29
|
)
|
30
30
|
@dispatcher = dispatcher
|
31
31
|
|
32
|
-
char_position = document.
|
32
|
+
char_position, _ = document.find_index_by_position(position)
|
33
33
|
delegate_request_if_needed!(global_state, document, char_position)
|
34
34
|
|
35
35
|
node_context = RubyDocument.locate(
|
@@ -25,7 +25,7 @@ module RubyLsp
|
|
25
25
|
end
|
26
26
|
def initialize(global_state, document, position, dispatcher)
|
27
27
|
super()
|
28
|
-
char_position = document.
|
28
|
+
char_position, _ = document.find_index_by_position(position)
|
29
29
|
delegate_request_if_needed!(global_state, document, char_position)
|
30
30
|
|
31
31
|
node_context = RubyDocument.locate(
|
@@ -34,7 +34,7 @@ module RubyLsp
|
|
34
34
|
def initialize(document, global_state, position, dispatcher, sorbet_level)
|
35
35
|
super()
|
36
36
|
|
37
|
-
char_position = document.
|
37
|
+
char_position, _ = document.find_index_by_position(position)
|
38
38
|
delegate_request_if_needed!(global_state, document, char_position)
|
39
39
|
|
40
40
|
node_context = RubyDocument.locate(
|
@@ -24,7 +24,7 @@ module RubyLsp
|
|
24
24
|
|
25
25
|
sig { override.returns(T.nilable(Interface::Range)) }
|
26
26
|
def perform
|
27
|
-
char_position = @document.
|
27
|
+
char_position, _ = @document.find_index_by_position(@position)
|
28
28
|
|
29
29
|
node_context = RubyDocument.locate(
|
30
30
|
@document.parse_result.value,
|
@@ -30,7 +30,7 @@ module RubyLsp
|
|
30
30
|
sig { override.returns(T::Array[Interface::Location]) }
|
31
31
|
def perform
|
32
32
|
position = @params[:position]
|
33
|
-
char_position = @document.
|
33
|
+
char_position, _ = @document.find_index_by_position(position)
|
34
34
|
|
35
35
|
node_context = RubyDocument.locate(
|
36
36
|
@document.parse_result.value,
|
@@ -40,7 +40,7 @@ module RubyLsp
|
|
40
40
|
|
41
41
|
sig { override.returns(T.nilable(Interface::WorkspaceEdit)) }
|
42
42
|
def perform
|
43
|
-
char_position = @document.
|
43
|
+
char_position, _ = @document.find_index_by_position(@position)
|
44
44
|
|
45
45
|
node_context = RubyDocument.locate(
|
46
46
|
@document.parse_result.value,
|
@@ -116,8 +116,8 @@ module RubyLsp
|
|
116
116
|
T.must(@global_state.index[fully_qualified_name]).each do |entry|
|
117
117
|
# Do not rename files that are not part of the workspace
|
118
118
|
uri = entry.uri
|
119
|
-
file_path =
|
120
|
-
next unless file_path
|
119
|
+
file_path = uri.full_path
|
120
|
+
next unless file_path&.start_with?(@global_state.workspace_path)
|
121
121
|
|
122
122
|
case entry
|
123
123
|
when RubyIndexer::Entry::Class, RubyIndexer::Entry::Module, RubyIndexer::Entry::Constant,
|
@@ -31,10 +31,7 @@ module RubyLsp
|
|
31
31
|
sig { returns(String) }
|
32
32
|
def ast_for_range
|
33
33
|
range = T.must(@range)
|
34
|
-
|
35
|
-
scanner = @document.create_scanner
|
36
|
-
start_char = scanner.find_char_position(range[:start])
|
37
|
-
end_char = scanner.find_char_position(range[:end])
|
34
|
+
start_char, end_char = @document.find_index_by_position(range[:start], range[:end])
|
38
35
|
|
39
36
|
queue = @tree.statements.body.dup
|
40
37
|
found_nodes = []
|
@@ -36,7 +36,7 @@ module RubyLsp
|
|
36
36
|
def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
|
37
37
|
super()
|
38
38
|
|
39
|
-
char_position = document.
|
39
|
+
char_position, _ = document.find_index_by_position(position)
|
40
40
|
delegate_request_if_needed!(global_state, document, char_position)
|
41
41
|
|
42
42
|
node_context = RubyDocument.locate(
|
@@ -31,7 +31,7 @@ module RubyLsp
|
|
31
31
|
|
32
32
|
# TODO: avoid passing document once we have alternative ways to get at
|
33
33
|
# encoding and file source
|
34
|
-
sig { params(document: RubyDocument, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
|
34
|
+
sig { params(document: RubyDocument, offense: ::RuboCop::Cop::Offense, uri: URI::Generic).void }
|
35
35
|
def initialize(document, offense, uri)
|
36
36
|
@document = document
|
37
37
|
@offense = offense
|
@@ -48,7 +48,7 @@ module RubyLsp
|
|
48
48
|
code_actions
|
49
49
|
end
|
50
50
|
|
51
|
-
sig { params(config: RuboCop::Config).returns(Interface::Diagnostic) }
|
51
|
+
sig { params(config: ::RuboCop::Config).returns(Interface::Diagnostic) }
|
52
52
|
def to_lsp_diagnostic(config)
|
53
53
|
# highlighted_area contains the begin and end position of the first line
|
54
54
|
# This ensures that multiline offenses don't clutter the editor
|
@@ -90,7 +90,7 @@ module RubyLsp
|
|
90
90
|
RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name]
|
91
91
|
end
|
92
92
|
|
93
|
-
sig { params(config: RuboCop::Config).returns(T.nilable(Interface::CodeDescription)) }
|
93
|
+
sig { params(config: ::RuboCop::Config).returns(T.nilable(Interface::CodeDescription)) }
|
94
94
|
def code_description(config)
|
95
95
|
cop = RuboCopRunner.find_cop_by_name(@offense.cop_name)
|
96
96
|
return unless cop
|
@@ -40,10 +40,10 @@ module RubyLsp
|
|
40
40
|
For more details, run RuboCop on the command line.
|
41
41
|
EOS
|
42
42
|
|
43
|
-
sig { params(rubocop_error: T.any(RuboCop::ErrorWithAnalyzedFileLocation, StandardError)).void }
|
43
|
+
sig { params(rubocop_error: T.any(::RuboCop::ErrorWithAnalyzedFileLocation, StandardError)).void }
|
44
44
|
def initialize(rubocop_error)
|
45
45
|
message = case rubocop_error
|
46
|
-
when RuboCop::ErrorWithAnalyzedFileLocation
|
46
|
+
when ::RuboCop::ErrorWithAnalyzedFileLocation
|
47
47
|
format(MESSAGE, "for the #{rubocop_error.cop.name} cop")
|
48
48
|
when StandardError
|
49
49
|
format(MESSAGE, rubocop_error.message)
|
@@ -53,7 +53,7 @@ module RubyLsp
|
|
53
53
|
end
|
54
54
|
|
55
55
|
# :nodoc:
|
56
|
-
class RuboCopRunner < RuboCop::Runner
|
56
|
+
class RuboCopRunner < ::RuboCop::Runner
|
57
57
|
extend T::Sig
|
58
58
|
|
59
59
|
class ConfigurationError < StandardError; end
|
@@ -68,14 +68,14 @@ module RubyLsp
|
|
68
68
|
T::Array[String],
|
69
69
|
)
|
70
70
|
|
71
|
-
sig { returns(T::Array[RuboCop::Cop::Offense]) }
|
71
|
+
sig { returns(T::Array[::RuboCop::Cop::Offense]) }
|
72
72
|
attr_reader :offenses
|
73
73
|
|
74
74
|
sig { returns(::RuboCop::Config) }
|
75
75
|
attr_reader :config_for_working_directory
|
76
76
|
|
77
77
|
begin
|
78
|
-
RuboCop::Options.new.parse(["--raise-cop-error"])
|
78
|
+
::RuboCop::Options.new.parse(["--raise-cop-error"])
|
79
79
|
DEFAULT_ARGS << "--raise-cop-error"
|
80
80
|
rescue OptionParser::InvalidOption
|
81
81
|
# older versions of RuboCop don't support this flag
|
@@ -85,7 +85,7 @@ module RubyLsp
|
|
85
85
|
sig { params(args: String).void }
|
86
86
|
def initialize(*args)
|
87
87
|
@options = T.let({}, T::Hash[Symbol, T.untyped])
|
88
|
-
@offenses = T.let([], T::Array[RuboCop::Cop::Offense])
|
88
|
+
@offenses = T.let([], T::Array[::RuboCop::Cop::Offense])
|
89
89
|
@errors = T.let([], T::Array[String])
|
90
90
|
@warnings = T.let([], T::Array[String])
|
91
91
|
|
@@ -113,9 +113,9 @@ module RubyLsp
|
|
113
113
|
# RuboCop rescues interrupts and then sets the `@aborting` variable to true. We don't want them to be rescued,
|
114
114
|
# so here we re-raise in case RuboCop received an interrupt.
|
115
115
|
raise Interrupt if aborting?
|
116
|
-
rescue RuboCop::Runner::InfiniteCorrectionLoop => error
|
116
|
+
rescue ::RuboCop::Runner::InfiniteCorrectionLoop => error
|
117
117
|
raise Formatting::Error, error.message
|
118
|
-
rescue RuboCop::ValidationError => error
|
118
|
+
rescue ::RuboCop::ValidationError => error
|
119
119
|
raise ConfigurationError, error.message
|
120
120
|
rescue StandardError => error
|
121
121
|
raise InternalRuboCopError, error
|
@@ -129,25 +129,25 @@ module RubyLsp
|
|
129
129
|
class << self
|
130
130
|
extend T::Sig
|
131
131
|
|
132
|
-
sig { params(cop_name: String).returns(T.nilable(T.class_of(RuboCop::Cop::Base))) }
|
132
|
+
sig { params(cop_name: String).returns(T.nilable(T.class_of(::RuboCop::Cop::Base))) }
|
133
133
|
def find_cop_by_name(cop_name)
|
134
134
|
cop_registry[cop_name]&.first
|
135
135
|
end
|
136
136
|
|
137
137
|
private
|
138
138
|
|
139
|
-
sig { returns(T::Hash[String, [T.class_of(RuboCop::Cop::Base)]]) }
|
139
|
+
sig { returns(T::Hash[String, [T.class_of(::RuboCop::Cop::Base)]]) }
|
140
140
|
def cop_registry
|
141
141
|
@cop_registry ||= T.let(
|
142
|
-
RuboCop::Cop::Registry.global.to_h,
|
143
|
-
T.nilable(T::Hash[String, [T.class_of(RuboCop::Cop::Base)]]),
|
142
|
+
::RuboCop::Cop::Registry.global.to_h,
|
143
|
+
T.nilable(T::Hash[String, [T.class_of(::RuboCop::Cop::Base)]]),
|
144
144
|
)
|
145
145
|
end
|
146
146
|
end
|
147
147
|
|
148
148
|
private
|
149
149
|
|
150
|
-
sig { params(_file: String, offenses: T::Array[RuboCop::Cop::Offense]).void }
|
150
|
+
sig { params(_file: String, offenses: T::Array[::RuboCop::Cop::Offense]).void }
|
151
151
|
def file_finished(_file, offenses)
|
152
152
|
@offenses = offenses
|
153
153
|
end
|
@@ -22,10 +22,10 @@ module RubyLsp
|
|
22
22
|
def perform
|
23
23
|
@index.fuzzy_search(@query).filter_map do |entry|
|
24
24
|
uri = entry.uri
|
25
|
-
file_path =
|
25
|
+
file_path = uri.full_path
|
26
26
|
|
27
27
|
# We only show symbols declared in the workspace
|
28
|
-
in_dependencies = !not_in_dependencies?(file_path)
|
28
|
+
in_dependencies = file_path && !not_in_dependencies?(file_path)
|
29
29
|
next if in_dependencies
|
30
30
|
|
31
31
|
# We should never show private symbols when searching the entire workspace
|
@@ -8,6 +8,22 @@ module RubyLsp
|
|
8
8
|
|
9
9
|
ParseResultType = type_member { { fixed: Prism::ParseResult } }
|
10
10
|
|
11
|
+
METHODS_THAT_CHANGE_DECLARATIONS = [
|
12
|
+
:private_constant,
|
13
|
+
:attr_reader,
|
14
|
+
:attr_writer,
|
15
|
+
:attr_accessor,
|
16
|
+
:alias_method,
|
17
|
+
:include,
|
18
|
+
:prepend,
|
19
|
+
:extend,
|
20
|
+
:public,
|
21
|
+
:protected,
|
22
|
+
:private,
|
23
|
+
:module_function,
|
24
|
+
:private_class_method,
|
25
|
+
].freeze
|
26
|
+
|
11
27
|
class SorbetLevel < T::Enum
|
12
28
|
enums do
|
13
29
|
None = new("none")
|
@@ -142,8 +158,8 @@ module RubyLsp
|
|
142
158
|
end
|
143
159
|
attr_reader :code_units_cache
|
144
160
|
|
145
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
146
|
-
def initialize(source:, version:, uri:,
|
161
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
|
162
|
+
def initialize(source:, version:, uri:, global_state:)
|
147
163
|
super
|
148
164
|
@code_units_cache = T.let(@parse_result.code_units_cache(@encoding), T.any(
|
149
165
|
T.proc.params(arg0: Integer).returns(Integer),
|
@@ -198,9 +214,8 @@ module RubyLsp
|
|
198
214
|
).returns(T.nilable(Prism::Node))
|
199
215
|
end
|
200
216
|
def locate_first_within_range(range, node_types: [])
|
201
|
-
|
202
|
-
|
203
|
-
end_position = scanner.find_char_position(range[:end])
|
217
|
+
start_position, end_position = find_index_by_position(range[:start], range[:end])
|
218
|
+
|
204
219
|
desired_range = (start_position...end_position)
|
205
220
|
queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
206
221
|
|
@@ -232,12 +247,66 @@ module RubyLsp
|
|
232
247
|
).returns(NodeContext)
|
233
248
|
end
|
234
249
|
def locate_node(position, node_types: [])
|
250
|
+
char_position, _ = find_index_by_position(position)
|
251
|
+
|
235
252
|
RubyDocument.locate(
|
236
253
|
@parse_result.value,
|
237
|
-
|
254
|
+
char_position,
|
238
255
|
code_units_cache: @code_units_cache,
|
239
256
|
node_types: node_types,
|
240
257
|
)
|
241
258
|
end
|
259
|
+
|
260
|
+
sig { returns(T::Boolean) }
|
261
|
+
def last_edit_may_change_declarations?
|
262
|
+
# This method controls when we should index documents. If there's no recent edit and the document has just been
|
263
|
+
# opened, we need to index it
|
264
|
+
return true unless @last_edit
|
265
|
+
|
266
|
+
case @last_edit
|
267
|
+
when Delete
|
268
|
+
# Not optimized yet. It's not trivial to identify that a declaration has been removed since the source is no
|
269
|
+
# longer there and we don't remember the deleted text
|
270
|
+
true
|
271
|
+
when Insert, Replace
|
272
|
+
position_may_impact_declarations?(@last_edit.range[:start])
|
273
|
+
else
|
274
|
+
false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
private
|
279
|
+
|
280
|
+
sig { params(position: T::Hash[Symbol, Integer]).returns(T::Boolean) }
|
281
|
+
def position_may_impact_declarations?(position)
|
282
|
+
node_context = locate_node(position)
|
283
|
+
node_at_edit = node_context.node
|
284
|
+
|
285
|
+
# Adjust to the parent when editing the constant of a class/module declaration
|
286
|
+
if node_at_edit.is_a?(Prism::ConstantReadNode) || node_at_edit.is_a?(Prism::ConstantPathNode)
|
287
|
+
node_at_edit = node_context.parent
|
288
|
+
end
|
289
|
+
|
290
|
+
case node_at_edit
|
291
|
+
when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode,
|
292
|
+
Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
|
293
|
+
Prism::ConstantPathAndWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantWriteNode,
|
294
|
+
Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode, Prism::GlobalVariableAndWriteNode,
|
295
|
+
Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableTargetNode,
|
296
|
+
Prism::GlobalVariableWriteNode, Prism::InstanceVariableWriteNode, Prism::InstanceVariableAndWriteNode,
|
297
|
+
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode,
|
298
|
+
Prism::InstanceVariableTargetNode, Prism::AliasMethodNode
|
299
|
+
true
|
300
|
+
when Prism::MultiWriteNode
|
301
|
+
[*node_at_edit.lefts, *node_at_edit.rest, *node_at_edit.rights].any? do |node|
|
302
|
+
node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
|
303
|
+
end
|
304
|
+
when Prism::CallNode
|
305
|
+
receiver = node_at_edit.receiver
|
306
|
+
(!receiver || receiver.is_a?(Prism::SelfNode)) && METHODS_THAT_CHANGE_DECLARATIONS.include?(node_at_edit.name)
|
307
|
+
else
|
308
|
+
false
|
309
|
+
end
|
310
|
+
end
|
242
311
|
end
|
243
312
|
end
|