ruby-lsp 0.22.1 → 0.23.10
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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +12 -11
- data/exe/ruby-lsp-check +5 -5
- data/exe/ruby-lsp-launcher +41 -15
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +26 -20
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +191 -100
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +60 -30
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +174 -61
- data/lib/ruby_indexer/lib/ruby_indexer/location.rb +12 -0
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +16 -14
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +82 -61
- data/lib/{core_ext → ruby_indexer/lib/ruby_indexer}/uri.rb +29 -3
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +36 -0
- data/lib/ruby_indexer/ruby_indexer.rb +2 -1
- data/lib/ruby_indexer/test/class_variables_test.rb +140 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +30 -6
- data/lib/ruby_indexer/test/configuration_test.rb +116 -51
- data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
- data/lib/ruby_indexer/test/index_test.rb +143 -44
- data/lib/ruby_indexer/test/instance_variables_test.rb +20 -0
- data/lib/ruby_indexer/test/method_test.rb +86 -8
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
- data/lib/ruby_indexer/test/reference_finder_test.rb +90 -2
- data/lib/ruby_indexer/test/test_case.rb +2 -2
- data/lib/ruby_indexer/test/uri_test.rb +72 -0
- data/lib/ruby_lsp/addon.rb +9 -0
- data/lib/ruby_lsp/base_server.rb +17 -18
- data/lib/ruby_lsp/client_capabilities.rb +7 -1
- data/lib/ruby_lsp/document.rb +72 -10
- data/lib/ruby_lsp/erb_document.rb +5 -3
- data/lib/ruby_lsp/global_state.rb +42 -3
- data/lib/ruby_lsp/internal.rb +3 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +9 -5
- data/lib/ruby_lsp/listeners/completion.rb +78 -6
- data/lib/ruby_lsp/listeners/definition.rb +80 -19
- data/lib/ruby_lsp/listeners/document_highlight.rb +3 -2
- data/lib/ruby_lsp/listeners/document_link.rb +21 -3
- data/lib/ruby_lsp/listeners/document_symbol.rb +12 -1
- data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
- data/lib/ruby_lsp/listeners/hover.rb +59 -2
- data/lib/ruby_lsp/load_sorbet.rb +3 -3
- data/lib/ruby_lsp/rbs_document.rb +2 -2
- data/lib/ruby_lsp/requests/code_action_resolve.rb +90 -6
- data/lib/ruby_lsp/requests/code_actions.rb +57 -1
- data/lib/ruby_lsp/requests/completion.rb +8 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +2 -1
- data/lib/ruby_lsp/requests/definition.rb +7 -1
- data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/folding_ranges.rb +2 -6
- data/lib/ruby_lsp/requests/formatting.rb +2 -6
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/on_type_formatting.rb +2 -2
- data/lib/ruby_lsp/requests/prepare_rename.rb +51 -0
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +29 -2
- data/lib/ruby_lsp/requests/rename.rb +17 -7
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
- 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/common.rb +2 -9
- 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/type_hierarchy_supertypes.rb +1 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -3
- data/lib/ruby_lsp/ruby_document.rb +80 -6
- data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
- data/lib/ruby_lsp/server.rb +205 -61
- data/lib/ruby_lsp/setup_bundler.rb +50 -43
- data/lib/ruby_lsp/store.rb +7 -7
- data/lib/ruby_lsp/test_helper.rb +45 -11
- data/lib/ruby_lsp/type_inferrer.rb +60 -31
- data/lib/ruby_lsp/utils.rb +63 -3
- metadata +8 -8
- data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +0 -29
@@ -0,0 +1,72 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "test_helper"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class URITest < Minitest::Test
|
8
|
+
def test_from_path_on_unix
|
9
|
+
uri = URI::Generic.from_path(path: "/some/unix/path/to/file.rb")
|
10
|
+
assert_equal("/some/unix/path/to/file.rb", uri.path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_from_path_on_windows
|
14
|
+
uri = URI::Generic.from_path(path: "C:/some/windows/path/to/file.rb")
|
15
|
+
assert_equal("/C:/some/windows/path/to/file.rb", uri.path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_from_path_on_windows_with_lowercase_drive
|
19
|
+
uri = URI::Generic.from_path(path: "c:/some/windows/path/to/file.rb")
|
20
|
+
assert_equal("/c:/some/windows/path/to/file.rb", uri.path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_to_standardized_path_on_unix
|
24
|
+
uri = URI::Generic.from_path(path: "/some/unix/path/to/file.rb")
|
25
|
+
assert_equal(uri.path, uri.to_standardized_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_to_standardized_path_on_windows
|
29
|
+
uri = URI::Generic.from_path(path: "C:/some/windows/path/to/file.rb")
|
30
|
+
assert_equal("C:/some/windows/path/to/file.rb", uri.to_standardized_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_to_standardized_path_on_windows_with_lowercase_drive
|
34
|
+
uri = URI::Generic.from_path(path: "c:/some/windows/path/to/file.rb")
|
35
|
+
assert_equal("c:/some/windows/path/to/file.rb", uri.to_standardized_path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_to_standardized_path_on_windows_with_received_uri
|
39
|
+
uri = URI("file:///c%3A/some/windows/path/to/file.rb")
|
40
|
+
assert_equal("c:/some/windows/path/to/file.rb", uri.to_standardized_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_plus_signs_are_properly_unescaped
|
44
|
+
path = "/opt/rubies/3.3.0/lib/ruby/3.3.0+0/pathname.rb"
|
45
|
+
uri = URI::Generic.from_path(path: path)
|
46
|
+
assert_equal(path, uri.to_standardized_path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_from_path_with_fragment
|
50
|
+
uri = URI::Generic.from_path(path: "/some/unix/path/to/file.rb", fragment: "L1,3-2,9")
|
51
|
+
assert_equal("file:///some/unix/path/to/file.rb#L1,3-2,9", uri.to_s)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_from_path_windows_long_file_paths
|
55
|
+
uri = URI::Generic.from_path(path: "//?/C:/hostedtoolcache/windows/Ruby/3.3.1/x64/lib/ruby/3.3.0/open-uri.rb")
|
56
|
+
assert_equal("C:/hostedtoolcache/windows/Ruby/3.3.1/x64/lib/ruby/3.3.0/open-uri.rb", uri.to_standardized_path)
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_from_path_computes_require_path_when_load_path_entry_is_given
|
60
|
+
uri = URI::Generic.from_path(path: "/some/unix/path/to/file.rb", load_path_entry: "/some/unix/path")
|
61
|
+
assert_equal("to/file", uri.require_path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_allows_adding_require_path_with_load_path_entry
|
65
|
+
uri = URI::Generic.from_path(path: "/some/unix/path/to/file.rb")
|
66
|
+
assert_nil(uri.require_path)
|
67
|
+
|
68
|
+
uri.add_require_path_from_load_entry("/some/unix/path")
|
69
|
+
assert_equal("to/file", uri.require_path)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -97,6 +97,15 @@ module RubyLsp
|
|
97
97
|
errors
|
98
98
|
end
|
99
99
|
|
100
|
+
# Unloads all add-ons. Only intended to be invoked once when shutting down the Ruby LSP server
|
101
|
+
sig { void }
|
102
|
+
def unload_addons
|
103
|
+
@addons.each(&:deactivate)
|
104
|
+
@addons.clear
|
105
|
+
@addon_classes.clear
|
106
|
+
@file_watcher_addons.clear
|
107
|
+
end
|
108
|
+
|
100
109
|
# Get a reference to another add-on object by name and version. If an add-on exports an API that can be used by
|
101
110
|
# other add-ons, this is the way to get access to that API.
|
102
111
|
#
|
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
|
@@ -94,20 +93,14 @@ module RubyLsp
|
|
94
93
|
when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
|
95
94
|
process_message(message)
|
96
95
|
when "shutdown"
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
@mutex.synchronize do
|
96
|
+
@global_state.synchronize do
|
97
|
+
send_log_message("Shutting down Ruby LSP...")
|
98
|
+
shutdown
|
102
99
|
run_shutdown
|
103
100
|
@writer.write(Result.new(id: message[:id], response: nil).to_hash)
|
104
101
|
end
|
105
102
|
when "exit"
|
106
|
-
@
|
107
|
-
status = @incoming_queue.closed? ? 0 : 1
|
108
|
-
send_log_message("Shutdown complete with status #{status}")
|
109
|
-
exit(status)
|
110
|
-
end
|
103
|
+
@global_state.synchronize { exit(@incoming_queue.closed? ? 0 : 1) }
|
111
104
|
else
|
112
105
|
@incoming_queue << message
|
113
106
|
end
|
@@ -122,8 +115,8 @@ module RubyLsp
|
|
122
115
|
@outgoing_queue.close
|
123
116
|
@cancelled_requests.clear
|
124
117
|
|
125
|
-
@worker.
|
126
|
-
@outgoing_dispatcher.
|
118
|
+
@worker.terminate
|
119
|
+
@outgoing_dispatcher.terminate
|
127
120
|
@store.clear
|
128
121
|
end
|
129
122
|
|
@@ -133,6 +126,12 @@ module RubyLsp
|
|
133
126
|
@outgoing_queue.pop
|
134
127
|
end
|
135
128
|
|
129
|
+
# This method is only intended to be used in tests! Pushes a message to the incoming queue directly
|
130
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
131
|
+
def push_message(message)
|
132
|
+
@incoming_queue << message
|
133
|
+
end
|
134
|
+
|
136
135
|
sig { abstract.params(message: T::Hash[Symbol, T.untyped]).void }
|
137
136
|
def process_message(message); end
|
138
137
|
|
@@ -152,7 +151,7 @@ module RubyLsp
|
|
152
151
|
id = message[:id]
|
153
152
|
|
154
153
|
# Check if the request was cancelled before trying to process it
|
155
|
-
@
|
154
|
+
@global_state.synchronize do
|
156
155
|
if id && @cancelled_requests.include?(id)
|
157
156
|
send_message(Result.new(id: id, response: nil))
|
158
157
|
@cancelled_requests.delete(id)
|
@@ -11,7 +11,8 @@ module RubyLsp
|
|
11
11
|
attr_reader :supports_watching_files,
|
12
12
|
:supports_request_delegation,
|
13
13
|
:window_show_message_supports_extra_properties,
|
14
|
-
:supports_progress
|
14
|
+
:supports_progress,
|
15
|
+
:supports_diagnostic_refresh
|
15
16
|
|
16
17
|
sig { void }
|
17
18
|
def initialize
|
@@ -32,6 +33,9 @@ module RubyLsp
|
|
32
33
|
|
33
34
|
# The editor supports displaying progress requests
|
34
35
|
@supports_progress = T.let(false, T::Boolean)
|
36
|
+
|
37
|
+
# The editor supports server initiated refresh for diagnostics
|
38
|
+
@supports_diagnostic_refresh = T.let(false, T::Boolean)
|
35
39
|
end
|
36
40
|
|
37
41
|
sig { params(capabilities: T::Hash[Symbol, T.untyped]).void }
|
@@ -57,6 +61,8 @@ module RubyLsp
|
|
57
61
|
|
58
62
|
progress = capabilities.dig(:window, :workDoneProgress)
|
59
63
|
@supports_progress = progress if progress
|
64
|
+
|
65
|
+
@supports_diagnostic_refresh = workspace_capabilities.dig(:diagnostics, :refreshSupport) || false
|
60
66
|
end
|
61
67
|
|
62
68
|
sig { returns(T::Boolean) }
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -15,6 +15,7 @@ module RubyLsp
|
|
15
15
|
extend T::Helpers
|
16
16
|
extend T::Generic
|
17
17
|
|
18
|
+
class LocationNotFoundError < StandardError; end
|
18
19
|
ParseResultType = type_member
|
19
20
|
|
20
21
|
# This maximum number of characters for providing expensive features, like semantic highlighting and diagnostics.
|
@@ -39,19 +40,24 @@ module RubyLsp
|
|
39
40
|
sig { returns(Encoding) }
|
40
41
|
attr_reader :encoding
|
41
42
|
|
43
|
+
sig { returns(T.nilable(Edit)) }
|
44
|
+
attr_reader :last_edit
|
45
|
+
|
42
46
|
sig { returns(T.any(Interface::SemanticTokens, Object)) }
|
43
47
|
attr_accessor :semantic_tokens
|
44
48
|
|
45
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
46
|
-
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
|
47
54
|
@cache = T.let(Hash.new(EMPTY_CACHE), T::Hash[String, T.untyped])
|
48
55
|
@semantic_tokens = T.let(EMPTY_CACHE, T.any(Interface::SemanticTokens, Object))
|
49
|
-
@encoding = T.let(encoding, Encoding)
|
50
|
-
@source = T.let(source, String)
|
51
|
-
@version = T.let(version, Integer)
|
56
|
+
@encoding = T.let(global_state.encoding, Encoding)
|
52
57
|
@uri = T.let(uri, URI::Generic)
|
53
58
|
@needs_parsing = T.let(true, T::Boolean)
|
54
59
|
@parse_result = T.let(T.unsafe(nil), ParseResultType)
|
60
|
+
@last_edit = T.let(nil, T.nilable(Edit))
|
55
61
|
parse!
|
56
62
|
end
|
57
63
|
|
@@ -63,7 +69,6 @@ module RubyLsp
|
|
63
69
|
sig { abstract.returns(LanguageId) }
|
64
70
|
def language_id; end
|
65
71
|
|
66
|
-
# TODO: remove this method once all non-positional requests have been migrated to the listener pattern
|
67
72
|
sig do
|
68
73
|
type_parameters(:T)
|
69
74
|
.params(
|
@@ -105,6 +110,19 @@ module RubyLsp
|
|
105
110
|
@version = version
|
106
111
|
@needs_parsing = true
|
107
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
|
108
126
|
end
|
109
127
|
|
110
128
|
# Returns `true` if the document was parsed and `false` if nothing needed parsing
|
@@ -114,16 +132,52 @@ module RubyLsp
|
|
114
132
|
sig { abstract.returns(T::Boolean) }
|
115
133
|
def syntax_error?; end
|
116
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
|
+
|
117
157
|
sig { returns(Scanner) }
|
118
158
|
def create_scanner
|
119
159
|
Scanner.new(@source, @encoding)
|
120
160
|
end
|
121
161
|
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
125
175
|
end
|
126
176
|
|
177
|
+
class Insert < Edit; end
|
178
|
+
class Replace < Edit; end
|
179
|
+
class Delete < Edit; end
|
180
|
+
|
127
181
|
class Scanner
|
128
182
|
extend T::Sig
|
129
183
|
|
@@ -144,7 +198,15 @@ module RubyLsp
|
|
144
198
|
def find_char_position(position)
|
145
199
|
# Find the character index for the beginning of the requested line
|
146
200
|
until @current_line == position[:line]
|
147
|
-
|
201
|
+
until LINE_BREAK == @source[@pos]
|
202
|
+
@pos += 1
|
203
|
+
|
204
|
+
if @pos >= @source.length
|
205
|
+
# Pack the code points back into the original string to provide context in the error message
|
206
|
+
raise LocationNotFoundError, "Requested position: #{position}\nSource:\n\n#{@source.pack("U*")}"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
148
210
|
@pos += 1
|
149
211
|
@current_line += 1
|
150
212
|
end
|
@@ -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,12 @@ 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
|
+
|
35
|
+
sig { returns(T.nilable(String)) }
|
36
|
+
attr_reader :telemetry_machine_id
|
37
|
+
|
32
38
|
sig { void }
|
33
39
|
def initialize
|
34
40
|
@workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
|
@@ -53,6 +59,13 @@ module RubyLsp
|
|
53
59
|
)
|
54
60
|
@client_capabilities = T.let(ClientCapabilities.new, ClientCapabilities)
|
55
61
|
@enabled_feature_flags = T.let({}, T::Hash[Symbol, T::Boolean])
|
62
|
+
@mutex = T.let(Mutex.new, Mutex)
|
63
|
+
@telemetry_machine_id = T.let(nil, T.nilable(String))
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) }
|
67
|
+
def synchronize(&block)
|
68
|
+
@mutex.synchronize(&block)
|
56
69
|
end
|
57
70
|
|
58
71
|
sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
@@ -85,6 +98,8 @@ module RubyLsp
|
|
85
98
|
@workspace_uri = URI(workspace_uri) if workspace_uri
|
86
99
|
|
87
100
|
specified_formatter = options.dig(:initializationOptions, :formatter)
|
101
|
+
rubocop_has_addon = defined?(::RuboCop::Version::STRING) &&
|
102
|
+
Gem::Requirement.new(">= 1.70.0").satisfied_by?(Gem::Version.new(::RuboCop::Version::STRING))
|
88
103
|
|
89
104
|
if specified_formatter
|
90
105
|
@formatter = specified_formatter
|
@@ -92,6 +107,12 @@ module RubyLsp
|
|
92
107
|
if specified_formatter != "auto"
|
93
108
|
notifications << Notification.window_log_message("Using formatter specified by user: #{@formatter}")
|
94
109
|
end
|
110
|
+
|
111
|
+
# If the user had originally configured to use `rubocop`, but their version doesn't provide the add-on yet,
|
112
|
+
# fallback to the internal integration
|
113
|
+
if specified_formatter == "rubocop" && !rubocop_has_addon
|
114
|
+
@formatter = "rubocop_internal"
|
115
|
+
end
|
95
116
|
end
|
96
117
|
|
97
118
|
if @formatter == "auto"
|
@@ -100,6 +121,23 @@ module RubyLsp
|
|
100
121
|
end
|
101
122
|
|
102
123
|
specified_linters = options.dig(:initializationOptions, :linters)
|
124
|
+
|
125
|
+
if specified_formatter == "rubocop" || specified_linters&.include?("rubocop")
|
126
|
+
notifications << Notification.window_log_message(<<~MESSAGE, type: Constant::MessageType::WARNING)
|
127
|
+
Formatter is configured to be `rubocop`. As of RuboCop v1.70.0, this identifier activates the add-on
|
128
|
+
implemented in the rubocop gem itself instead of the internal integration provided by the Ruby LSP.
|
129
|
+
|
130
|
+
If you wish to use the internal integration, please configure the formatter as `rubocop_internal`.
|
131
|
+
MESSAGE
|
132
|
+
end
|
133
|
+
|
134
|
+
# If the user had originally configured to use `rubocop`, but their version doesn't provide the add-on yet,
|
135
|
+
# fall back to the internal integration
|
136
|
+
if specified_linters&.include?("rubocop") && !rubocop_has_addon
|
137
|
+
specified_linters.delete("rubocop")
|
138
|
+
specified_linters << "rubocop_internal"
|
139
|
+
end
|
140
|
+
|
103
141
|
@linters = specified_linters || detect_linters(direct_dependencies, all_dependencies)
|
104
142
|
|
105
143
|
notifications << if specified_linters
|
@@ -141,6 +179,7 @@ module RubyLsp
|
|
141
179
|
enabled_flags = options.dig(:initializationOptions, :enabledFeatureFlags)
|
142
180
|
@enabled_feature_flags = enabled_flags if enabled_flags
|
143
181
|
|
182
|
+
@telemetry_machine_id = options.dig(:initializationOptions, :telemetryMachineId)
|
144
183
|
notifications
|
145
184
|
end
|
146
185
|
|
@@ -176,13 +215,13 @@ module RubyLsp
|
|
176
215
|
sig { params(direct_dependencies: T::Array[String], all_dependencies: T::Array[String]).returns(String) }
|
177
216
|
def detect_formatter(direct_dependencies, all_dependencies)
|
178
217
|
# NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
|
179
|
-
return "
|
218
|
+
return "rubocop_internal" if direct_dependencies.any?(/^rubocop/)
|
180
219
|
|
181
220
|
syntax_tree_is_direct_dependency = direct_dependencies.include?("syntax_tree")
|
182
221
|
return "syntax_tree" if syntax_tree_is_direct_dependency
|
183
222
|
|
184
223
|
rubocop_is_transitive_dependency = all_dependencies.include?("rubocop")
|
185
|
-
return "
|
224
|
+
return "rubocop_internal" if dot_rubocop_yml_present && rubocop_is_transitive_dependency
|
186
225
|
|
187
226
|
"none"
|
188
227
|
end
|
@@ -194,7 +233,7 @@ module RubyLsp
|
|
194
233
|
linters = []
|
195
234
|
|
196
235
|
if dependencies.any?(/^rubocop/) || (all_dependencies.include?("rubocop") && dot_rubocop_yml_present)
|
197
|
-
linters << "
|
236
|
+
linters << "rubocop_internal"
|
198
237
|
end
|
199
238
|
|
200
239
|
linters
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -22,11 +22,12 @@ require "prism/visitor"
|
|
22
22
|
require "language_server-protocol"
|
23
23
|
require "rbs"
|
24
24
|
require "fileutils"
|
25
|
+
require "open3"
|
26
|
+
require "securerandom"
|
25
27
|
|
26
28
|
require "ruby-lsp"
|
27
29
|
require "ruby_lsp/base_server"
|
28
30
|
require "ruby_indexer/ruby_indexer"
|
29
|
-
require "core_ext/uri"
|
30
31
|
require "ruby_lsp/utils"
|
31
32
|
require "ruby_lsp/static_docs"
|
32
33
|
require "ruby_lsp/scope"
|
@@ -78,6 +79,7 @@ require "ruby_lsp/requests/hover"
|
|
78
79
|
require "ruby_lsp/requests/inlay_hints"
|
79
80
|
require "ruby_lsp/requests/on_type_formatting"
|
80
81
|
require "ruby_lsp/requests/prepare_type_hierarchy"
|
82
|
+
require "ruby_lsp/requests/prepare_rename"
|
81
83
|
require "ruby_lsp/requests/range_formatting"
|
82
84
|
require "ruby_lsp/requests/references"
|
83
85
|
require "ruby_lsp/requests/rename"
|
@@ -15,7 +15,7 @@ module RubyLsp
|
|
15
15
|
"bundle exec ruby"
|
16
16
|
rescue Bundler::GemfileNotFound
|
17
17
|
"ruby"
|
18
|
-
end
|
18
|
+
end,
|
19
19
|
String,
|
20
20
|
)
|
21
21
|
ACCESS_MODIFIERS = T.let([:public, :private, :protected], T::Array[Symbol])
|
@@ -198,7 +198,7 @@ module RubyLsp
|
|
198
198
|
|
199
199
|
@response_builder << create_code_lens(
|
200
200
|
node,
|
201
|
-
title: "Run",
|
201
|
+
title: "▶ Run",
|
202
202
|
command_name: "rubyLsp.runTest",
|
203
203
|
arguments: arguments,
|
204
204
|
data: { type: "test", **grouping_data },
|
@@ -206,7 +206,7 @@ module RubyLsp
|
|
206
206
|
|
207
207
|
@response_builder << create_code_lens(
|
208
208
|
node,
|
209
|
-
title: "Run In Terminal",
|
209
|
+
title: "▶ Run In Terminal",
|
210
210
|
command_name: "rubyLsp.runTestInTerminal",
|
211
211
|
arguments: arguments,
|
212
212
|
data: { type: "test_in_terminal", **grouping_data },
|
@@ -229,7 +229,11 @@ module RubyLsp
|
|
229
229
|
).returns(String)
|
230
230
|
end
|
231
231
|
def generate_test_command(group_stack: [], spec_name: nil, method_name: nil)
|
232
|
-
|
232
|
+
path = T.must(@path)
|
233
|
+
command = BASE_COMMAND
|
234
|
+
command += " -Itest" if File.fnmatch?("**/test/**/*", path, File::FNM_PATHNAME)
|
235
|
+
command += " -Ispec" if File.fnmatch?("**/spec/**/*", path, File::FNM_PATHNAME)
|
236
|
+
command += " #{path}"
|
233
237
|
|
234
238
|
case @global_state.test_library
|
235
239
|
when "minitest"
|
@@ -282,7 +286,7 @@ module RubyLsp
|
|
282
286
|
when Prism::StringNode
|
283
287
|
first_argument.content
|
284
288
|
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
285
|
-
constant_name(first_argument)
|
289
|
+
RubyIndexer::Index.constant_name(first_argument)
|
286
290
|
end
|
287
291
|
|
288
292
|
return unless name
|