ruby-lsp 0.23.11 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +10 -4
  5. data/exe/ruby-lsp-check +0 -4
  6. data/exe/ruby-lsp-launcher +45 -22
  7. data/exe/ruby-lsp-test-exec +6 -0
  8. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +1 -2
  9. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +3 -6
  10. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +82 -116
  11. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +140 -183
  12. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +10 -14
  13. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +107 -236
  14. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +166 -281
  15. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +4 -27
  16. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +23 -27
  17. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +25 -57
  18. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +58 -68
  19. data/lib/ruby_indexer/lib/ruby_indexer/uri.rb +17 -19
  20. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +7 -11
  21. data/lib/ruby_indexer/test/class_variables_test.rb +14 -14
  22. data/lib/ruby_indexer/test/classes_and_modules_test.rb +65 -40
  23. data/lib/ruby_indexer/test/configuration_test.rb +49 -9
  24. data/lib/ruby_indexer/test/constant_test.rb +34 -34
  25. data/lib/ruby_indexer/test/enhancements_test.rb +1 -1
  26. data/lib/ruby_indexer/test/index_test.rb +185 -135
  27. data/lib/ruby_indexer/test/instance_variables_test.rb +61 -37
  28. data/lib/ruby_indexer/test/method_test.rb +166 -123
  29. data/lib/ruby_indexer/test/prefix_tree_test.rb +21 -21
  30. data/lib/ruby_indexer/test/rbs_indexer_test.rb +70 -75
  31. data/lib/ruby_indexer/test/reference_finder_test.rb +79 -14
  32. data/lib/ruby_indexer/test/test_case.rb +9 -3
  33. data/lib/ruby_indexer/test/uri_test.rb +15 -2
  34. data/lib/ruby_lsp/addon.rb +88 -86
  35. data/lib/ruby_lsp/base_server.rb +59 -54
  36. data/lib/ruby_lsp/client_capabilities.rb +16 -13
  37. data/lib/ruby_lsp/document.rb +205 -104
  38. data/lib/ruby_lsp/erb_document.rb +45 -47
  39. data/lib/ruby_lsp/global_state.rb +73 -57
  40. data/lib/ruby_lsp/internal.rb +8 -3
  41. data/lib/ruby_lsp/listeners/code_lens.rb +82 -89
  42. data/lib/ruby_lsp/listeners/completion.rb +81 -76
  43. data/lib/ruby_lsp/listeners/definition.rb +44 -58
  44. data/lib/ruby_lsp/listeners/document_highlight.rb +123 -150
  45. data/lib/ruby_lsp/listeners/document_link.rb +50 -70
  46. data/lib/ruby_lsp/listeners/document_symbol.rb +38 -52
  47. data/lib/ruby_lsp/listeners/folding_ranges.rb +40 -43
  48. data/lib/ruby_lsp/listeners/hover.rb +107 -115
  49. data/lib/ruby_lsp/listeners/inlay_hints.rb +8 -13
  50. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +54 -56
  51. data/lib/ruby_lsp/listeners/signature_help.rb +12 -27
  52. data/lib/ruby_lsp/listeners/spec_style.rb +214 -0
  53. data/lib/ruby_lsp/listeners/test_discovery.rb +92 -0
  54. data/lib/ruby_lsp/listeners/test_style.rb +205 -95
  55. data/lib/ruby_lsp/node_context.rb +12 -39
  56. data/lib/ruby_lsp/rbs_document.rb +10 -11
  57. data/lib/ruby_lsp/requests/code_action_resolve.rb +65 -61
  58. data/lib/ruby_lsp/requests/code_actions.rb +14 -26
  59. data/lib/ruby_lsp/requests/code_lens.rb +31 -21
  60. data/lib/ruby_lsp/requests/completion.rb +8 -21
  61. data/lib/ruby_lsp/requests/completion_resolve.rb +6 -6
  62. data/lib/ruby_lsp/requests/definition.rb +8 -20
  63. data/lib/ruby_lsp/requests/diagnostics.rb +8 -11
  64. data/lib/ruby_lsp/requests/discover_tests.rb +20 -7
  65. data/lib/ruby_lsp/requests/document_highlight.rb +6 -16
  66. data/lib/ruby_lsp/requests/document_link.rb +6 -17
  67. data/lib/ruby_lsp/requests/document_symbol.rb +5 -8
  68. data/lib/ruby_lsp/requests/folding_ranges.rb +7 -15
  69. data/lib/ruby_lsp/requests/formatting.rb +6 -9
  70. data/lib/ruby_lsp/requests/go_to_relevant_file.rb +85 -0
  71. data/lib/ruby_lsp/requests/hover.rb +12 -25
  72. data/lib/ruby_lsp/requests/inlay_hints.rb +8 -19
  73. data/lib/ruby_lsp/requests/on_type_formatting.rb +32 -40
  74. data/lib/ruby_lsp/requests/prepare_rename.rb +5 -10
  75. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +5 -15
  76. data/lib/ruby_lsp/requests/range_formatting.rb +5 -6
  77. data/lib/ruby_lsp/requests/references.rb +17 -57
  78. data/lib/ruby_lsp/requests/rename.rb +27 -51
  79. data/lib/ruby_lsp/requests/request.rb +13 -25
  80. data/lib/ruby_lsp/requests/selection_ranges.rb +7 -7
  81. data/lib/ruby_lsp/requests/semantic_highlighting.rb +16 -35
  82. data/lib/ruby_lsp/requests/show_syntax_tree.rb +7 -8
  83. data/lib/ruby_lsp/requests/signature_help.rb +9 -27
  84. data/lib/ruby_lsp/requests/support/annotation.rb +4 -10
  85. data/lib/ruby_lsp/requests/support/common.rb +16 -58
  86. data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
  87. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +27 -35
  88. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +13 -16
  89. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +34 -36
  90. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -3
  91. data/lib/ruby_lsp/requests/support/sorbet.rb +29 -38
  92. data/lib/ruby_lsp/requests/support/source_uri.rb +20 -32
  93. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +12 -19
  94. data/lib/ruby_lsp/requests/support/test_item.rb +16 -14
  95. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +5 -6
  96. data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -4
  97. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +6 -9
  98. data/lib/ruby_lsp/response_builders/document_symbol.rb +15 -21
  99. data/lib/ruby_lsp/response_builders/hover.rb +12 -18
  100. data/lib/ruby_lsp/response_builders/response_builder.rb +6 -7
  101. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +62 -91
  102. data/lib/ruby_lsp/response_builders/signature_help.rb +6 -8
  103. data/lib/ruby_lsp/response_builders/test_collection.rb +35 -13
  104. data/lib/ruby_lsp/ruby_document.rb +32 -98
  105. data/lib/ruby_lsp/scope.rb +7 -11
  106. data/lib/ruby_lsp/scripts/compose_bundle.rb +6 -4
  107. data/lib/ruby_lsp/server.rb +303 -196
  108. data/lib/ruby_lsp/setup_bundler.rb +121 -82
  109. data/lib/ruby_lsp/static_docs.rb +12 -7
  110. data/lib/ruby_lsp/store.rb +21 -49
  111. data/lib/ruby_lsp/test_helper.rb +3 -16
  112. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +233 -0
  113. data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +145 -0
  114. data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +92 -0
  115. data/lib/ruby_lsp/type_inferrer.rb +13 -14
  116. data/lib/ruby_lsp/utils.rb +138 -93
  117. data/static_docs/break.md +103 -0
  118. metadata +14 -20
  119. data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -2,80 +2,69 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
