ruby-lsp 0.23.15 → 0.26.9
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/VERSION +1 -1
- data/exe/ruby-lsp +17 -14
- data/exe/ruby-lsp-check +0 -4
- data/exe/ruby-lsp-launcher +41 -14
- data/exe/ruby-lsp-test-exec +6 -0
- data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +0 -1
- data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +0 -1
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +4 -3
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +42 -20
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +1 -7
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +49 -62
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +84 -74
- data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +6 -9
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +9 -14
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +12 -8
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +4 -4
- data/lib/ruby_lsp/addon.rb +44 -15
- data/lib/ruby_lsp/base_server.rb +56 -37
- data/lib/ruby_lsp/client_capabilities.rb +6 -1
- data/lib/ruby_lsp/document.rb +174 -62
- data/lib/ruby_lsp/erb_document.rb +10 -8
- data/lib/ruby_lsp/global_state.rb +86 -33
- data/lib/ruby_lsp/internal.rb +6 -3
- data/lib/ruby_lsp/listeners/completion.rb +22 -11
- data/lib/ruby_lsp/listeners/definition.rb +41 -21
- data/lib/ruby_lsp/listeners/document_highlight.rb +26 -1
- data/lib/ruby_lsp/listeners/document_link.rb +64 -28
- data/lib/ruby_lsp/listeners/hover.rb +27 -16
- data/lib/ruby_lsp/listeners/inlay_hints.rb +5 -3
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +2 -2
- data/lib/ruby_lsp/listeners/signature_help.rb +2 -2
- data/lib/ruby_lsp/listeners/spec_style.rb +155 -79
- data/lib/ruby_lsp/listeners/test_discovery.rb +39 -21
- data/lib/ruby_lsp/listeners/test_style.rb +75 -35
- data/lib/ruby_lsp/rbs_document.rb +3 -6
- data/lib/ruby_lsp/requests/code_action_resolve.rb +83 -58
- data/lib/ruby_lsp/requests/code_actions.rb +20 -5
- data/lib/ruby_lsp/requests/code_lens.rb +27 -6
- data/lib/ruby_lsp/requests/completion.rb +3 -3
- data/lib/ruby_lsp/requests/completion_resolve.rb +8 -6
- data/lib/ruby_lsp/requests/definition.rb +4 -7
- data/lib/ruby_lsp/requests/discover_tests.rb +2 -2
- data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
- data/lib/ruby_lsp/requests/document_link.rb +1 -1
- data/lib/ruby_lsp/requests/folding_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/go_to_relevant_file.rb +64 -12
- data/lib/ruby_lsp/requests/hover.rb +3 -6
- data/lib/ruby_lsp/requests/inlay_hints.rb +4 -4
- data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
- data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +10 -21
- data/lib/ruby_lsp/requests/rename.rb +9 -10
- data/lib/ruby_lsp/requests/request.rb +8 -8
- data/lib/ruby_lsp/requests/selection_ranges.rb +2 -2
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +2 -2
- data/lib/ruby_lsp/requests/signature_help.rb +2 -2
- data/lib/ruby_lsp/requests/support/annotation.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +9 -12
- data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
- data/lib/ruby_lsp/requests/support/package_url.rb +414 -0
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +7 -1
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -3
- data/lib/ruby_lsp/requests/support/source_uri.rb +7 -4
- data/lib/ruby_lsp/requests/support/test_item.rb +7 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +20 -12
- data/lib/ruby_lsp/response_builders/collection_response_builder.rb +1 -4
- data/lib/ruby_lsp/response_builders/document_symbol.rb +2 -3
- data/lib/ruby_lsp/response_builders/hover.rb +1 -4
- data/lib/ruby_lsp/response_builders/response_builder.rb +6 -7
- data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +4 -5
- data/lib/ruby_lsp/response_builders/signature_help.rb +1 -2
- data/lib/ruby_lsp/response_builders/test_collection.rb +29 -3
- data/lib/ruby_lsp/ruby_document.rb +14 -42
- data/lib/ruby_lsp/scripts/compose_bundle.rb +3 -3
- data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +3 -1
- data/lib/ruby_lsp/server.rb +173 -130
- data/lib/ruby_lsp/setup_bundler.rb +114 -47
- data/lib/ruby_lsp/static_docs.rb +1 -0
- data/lib/ruby_lsp/store.rb +6 -16
- data/lib/ruby_lsp/test_helper.rb +1 -4
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +121 -17
- data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +65 -25
- data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +16 -18
- data/lib/ruby_lsp/utils.rb +102 -13
- data/static_docs/break.md +103 -0
- metadata +8 -33
- data/lib/ruby_indexer/test/class_variables_test.rb +0 -140
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +0 -770
- data/lib/ruby_indexer/test/configuration_test.rb +0 -280
- data/lib/ruby_indexer/test/constant_test.rb +0 -402
- data/lib/ruby_indexer/test/enhancements_test.rb +0 -325
- data/lib/ruby_indexer/test/global_variable_test.rb +0 -49
- data/lib/ruby_indexer/test/index_test.rb +0 -2190
- data/lib/ruby_indexer/test/instance_variables_test.rb +0 -240
- data/lib/ruby_indexer/test/method_test.rb +0 -973
- data/lib/ruby_indexer/test/prefix_tree_test.rb +0 -150
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +0 -380
- data/lib/ruby_indexer/test/reference_finder_test.rb +0 -330
- data/lib/ruby_indexer/test/test_case.rb +0 -51
- data/lib/ruby_indexer/test/uri_test.rb +0 -85
- data/lib/ruby_lsp/load_sorbet.rb +0 -62
data/lib/ruby_lsp/document.rb
CHANGED
|
@@ -2,29 +2,16 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
module RubyLsp
|
|
5
|
+
# @abstract
|
|
6
|
+
#: [ParseResultType]
|
|
5
7
|
class Document
|
|
6
|
-
class
|
|
7
|
-
enums do
|
|
8
|
-
Ruby = new("ruby")
|
|
9
|
-
ERB = new("erb")
|
|
10
|
-
RBS = new("rbs")
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
extend T::Sig
|
|
15
|
-
extend T::Helpers
|
|
16
|
-
extend T::Generic
|
|
17
|
-
|
|
18
|
-
class LocationNotFoundError < StandardError; end
|
|
19
|
-
ParseResultType = type_member
|
|
8
|
+
class InvalidLocationError < StandardError; end
|
|
20
9
|
|
|
21
10
|
# This maximum number of characters for providing expensive features, like semantic highlighting and diagnostics.
|
|
22
11
|
# This is the same number used by the TypeScript extension in VS Code
|
|
23
12
|
MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES = 100_000
|
|
24
13
|
EMPTY_CACHE = Object.new.freeze #: Object
|
|
25
14
|
|
|
26
|
-
abstract!
|
|
27
|
-
|
|
28
15
|
#: ParseResultType
|
|
29
16
|
attr_reader :parse_result
|
|
30
17
|
|
|
@@ -56,8 +43,13 @@ module RubyLsp
|
|
|
56
43
|
@encoding = global_state.encoding #: Encoding
|
|
57
44
|
@uri = uri #: URI::Generic
|
|
58
45
|
@needs_parsing = true #: bool
|
|
59
|
-
@parse_result = T.unsafe(nil) #: ParseResultType
|
|
60
46
|
@last_edit = nil #: Edit?
|
|
47
|
+
|
|
48
|
+
# Workaround to be able to type parse_result properly. It is immediately set when invoking parse!
|
|
49
|
+
@parse_result = ( # rubocop:disable Style/RedundantParentheses
|
|
50
|
+
nil #: as untyped
|
|
51
|
+
) #: ParseResultType
|
|
52
|
+
|
|
61
53
|
parse!
|
|
62
54
|
end
|
|
63
55
|
|
|
@@ -66,8 +58,11 @@ module RubyLsp
|
|
|
66
58
|
self.class == other.class && uri == other.uri && @source == other.source
|
|
67
59
|
end
|
|
68
60
|
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
# @abstract
|
|
62
|
+
#: -> Symbol
|
|
63
|
+
def language_id
|
|
64
|
+
raise AbstractMethodInvokedError
|
|
65
|
+
end
|
|
71
66
|
|
|
72
67
|
#: [T] (String request_name) { (Document[ParseResultType] document) -> T } -> T
|
|
73
68
|
def cache_fetch(request_name, &block)
|
|
@@ -89,6 +84,11 @@ module RubyLsp
|
|
|
89
84
|
@cache[request_name]
|
|
90
85
|
end
|
|
91
86
|
|
|
87
|
+
#: (String request_name) -> void
|
|
88
|
+
def clear_cache(request_name)
|
|
89
|
+
@cache[request_name] = EMPTY_CACHE
|
|
90
|
+
end
|
|
91
|
+
|
|
92
92
|
#: (Array[Hash[Symbol, untyped]] edits, version: Integer) -> void
|
|
93
93
|
def push_edits(edits, version:)
|
|
94
94
|
edits.each do |edit|
|
|
@@ -120,11 +120,17 @@ module RubyLsp
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
# Returns `true` if the document was parsed and `false` if nothing needed parsing
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
# @abstract
|
|
124
|
+
#: -> bool
|
|
125
|
+
def parse!
|
|
126
|
+
raise AbstractMethodInvokedError
|
|
127
|
+
end
|
|
125
128
|
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
# @abstract
|
|
130
|
+
#: -> bool
|
|
131
|
+
def syntax_error?
|
|
132
|
+
raise AbstractMethodInvokedError
|
|
133
|
+
end
|
|
128
134
|
|
|
129
135
|
#: -> bool
|
|
130
136
|
def past_expensive_limit?
|
|
@@ -133,27 +139,28 @@ module RubyLsp
|
|
|
133
139
|
|
|
134
140
|
#: (Hash[Symbol, untyped] start_pos, ?Hash[Symbol, untyped]? end_pos) -> [Integer, Integer?]
|
|
135
141
|
def find_index_by_position(start_pos, end_pos = nil)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
[start_index, end_index]
|
|
141
|
-
end
|
|
142
|
+
scanner = create_scanner
|
|
143
|
+
start_index = scanner.find_char_position(start_pos)
|
|
144
|
+
end_index = scanner.find_char_position(end_pos) if end_pos
|
|
145
|
+
[start_index, end_index]
|
|
142
146
|
end
|
|
143
147
|
|
|
144
148
|
private
|
|
145
149
|
|
|
146
150
|
#: -> Scanner
|
|
147
151
|
def create_scanner
|
|
148
|
-
|
|
152
|
+
case @encoding
|
|
153
|
+
when Encoding::UTF_8
|
|
154
|
+
Utf8Scanner.new(@source)
|
|
155
|
+
when Encoding::UTF_16LE
|
|
156
|
+
Utf16Scanner.new(@source)
|
|
157
|
+
else
|
|
158
|
+
Utf32Scanner.new(@source)
|
|
159
|
+
end
|
|
149
160
|
end
|
|
150
161
|
|
|
162
|
+
# @abstract
|
|
151
163
|
class Edit
|
|
152
|
-
extend T::Sig
|
|
153
|
-
extend T::Helpers
|
|
154
|
-
|
|
155
|
-
abstract!
|
|
156
|
-
|
|
157
164
|
#: Hash[Symbol, untyped]
|
|
158
165
|
attr_reader :range
|
|
159
166
|
|
|
@@ -167,33 +174,114 @@ module RubyLsp
|
|
|
167
174
|
class Replace < Edit; end
|
|
168
175
|
class Delete < Edit; end
|
|
169
176
|
|
|
177
|
+
# Parent class for all position scanners. Scanners are used to translate a position given by the editor into a
|
|
178
|
+
# string index that we can use to find the right place in the document source. The logic for finding the correct
|
|
179
|
+
# index depends on the encoding negotiated with the editor, so we have different subclasses for each encoding.
|
|
180
|
+
# See https://microsoft.github.io/language-server-protocol/specification/#positionEncodingKind for more information
|
|
181
|
+
# @abstract
|
|
170
182
|
class Scanner
|
|
171
|
-
extend T::Sig
|
|
172
|
-
|
|
173
183
|
LINE_BREAK = 0x0A #: Integer
|
|
174
184
|
# After character 0xFFFF, UTF-16 considers characters to have length 2 and we have to account for that
|
|
175
185
|
SURROGATE_PAIR_START = 0xFFFF #: Integer
|
|
176
186
|
|
|
177
|
-
#:
|
|
178
|
-
def initialize
|
|
187
|
+
#: -> void
|
|
188
|
+
def initialize
|
|
179
189
|
@current_line = 0 #: Integer
|
|
180
190
|
@pos = 0 #: Integer
|
|
181
|
-
@source = source.codepoints #: Array[Integer]
|
|
182
|
-
@encoding = encoding
|
|
183
191
|
end
|
|
184
192
|
|
|
185
|
-
# Finds the character index inside the source string for a given line and column
|
|
193
|
+
# Finds the character index inside the source string for a given line and column. This method always returns the
|
|
194
|
+
# character index regardless of whether we are searching positions based on bytes, code units, or codepoints.
|
|
195
|
+
# @abstract
|
|
196
|
+
#: (Hash[Symbol, untyped] position) -> Integer
|
|
197
|
+
def find_char_position(position)
|
|
198
|
+
raise AbstractMethodInvokedError
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# For the UTF-8 encoding, positions correspond to bytes
|
|
203
|
+
class Utf8Scanner < Scanner
|
|
204
|
+
#: (String source) -> void
|
|
205
|
+
def initialize(source)
|
|
206
|
+
super()
|
|
207
|
+
@bytes = source.bytes #: Array[Integer]
|
|
208
|
+
@character_length = 0 #: Integer
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# @override
|
|
212
|
+
#: (Hash[Symbol, untyped] position) -> Integer
|
|
213
|
+
def find_char_position(position)
|
|
214
|
+
# Each group of bytes is a character. We advance based on the number of bytes to count how many full characters
|
|
215
|
+
# we have in the requested offset
|
|
216
|
+
until @current_line == position[:line]
|
|
217
|
+
byte = @bytes[@pos] #: Integer?
|
|
218
|
+
raise InvalidLocationError unless byte
|
|
219
|
+
|
|
220
|
+
until LINE_BREAK == byte
|
|
221
|
+
@pos += character_byte_length(byte)
|
|
222
|
+
@character_length += 1
|
|
223
|
+
byte = @bytes[@pos]
|
|
224
|
+
raise InvalidLocationError unless byte
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
@pos += 1
|
|
228
|
+
@character_length += 1
|
|
229
|
+
@current_line += 1
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# @character_length has the number of characters until the beginning of the line. We don't accumulate on it for
|
|
233
|
+
# the character part because locating the same position twice must return the same value
|
|
234
|
+
line_byte_offset = 0
|
|
235
|
+
line_characters = 0
|
|
236
|
+
|
|
237
|
+
while line_byte_offset < position[:character]
|
|
238
|
+
byte = @bytes[@pos + line_byte_offset] #: Integer?
|
|
239
|
+
raise InvalidLocationError unless byte
|
|
240
|
+
|
|
241
|
+
line_byte_offset += character_byte_length(byte)
|
|
242
|
+
line_characters += 1
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
@character_length + line_characters
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
private
|
|
249
|
+
|
|
250
|
+
#: (Integer) -> Integer
|
|
251
|
+
def character_byte_length(byte)
|
|
252
|
+
if byte < 0x80 # 1-byte character
|
|
253
|
+
1
|
|
254
|
+
elsif byte < 0xE0 # 2-byte character
|
|
255
|
+
2
|
|
256
|
+
elsif byte < 0xF0 # 3-byte character
|
|
257
|
+
3
|
|
258
|
+
else # 4-byte character
|
|
259
|
+
4
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# For the UTF-16 encoding, positions correspond to UTF-16 code units, which count characters beyond the surrogate
|
|
265
|
+
# pair as length 2
|
|
266
|
+
class Utf16Scanner < Scanner
|
|
267
|
+
#: (String) -> void
|
|
268
|
+
def initialize(source)
|
|
269
|
+
super()
|
|
270
|
+
@codepoints = source.codepoints #: Array[Integer]
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# @override
|
|
186
274
|
#: (Hash[Symbol, untyped] position) -> Integer
|
|
187
275
|
def find_char_position(position)
|
|
188
276
|
# Find the character index for the beginning of the requested line
|
|
189
277
|
until @current_line == position[:line]
|
|
190
|
-
|
|
191
|
-
|
|
278
|
+
codepoint = @codepoints[@pos] #: Integer?
|
|
279
|
+
raise InvalidLocationError unless codepoint
|
|
192
280
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
281
|
+
until LINE_BREAK == @codepoints[@pos]
|
|
282
|
+
@pos += 1
|
|
283
|
+
codepoint = @codepoints[@pos] #: Integer?
|
|
284
|
+
raise InvalidLocationError unless codepoint
|
|
197
285
|
end
|
|
198
286
|
|
|
199
287
|
@pos += 1
|
|
@@ -202,29 +290,53 @@ module RubyLsp
|
|
|
202
290
|
|
|
203
291
|
# The final position is the beginning of the line plus the requested column. If the encoding is UTF-16, we also
|
|
204
292
|
# need to adjust for surrogate pairs
|
|
205
|
-
|
|
293
|
+
line_characters = 0
|
|
294
|
+
line_code_units = 0
|
|
295
|
+
|
|
296
|
+
while line_code_units < position[:character]
|
|
297
|
+
code_point = @codepoints[@pos + line_characters]
|
|
298
|
+
raise InvalidLocationError unless code_point
|
|
299
|
+
|
|
300
|
+
line_code_units += if code_point > SURROGATE_PAIR_START
|
|
301
|
+
2 # Surrogate pair, so we skip the next code unit
|
|
302
|
+
else
|
|
303
|
+
1 # Single code unit character
|
|
304
|
+
end
|
|
206
305
|
|
|
207
|
-
|
|
208
|
-
requested_position -= utf_16_character_position_correction(@pos, requested_position)
|
|
306
|
+
line_characters += 1
|
|
209
307
|
end
|
|
210
308
|
|
|
211
|
-
|
|
309
|
+
@pos + line_characters
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# For the UTF-32 encoding, positions correspond directly to codepoints
|
|
314
|
+
class Utf32Scanner < Scanner
|
|
315
|
+
#: (String) -> void
|
|
316
|
+
def initialize(source)
|
|
317
|
+
super()
|
|
318
|
+
@codepoints = source.codepoints #: Array[Integer]
|
|
212
319
|
end
|
|
213
320
|
|
|
214
|
-
#
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
321
|
+
# @override
|
|
322
|
+
#: (Hash[Symbol, untyped] position) -> Integer
|
|
323
|
+
def find_char_position(position)
|
|
324
|
+
# Find the character index for the beginning of the requested line
|
|
325
|
+
until @current_line == position[:line]
|
|
326
|
+
codepoint = @codepoints[@pos] #: Integer?
|
|
327
|
+
raise InvalidLocationError unless codepoint
|
|
219
328
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
329
|
+
until LINE_BREAK == @codepoints[@pos]
|
|
330
|
+
@pos += 1
|
|
331
|
+
codepoint = @codepoints[@pos] #: Integer?
|
|
332
|
+
raise InvalidLocationError unless codepoint
|
|
333
|
+
end
|
|
223
334
|
|
|
224
|
-
|
|
335
|
+
@pos += 1
|
|
336
|
+
@current_line += 1
|
|
225
337
|
end
|
|
226
338
|
|
|
227
|
-
|
|
339
|
+
@pos + position[:character]
|
|
228
340
|
end
|
|
229
341
|
end
|
|
230
342
|
end
|
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
module RubyLsp
|
|
5
|
+
#: [ParseResultType = Prism::ParseLexResult]
|
|
5
6
|
class ERBDocument < Document
|
|
6
|
-
extend T::Generic
|
|
7
|
-
|
|
8
|
-
ParseResultType = type_member { { fixed: Prism::ParseResult } }
|
|
9
|
-
|
|
10
7
|
#: String
|
|
11
8
|
attr_reader :host_language_source
|
|
12
9
|
|
|
@@ -34,11 +31,16 @@ module RubyLsp
|
|
|
34
31
|
@host_language_source = scanner.host_language
|
|
35
32
|
# Use partial script to avoid syntax errors in ERB files where keywords may be used without the full context in
|
|
36
33
|
# which they will be evaluated
|
|
37
|
-
@parse_result = Prism.
|
|
34
|
+
@parse_result = Prism.parse_lex(scanner.ruby, partial_script: true)
|
|
38
35
|
@code_units_cache = @parse_result.code_units_cache(@encoding)
|
|
39
36
|
true
|
|
40
37
|
end
|
|
41
38
|
|
|
39
|
+
#: -> Prism::ProgramNode
|
|
40
|
+
def ast
|
|
41
|
+
@parse_result.value.first
|
|
42
|
+
end
|
|
43
|
+
|
|
42
44
|
# @override
|
|
43
45
|
#: -> bool
|
|
44
46
|
def syntax_error?
|
|
@@ -46,9 +48,9 @@ module RubyLsp
|
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
# @override
|
|
49
|
-
#: ->
|
|
51
|
+
#: -> Symbol
|
|
50
52
|
def language_id
|
|
51
|
-
|
|
53
|
+
:erb
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
#: (Hash[Symbol, untyped] position, ?node_types: Array[singleton(Prism::Node)]) -> NodeContext
|
|
@@ -56,7 +58,7 @@ module RubyLsp
|
|
|
56
58
|
char_position, _ = find_index_by_position(position)
|
|
57
59
|
|
|
58
60
|
RubyDocument.locate(
|
|
59
|
-
|
|
61
|
+
ast,
|
|
60
62
|
char_position,
|
|
61
63
|
code_units_cache: @code_units_cache,
|
|
62
64
|
node_types: node_types,
|
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
module RubyLsp
|
|
5
|
+
# Holds the detected value and the reason for detection
|
|
6
|
+
class DetectionResult
|
|
7
|
+
#: String
|
|
8
|
+
attr_reader :value
|
|
9
|
+
|
|
10
|
+
#: String
|
|
11
|
+
attr_reader :reason
|
|
12
|
+
|
|
13
|
+
#: (String value, String reason) -> void
|
|
14
|
+
def initialize(value, reason)
|
|
15
|
+
@value = value
|
|
16
|
+
@reason = reason
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
5
20
|
class GlobalState
|
|
6
21
|
#: String
|
|
7
22
|
attr_reader :test_library
|
|
@@ -56,6 +71,17 @@ module RubyLsp
|
|
|
56
71
|
@enabled_feature_flags = {} #: Hash[Symbol, bool]
|
|
57
72
|
@mutex = Mutex.new #: Mutex
|
|
58
73
|
@telemetry_machine_id = nil #: String?
|
|
74
|
+
@feature_configuration = {
|
|
75
|
+
inlayHint: RequestConfig.new({
|
|
76
|
+
enableAll: false,
|
|
77
|
+
implicitRescue: false,
|
|
78
|
+
implicitHashValue: false,
|
|
79
|
+
}),
|
|
80
|
+
codeLens: RequestConfig.new({
|
|
81
|
+
enableAll: false,
|
|
82
|
+
enableTestCodeLens: true,
|
|
83
|
+
}),
|
|
84
|
+
} #: Hash[Symbol, RequestConfig]
|
|
59
85
|
end
|
|
60
86
|
|
|
61
87
|
#: [T] { -> T } -> T
|
|
@@ -111,8 +137,11 @@ module RubyLsp
|
|
|
111
137
|
end
|
|
112
138
|
|
|
113
139
|
if @formatter == "auto"
|
|
114
|
-
|
|
115
|
-
|
|
140
|
+
formatter_result = detect_formatter(direct_dependencies, all_dependencies)
|
|
141
|
+
@formatter = formatter_result.value
|
|
142
|
+
notifications << Notification.window_log_message(
|
|
143
|
+
"Auto detected formatter: #{@formatter} (#{formatter_result.reason})",
|
|
144
|
+
)
|
|
116
145
|
end
|
|
117
146
|
|
|
118
147
|
specified_linters = options.dig(:initializationOptions, :linters)
|
|
@@ -133,21 +162,28 @@ module RubyLsp
|
|
|
133
162
|
specified_linters << "rubocop_internal"
|
|
134
163
|
end
|
|
135
164
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
Notification.window_log_message("Using linters specified by user: #{@linters.join(", ")}")
|
|
165
|
+
if specified_linters
|
|
166
|
+
@linters = specified_linters
|
|
167
|
+
notifications << Notification.window_log_message("Using linters specified by user: #{@linters.join(", ")}")
|
|
140
168
|
else
|
|
141
|
-
|
|
169
|
+
linter_results = detect_linters(direct_dependencies, all_dependencies)
|
|
170
|
+
@linters = linter_results.map(&:value)
|
|
171
|
+
linter_messages = linter_results.map { |r| "#{r.value} (#{r.reason})" }
|
|
172
|
+
notifications << Notification.window_log_message("Auto detected linters: #{linter_messages.join(", ")}")
|
|
142
173
|
end
|
|
143
174
|
|
|
144
|
-
|
|
145
|
-
|
|
175
|
+
test_library_result = detect_test_library(direct_dependencies)
|
|
176
|
+
@test_library = test_library_result.value
|
|
177
|
+
notifications << Notification.window_log_message(
|
|
178
|
+
"Detected test library: #{@test_library} (#{test_library_result.reason})",
|
|
179
|
+
)
|
|
146
180
|
|
|
147
|
-
|
|
148
|
-
|
|
181
|
+
typechecker_result = detect_typechecker(all_dependencies)
|
|
182
|
+
@has_type_checker = !typechecker_result.nil?
|
|
183
|
+
if typechecker_result
|
|
149
184
|
notifications << Notification.window_log_message(
|
|
150
|
-
"Ruby LSP detected this is a Sorbet project and will defer to the
|
|
185
|
+
"Ruby LSP detected this is a Sorbet project (#{typechecker_result.reason}) and will defer to the " \
|
|
186
|
+
"Sorbet LSP for some functionality",
|
|
151
187
|
)
|
|
152
188
|
end
|
|
153
189
|
|
|
@@ -175,9 +211,19 @@ module RubyLsp
|
|
|
175
211
|
@enabled_feature_flags = enabled_flags if enabled_flags
|
|
176
212
|
|
|
177
213
|
@telemetry_machine_id = options.dig(:initializationOptions, :telemetryMachineId)
|
|
214
|
+
|
|
215
|
+
options.dig(:initializationOptions, :featuresConfiguration)&.each do |feature_name, config|
|
|
216
|
+
@feature_configuration[feature_name]&.merge!(config)
|
|
217
|
+
end
|
|
218
|
+
|
|
178
219
|
notifications
|
|
179
220
|
end
|
|
180
221
|
|
|
222
|
+
#: (Symbol) -> RequestConfig?
|
|
223
|
+
def feature_configuration(feature_name)
|
|
224
|
+
@feature_configuration[feature_name]
|
|
225
|
+
end
|
|
226
|
+
|
|
181
227
|
#: (Symbol flag) -> bool?
|
|
182
228
|
def enabled_feature?(flag)
|
|
183
229
|
@enabled_feature_flags[:all] || @enabled_feature_flags[flag]
|
|
@@ -207,60 +253,67 @@ module RubyLsp
|
|
|
207
253
|
|
|
208
254
|
private
|
|
209
255
|
|
|
210
|
-
#: (Array[String] direct_dependencies, Array[String] all_dependencies) ->
|
|
256
|
+
#: (Array[String] direct_dependencies, Array[String] all_dependencies) -> DetectionResult
|
|
211
257
|
def detect_formatter(direct_dependencies, all_dependencies)
|
|
212
258
|
# NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
|
|
213
|
-
|
|
259
|
+
if direct_dependencies.any?(/^rubocop/)
|
|
260
|
+
return DetectionResult.new("rubocop_internal", "direct dependency matching /^rubocop/")
|
|
261
|
+
end
|
|
214
262
|
|
|
215
|
-
|
|
216
|
-
|
|
263
|
+
if direct_dependencies.include?("syntax_tree")
|
|
264
|
+
return DetectionResult.new("syntax_tree", "direct dependency")
|
|
265
|
+
end
|
|
217
266
|
|
|
218
|
-
|
|
219
|
-
|
|
267
|
+
if all_dependencies.include?("rubocop") && dot_rubocop_yml_present
|
|
268
|
+
return DetectionResult.new("rubocop_internal", "transitive dependency with .rubocop.yml present")
|
|
269
|
+
end
|
|
220
270
|
|
|
221
|
-
"none"
|
|
271
|
+
DetectionResult.new("none", "no formatter detected")
|
|
222
272
|
end
|
|
223
273
|
|
|
224
274
|
# Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
|
|
225
275
|
# single linter. To have multiple linters running, the user must configure them manually
|
|
226
|
-
#: (Array[String] dependencies, Array[String] all_dependencies) -> Array[
|
|
276
|
+
#: (Array[String] dependencies, Array[String] all_dependencies) -> Array[DetectionResult]
|
|
227
277
|
def detect_linters(dependencies, all_dependencies)
|
|
228
|
-
linters = []
|
|
278
|
+
linters = [] #: Array[DetectionResult]
|
|
229
279
|
|
|
230
|
-
if dependencies.any?(/^rubocop/)
|
|
231
|
-
linters << "rubocop_internal"
|
|
280
|
+
if dependencies.any?(/^rubocop/)
|
|
281
|
+
linters << DetectionResult.new("rubocop_internal", "direct dependency matching /^rubocop/")
|
|
282
|
+
elsif all_dependencies.include?("rubocop") && dot_rubocop_yml_present
|
|
283
|
+
linters << DetectionResult.new("rubocop_internal", "transitive dependency with .rubocop.yml present")
|
|
232
284
|
end
|
|
233
285
|
|
|
234
286
|
linters
|
|
235
287
|
end
|
|
236
288
|
|
|
237
|
-
#: (Array[String] dependencies) ->
|
|
289
|
+
#: (Array[String] dependencies) -> DetectionResult
|
|
238
290
|
def detect_test_library(dependencies)
|
|
239
291
|
if dependencies.any?(/^rspec/)
|
|
240
|
-
"rspec"
|
|
292
|
+
DetectionResult.new("rspec", "direct dependency matching /^rspec/")
|
|
241
293
|
# A Rails app may have a dependency on minitest, but we would instead want to use the Rails test runner provided
|
|
242
294
|
# by ruby-lsp-rails. A Rails app doesn't need to depend on the rails gem itself, individual components like
|
|
243
295
|
# activestorage may be added to the gemfile so that other components aren't downloaded. Check for the presence
|
|
244
296
|
# of bin/rails to support these cases.
|
|
245
297
|
elsif bin_rails_present
|
|
246
|
-
"rails"
|
|
298
|
+
DetectionResult.new("rails", "bin/rails present")
|
|
247
299
|
# NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
|
|
248
300
|
elsif dependencies.any?(/^minitest$/)
|
|
249
|
-
"minitest"
|
|
301
|
+
DetectionResult.new("minitest", "direct dependency matching /^minitest$/")
|
|
250
302
|
elsif dependencies.any?(/^test-unit/)
|
|
251
|
-
"test-unit"
|
|
303
|
+
DetectionResult.new("test-unit", "direct dependency matching /^test-unit/")
|
|
252
304
|
else
|
|
253
|
-
"unknown"
|
|
305
|
+
DetectionResult.new("unknown", "no test library detected")
|
|
254
306
|
end
|
|
255
307
|
end
|
|
256
308
|
|
|
257
|
-
#: (Array[String] dependencies) ->
|
|
309
|
+
#: (Array[String] dependencies) -> DetectionResult?
|
|
258
310
|
def detect_typechecker(dependencies)
|
|
259
|
-
return
|
|
311
|
+
return if ENV["RUBY_LSP_BYPASS_TYPECHECKER"]
|
|
312
|
+
return if dependencies.none?(/^sorbet-static/)
|
|
260
313
|
|
|
261
|
-
|
|
314
|
+
DetectionResult.new("sorbet", "sorbet-static in dependencies")
|
|
262
315
|
rescue Bundler::GemfileNotFound
|
|
263
|
-
|
|
316
|
+
nil
|
|
264
317
|
end
|
|
265
318
|
|
|
266
319
|
#: -> bool
|
data/lib/ruby_lsp/internal.rb
CHANGED
|
@@ -6,15 +6,18 @@
|
|
|
6
6
|
yarp_require_paths = Gem.loaded_specs["yarp"]&.full_require_paths
|
|
7
7
|
$LOAD_PATH.delete_if { |path| yarp_require_paths.include?(path) } if yarp_require_paths
|
|
8
8
|
|
|
9
|
-
require "sorbet-runtime"
|
|
10
|
-
|
|
11
9
|
# Set Bundler's UI level to silent as soon as possible to prevent any prints to STDOUT
|
|
12
10
|
require "bundler"
|
|
13
11
|
Bundler.ui.level = :silent
|
|
14
12
|
|
|
15
13
|
require "json"
|
|
16
14
|
require "uri"
|
|
17
|
-
require "cgi"
|
|
15
|
+
require "cgi/escape"
|
|
16
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.5")
|
|
17
|
+
# Just requiring `cgi/escape` leaves CGI.unescape broken on older rubies
|
|
18
|
+
# Some background on why this is necessary: https://bugs.ruby-lang.org/issues/21258
|
|
19
|
+
require "cgi/util"
|
|
20
|
+
end
|
|
18
21
|
require "set"
|
|
19
22
|
require "strscan"
|
|
20
23
|
require "prism"
|