+ # @abstract
6
+ #: [ParseResultType]
5
7
  class Document
6
- class LanguageId < T::Enum
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
- EMPTY_CACHE = T.let(Object.new.freeze, Object)
13
+ EMPTY_CACHE = Object.new.freeze #: Object
25
14
 
26
- abstract!
27
-
28
- sig { returns(ParseResultType) }
15
+ #: ParseResultType
29
16
  attr_reader :parse_result
30
17
 
31
- sig { returns(String) }
18
+ #: String
32
19
  attr_reader :source
33
20
 
34
- sig { returns(Integer) }
21
+ #: Integer
35
22
  attr_reader :version
36
23
 
37
- sig { returns(URI::Generic) }
24
+ #: URI::Generic
38
25
  attr_reader :uri
39
26
 
40
- sig { returns(Encoding) }
27
+ #: Encoding
41
28
  attr_reader :encoding
42
29
 
43
- sig { returns(T.nilable(Edit)) }
30
+ #: Edit?
44
31
  attr_reader :last_edit
45
32
 
46
- sig { returns(T.any(Interface::SemanticTokens, Object)) }
33
+ #: (Interface::SemanticTokens | Object)
47
34
  attr_accessor :semantic_tokens
48
35
 
49
- sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
36
+ #: (source: String, version: Integer, uri: URI::Generic, global_state: GlobalState) -> void
50
37
  def initialize(source:, version:, uri:, global_state:)
51
38
  @source = source
52
39
  @version = version
53
40
  @global_state = global_state
54
- @cache = T.let(Hash.new(EMPTY_CACHE), T::Hash[String, T.untyped])
55
- @semantic_tokens = T.let(EMPTY_CACHE, T.any(Interface::SemanticTokens, Object))
56
- @encoding = T.let(global_state.encoding, Encoding)
57
- @uri = T.let(uri, URI::Generic)
58
- @needs_parsing = T.let(true, T::Boolean)
59
- @parse_result = T.let(T.unsafe(nil), ParseResultType)
60
- @last_edit = T.let(nil, T.nilable(Edit))
41
+ @cache = Hash.new(EMPTY_CACHE) #: Hash[String, untyped]
42
+ @semantic_tokens = EMPTY_CACHE #: (Interface::SemanticTokens | Object)
43
+ @encoding = global_state.encoding #: Encoding
44
+ @uri = uri #: URI::Generic
45
+ @needs_parsing = true #: bool
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
 
64
- sig { params(other: Document[T.untyped]).returns(T::Boolean) }
56
+ #: (Document[untyped] other) -> bool
65
57
  def ==(other)
66
58
  self.class == other.class && uri == other.uri && @source == other.source
67
59
  end
68
60
 
69
- sig { abstract.returns(LanguageId) }
70
- def language_id; end
71
-
72
- sig do
73
- type_parameters(:T)
74
- .params(
75
- request_name: String,
76
- block: T.proc.params(document: Document[ParseResultType]).returns(T.type_parameter(:T)),
77
- ).returns(T.type_parameter(:T))
61
+ # @abstract
62
+ #: -> Symbol
63
+ def language_id
64
+ raise AbstractMethodInvokedError
78
65
  end
66
+
67
+ #: [T] (String request_name) { (Document[ParseResultType] document) -> T } -> T
79
68
  def cache_fetch(request_name, &block)
80
69
  cached = @cache[request_name]
81
70
  return cached if cached != EMPTY_CACHE
@@ -85,17 +74,22 @@ module RubyLsp
85
74
  result
86
75
  end
87
76
 
88
- sig { type_parameters(:T).params(request_name: String, value: T.type_parameter(:T)).returns(T.type_parameter(:T)) }
77
+ #: [T] (String request_name, T value) -> T
89
78
  def cache_set(request_name, value)
90
79
  @cache[request_name] = value
91
80
  end
92
81
 
93
- sig { params(request_name: String).returns(T.untyped) }
82
+ #: (String request_name) -> untyped
94
83
  def cache_get(request_name)
95
84
  @cache[request_name]
96
85
  end
97
86
 
98
- sig { params(edits: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).void }
87
+ #: (String request_name) -> void
88
+ def clear_cache(request_name)
89
+ @cache[request_name] = EMPTY_CACHE
90
+ end
91
+
92
+ #: (Array[Hash[Symbol, untyped]] edits, version: Integer) -> void
99
93
  def push_edits(edits, version:)
100
94
  edits.each do |edit|
101
95
  range = edit[:range]
@@ -126,49 +120,51 @@ module RubyLsp
126
120
  end
127
121
 
128
122
  # Returns `true` if the document was parsed and `false` if nothing needed parsing
129
- sig { abstract.returns(T::Boolean) }
130
- def parse!; end
123
+ # @abstract
124
+ #: -> bool
125
+ def parse!
126
+ raise AbstractMethodInvokedError
127
+ end
131
128
 
132
- sig { abstract.returns(T::Boolean) }
133
- def syntax_error?; end
129
+ # @abstract
130
+ #: -> bool
131
+ def syntax_error?
132
+ raise AbstractMethodInvokedError
133
+ end
134
134
 
135
- sig { returns(T::Boolean) }
135
+ #: -> bool
136
136
  def past_expensive_limit?
137
137
  @source.length > MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES
138
138
  end
139
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
140
+ #: (Hash[Symbol, untyped] start_pos, ?Hash[Symbol, untyped]? end_pos) -> [Integer, Integer?]
146
141
  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
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]
153
146
  end
154
147
 
155
148
  private
156
149
 
157
- sig { returns(Scanner) }
150
+ #: -> Scanner
158
151
  def create_scanner
159
- Scanner.new(@source, @encoding)
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
160
160
  end
161
161
 
162
+ # @abstract
162
163
  class Edit
163
- extend T::Sig
164
- extend T::Helpers
165
-
166
- abstract!
167
-
168
- sig { returns(T::Hash[Symbol, T.untyped]) }
164
+ #: Hash[Symbol, untyped]
169
165
  attr_reader :range
170
166
 
171
- sig { params(range: T::Hash[Symbol, T.untyped]).void }
167
+ #: (Hash[Symbol, untyped] range) -> void
172
168
  def initialize(range)
173
169
  @range = range
174
170
  end
@@ -178,33 +174,114 @@ module RubyLsp
178
174
  class Replace < Edit; end
179
175
  class Delete < Edit; end
180
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
181
182
  class Scanner
182
- extend T::Sig
183
-
184
- LINE_BREAK = T.let(0x0A, Integer)
183
+ LINE_BREAK = 0x0A #: Integer
185
184
  # After character 0xFFFF, UTF-16 considers characters to have length 2 and we have to account for that
186
- SURROGATE_PAIR_START = T.let(0xFFFF, Integer)
187
-
188
- sig { params(source: String, encoding: Encoding).void }
189
- def initialize(source, encoding)
190
- @current_line = T.let(0, Integer)
191
- @pos = T.let(0, Integer)
192
- @source = T.let(source.codepoints, T::Array[Integer])
193
- @encoding = encoding
185
+ SURROGATE_PAIR_START = 0xFFFF #: Integer
186
+
187
+ #: -> void
188
+ def initialize
189
+ @current_line = 0 #: Integer
190
+ @pos = 0 #: Integer
191
+ end
192
+
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]
194
271
  end
195
272
 
196
- # Finds the character index inside the source string for a given line and column
197
- sig { params(position: T::Hash[Symbol, T.untyped]).returns(Integer) }
273
+ # @override
274
+ #: (Hash[Symbol, untyped] position) -> Integer
198
275
  def find_char_position(position)
199
276
  # Find the character index for the beginning of the requested line
200
277
  until @current_line == position[:line]
201
- until LINE_BREAK == @source[@pos]
202
- @pos += 1
278
+ codepoint = @codepoints[@pos] #: Integer?
279
+ raise InvalidLocationError unless codepoint
203
280
 
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
281
+ until LINE_BREAK == @codepoints[@pos]
282
+ @pos += 1
283
+ codepoint = @codepoints[@pos] #: Integer?
284
+ raise InvalidLocationError unless codepoint
208
285
  end
209
286
 
210
287
  @pos += 1
@@ -213,29 +290,53 @@ module RubyLsp
213
290
 
214
291
  # The final position is the beginning of the line plus the requested column. If the encoding is UTF-16, we also
215
292
  # need to adjust for surrogate pairs
216
- requested_position = @pos + position[:character]
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
217
305
 
218
- if @encoding == Encoding::UTF_16LE
219
- requested_position -= utf_16_character_position_correction(@pos, requested_position)
306
+ line_characters += 1
220
307
  end
221
308
 
222
- requested_position
309
+ @pos + line_characters
223
310
  end
311
+ end
224
312
 
225
- # Subtract 1 for each character after 0xFFFF in the current line from the column position, so that we hit the
226
- # right character in the UTF-8 representation
227
- sig { params(current_position: Integer, requested_position: Integer).returns(Integer) }
228
- def utf_16_character_position_correction(current_position, requested_position)
229
- utf16_unicode_correction = 0
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]
319
+ end
230
320
 
231
- until current_position == requested_position
232
- codepoint = @source[current_position]
233
- utf16_unicode_correction += 1 if codepoint && codepoint > SURROGATE_PAIR_START
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
234
328
 
235
- current_position += 1
329
+ until LINE_BREAK == @codepoints[@pos]
330
+ @pos += 1
331
+ codepoint = @codepoints[@pos] #: Integer?
332
+ raise InvalidLocationError unless codepoint
333
+ end
334
+
335
+ @pos += 1
336
+ @current_line += 1
236
337
  end
237
338
 
238
- utf16_unicode_correction
339
+ @pos + position[:character]
239
340
  end
240
341
  end
241
342
  end
@@ -2,36 +2,26 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
+ #: [ParseResultType = Prism::ParseLexResult]
5
6
  class ERBDocument < Document
6
- extend T::Sig
7
- extend T::Generic
8
-
9
- ParseResultType = type_member { { fixed: Prism::ParseResult } }
10
-
11
- sig { returns(String) }
7
+ #: String
12
8
  attr_reader :host_language_source
13
9
 
14
- sig do
15
- returns(T.any(
16
- T.proc.params(arg0: Integer).returns(Integer),
17
- Prism::CodeUnitsCache,
18
- ))
19
- end
10
+ #: (^(Integer arg0) -> Integer | Prism::CodeUnitsCache)
20
11
  attr_reader :code_units_cache
21
12
 
22
- sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
13
+ #: (source: String, version: Integer, uri: URI::Generic, global_state: GlobalState) -> void
23
14
  def initialize(source:, version:, uri:, global_state:)
24
15
  # This has to be initialized before calling super because we call `parse` in the parent constructor, which
25
16
  # overrides this with the proper virtual host language source
26
- @host_language_source = T.let("", String)
17
+ @host_language_source = "" #: String
27
18
  super
28
- @code_units_cache = T.let(@parse_result.code_units_cache(@encoding), T.any(
29
- T.proc.params(arg0: Integer).returns(Integer),
30
- Prism::CodeUnitsCache,
31
- ))
19
+ @code_units_cache =
20
+ @parse_result.code_units_cache(@encoding) #: (^(Integer arg0) -> Integer | Prism::CodeUnitsCache)
32
21
  end
33
22
 
34
- sig { override.returns(T::Boolean) }
23
+ # @override
24
+ #: -> bool
35
25
  def parse!
36
26
  return false unless @needs_parsing
37
27
 
@@ -41,60 +31,60 @@ module RubyLsp
41
31
  @host_language_source = scanner.host_language
42
32
  # Use partial script to avoid syntax errors in ERB files where keywords may be used without the full context in
43
33
  # which they will be evaluated
44
- @parse_result = Prism.parse(scanner.ruby, partial_script: true)
34
+ @parse_result = Prism.parse_lex(scanner.ruby, partial_script: true)
45
35
  @code_units_cache = @parse_result.code_units_cache(@encoding)
46
36
  true
47
37
  end
48
38
 
49
- sig { override.returns(T::Boolean) }
39
+ #: -> Prism::ProgramNode
40
+ def ast
41
+ @parse_result.value.first
42
+ end
43
+
44
+ # @override
45
+ #: -> bool
50
46
  def syntax_error?
51
47
  @parse_result.failure?
52
48
  end
53
49
 
54
- sig { override.returns(LanguageId) }
50
+ # @override
51
+ #: -> Symbol
55
52
  def language_id
56
- LanguageId::ERB
53
+ :erb
57
54
  end
58
55
 
59
- sig do
60
- params(
61
- position: T::Hash[Symbol, T.untyped],
62
- node_types: T::Array[T.class_of(Prism::Node)],
63
- ).returns(NodeContext)
64
- end
56
+ #: (Hash[Symbol, untyped] position, ?node_types: Array[singleton(Prism::Node)]) -> NodeContext
65
57
  def locate_node(position, node_types: [])
66
58
  char_position, _ = find_index_by_position(position)
67
59
 
68
60
  RubyDocument.locate(
69
- @parse_result.value,
61
+ ast,
70
62
  char_position,
71
63
  code_units_cache: @code_units_cache,
72
64
  node_types: node_types,
73
65
  )
74
66
  end
75
67
 
76
- sig { params(char_position: Integer).returns(T.nilable(T::Boolean)) }
68
+ #: (Integer char_position) -> bool?
77
69
  def inside_host_language?(char_position)
78
70
  char = @host_language_source[char_position]
79
71
  char && char != " "
80
72
  end
81
73
 
82
74
  class ERBScanner
83
- extend T::Sig
84
-
85
- sig { returns(String) }
75
+ #: String
86
76
  attr_reader :ruby, :host_language
87
77
 
88
- sig { params(source: String).void }
78
+ #: (String source) -> void
89
79
  def initialize(source)
90
80
  @source = source
91
- @host_language = T.let(+"", String)
92
- @ruby = T.let(+"", String)
93
- @current_pos = T.let(0, Integer)
94
- @inside_ruby = T.let(false, T::Boolean)
81
+ @host_language = +"" #: String
82
+ @ruby = +"" #: String
83
+ @current_pos = 0 #: Integer
84
+ @inside_ruby = false #: bool
95
85
  end
96
86
 
97
- sig { void }
87
+ #: -> void
98
88
  def scan
99
89
  while @current_pos < @source.length
100
90
  scan_char
@@ -104,7 +94,7 @@ module RubyLsp
104
94
 
105
95
  private
106
96
 
107
- sig { void }
97
+ #: -> void
108
98
  def scan_char
109
99
  char = @source[@current_pos]
110
100
 
@@ -123,7 +113,9 @@ module RubyLsp
123
113
  push_char(" ")
124
114
  end
125
115
  else
126
- push_char(T.must(char))
116
+ push_char(
117
+ char, #: as !nil
118
+ )
127
119
  end
128
120
  when "-"
129
121
  if @inside_ruby && next_char == "%" &&
@@ -132,7 +124,9 @@ module RubyLsp
132
124
  push_char(" ")
133
125
  @inside_ruby = false
134
126
  else
135
- push_char(T.must(char))
127
+ push_char(
128
+ char, #: as !nil
129
+ )
136
130
  end
137
131
  when "%"
138
132
  if @inside_ruby && next_char == ">"
@@ -140,7 +134,9 @@ module RubyLsp
140
134
  @current_pos += 1
141
135
  push_char(" ")
142
136
  else
143
- push_char(T.must(char))
137
+ push_char(
138
+ char, #: as !nil
139
+ )
144
140
  end
145
141
  when "\r"
146
142
  @ruby << char
@@ -155,11 +151,13 @@ module RubyLsp
155
151
  @ruby << char
156
152
  @host_language << char
157
153
  else
158
- push_char(T.must(char))
154
+ push_char(
155
+ char, #: as !nil
156
+ )
159
157
  end
160
158
  end
161
159
 
162
- sig { params(char: String).void }
160
+ #: (String char) -> void
163
161
  def push_char(char)
164
162
  if @inside_ruby
165
163
  @ruby << char
@@ -170,7 +168,7 @@ module RubyLsp
170
168
  end
171
169
  end
172
170
 
173
- sig { returns(String) }
171
+ #: -> String
174
172
  def next_char
175
173
  @source[@current_pos + 1] || ""
176
174
  end