ruby-lsp 0.23.23 → 0.24.0

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp-launcher +7 -2
  4. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +7 -1
  5. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +1 -4
  6. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +6 -18
  7. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +16 -5
  8. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +12 -8
  9. data/lib/ruby_indexer/test/index_test.rb +24 -0
  10. data/lib/ruby_indexer/test/instance_variables_test.rb +24 -0
  11. data/lib/ruby_indexer/test/method_test.rb +17 -0
  12. data/lib/ruby_indexer/test/reference_finder_test.rb +79 -14
  13. data/lib/ruby_lsp/addon.rb +9 -9
  14. data/lib/ruby_lsp/base_server.rb +5 -7
  15. data/lib/ruby_lsp/document.rb +36 -25
  16. data/lib/ruby_lsp/erb_document.rb +8 -3
  17. data/lib/ruby_lsp/listeners/completion.rb +9 -1
  18. data/lib/ruby_lsp/listeners/spec_style.rb +7 -8
  19. data/lib/ruby_lsp/listeners/test_discovery.rb +18 -15
  20. data/lib/ruby_lsp/listeners/test_style.rb +7 -8
  21. data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -2
  22. data/lib/ruby_lsp/requests/completion.rb +1 -1
  23. data/lib/ruby_lsp/requests/definition.rb +1 -1
  24. data/lib/ruby_lsp/requests/discover_tests.rb +2 -2
  25. data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
  26. data/lib/ruby_lsp/requests/hover.rb +1 -1
  27. data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
  28. data/lib/ruby_lsp/requests/references.rb +6 -2
  29. data/lib/ruby_lsp/requests/rename.rb +8 -6
  30. data/lib/ruby_lsp/requests/request.rb +3 -6
  31. data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
  32. data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -1
  33. data/lib/ruby_lsp/requests/signature_help.rb +1 -1
  34. data/lib/ruby_lsp/requests/support/common.rb +1 -3
  35. data/lib/ruby_lsp/requests/support/formatter.rb +8 -13
  36. data/lib/ruby_lsp/response_builders/response_builder.rb +3 -5
  37. data/lib/ruby_lsp/ruby_document.rb +9 -4
  38. data/lib/ruby_lsp/server.rb +9 -30
  39. data/lib/ruby_lsp/setup_bundler.rb +3 -3
  40. data/lib/ruby_lsp/test_helper.rb +1 -4
  41. data/lib/ruby_lsp/utils.rb +3 -6
  42. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2079a33c4ec7e87a6b90bcb3cc11eca81e0d2a9c6d591c2fc14b07234b56c60b
4
- data.tar.gz: 2837a0acd68b11e065236fb3190baee2e358520694710676fe214e125ae74902
3
+ metadata.gz: 575c7b2510306819184c495560cc9bf9dee6dd9151cbeabe0ba3a8085996f58d
4
+ data.tar.gz: 0f714d35b90754c5c37672fef82a75ab5e0a2358ba821c397a3191a28518c9d3
5
5
  SHA512:
6
- metadata.gz: fd82d565032cf65414ea926313dfbb7be1c5a21c1d49f9191ed6b9d3c6f4d28458db21f78f14d37af03b84223dc40e4d8cb4c24a1a4a754359757048d84e930a
7
- data.tar.gz: 48c1dd2271b23e585c37a941db30729d727ceaffb7c12c1b3f806a138834192b9073df72c83bec48a7015bad7d2b109e08f591dc0a69767c39a30c8a6bd96c94
6
+ metadata.gz: f909d33820859b7e6a6be0e75345b00397a0717fdc5f63cfddf84b7277e7420469439f2a8d48561b96ae20f91bcbfd9ee9444522ec11e9da4611ac6371aeb52a
7
+ data.tar.gz: 866291dfe01240d91b1e8adfc74cc16482a5ddf9c68fc763f6869145e78ca4ea35f47f0e3727b1c1d930cc140e60e3e23c979bf68fc97a26eb6b2cd45c9403b4
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.23.23
1
+ 0.24.0
@@ -83,8 +83,13 @@ begin
83
83
  # This Marshal load can only happen after requiring Bundler because it will load a custom error class from Bundler
84
84
  # itself. If we try to load before requiring, the class will not be defined and loading will fail
85
85
  error_path = File.join(".ruby-lsp", "install_error")
86
- install_error = if File.exist?(error_path)
87
- Marshal.load(File.read(error_path))
86
+ install_error = begin
87
+ Marshal.load(File.read(error_path)) if File.exist?(error_path)
88
+ rescue ArgumentError
89
+ # The class we tried to load is not defined. This might happen when the user upgrades Bundler and new error
90
+ # classes are introduced or removed
91
+ File.delete(error_path)
92
+ nil
88
93
  end
89
94
 
90
95
  Bundler.setup
@@ -9,7 +9,7 @@ module RubyIndexer
9
9
  #: Array[String]
10
10
  attr_reader :indexing_errors
11
11
 
12
- #: (Index index, Prism::Dispatcher dispatcher, Prism::ParseResult parse_result, URI::Generic uri, ?collect_comments: bool) -> void
12
+ #: (Index index, Prism::Dispatcher dispatcher, Prism::ParseLexResult | Prism::ParseResult parse_result, URI::Generic uri, ?collect_comments: bool) -> void
13
13
  def initialize(index, dispatcher, parse_result, uri, collect_comments: false)
14
14
  @index = index
15
15
  @uri = uri
@@ -260,6 +260,9 @@ module RubyIndexer
260
260
  handle_attribute(node, reader: false, writer: true)
261
261
  when :attr_accessor
262
262
  handle_attribute(node, reader: true, writer: true)
263
+ when :attr
264
+ has_writer = node.arguments&.arguments&.last&.is_a?(Prism::TrueNode) || false
265
+ handle_attribute(node, reader: true, writer: has_writer)
263
266
  when :alias_method
264
267
  handle_alias_method(node)
265
268
  when :include, :prepend, :extend
@@ -726,6 +729,9 @@ module RubyIndexer
726
729
  comment = @comments_by_line[line]
727
730
  break unless comment
728
731
 
732
+ # a trailing comment from a previous line is not a comment for this node
733
+ break if comment.trailing?
734
+
729
735
  comment_content = comment.location.slice
730
736
 
731
737
  # invalid encodings would raise an "invalid byte sequence" exception
@@ -2,11 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyIndexer
5
+ # @abstract
5
6
  class Enhancement
6
- extend T::Helpers
7
-
8
- abstract!
9
-
10
7
  @enhancements = [] #: Array[Class[Enhancement]]
11
8
 
12
9
  class << self
@@ -98,11 +98,8 @@ module RubyIndexer
98
98
  end
99
99
  end
100
100
 
101
+ # @abstract
101
102
  class ModuleOperation
102
- extend T::Helpers
103
-
104
- abstract!
105
-
106
103
  #: String
107
104
  attr_reader :module_name
108
105
 
@@ -115,11 +112,8 @@ module RubyIndexer
115
112
  class Include < ModuleOperation; end
116
113
  class Prepend < ModuleOperation; end
117
114
 
115
+ # @abstract
118
116
  class Namespace < Entry
119
- extend T::Helpers
120
-
121
- abstract!
122
-
123
117
  #: Array[String]
124
118
  attr_reader :nesting
125
119
 
@@ -191,11 +185,8 @@ module RubyIndexer
191
185
  class Constant < Entry
192
186
  end
193
187
 
188
+ # @abstract
194
189
  class Parameter
195
- extend T::Helpers
196
-
197
- abstract!
198
-
199
190
  # Name includes just the name of the parameter, excluding symbols like splats
200
191
  #: Symbol
201
192
  attr_reader :name
@@ -289,12 +280,8 @@ module RubyIndexer
289
280
  end
290
281
  end
291
282
 
283
+ # @abstract
292
284
  class Member < Entry
293
- extend T::Sig
294
- extend T::Helpers
295
-
296
- abstract!
297
-
298
285
  #: Entry::Namespace?
299
286
  attr_reader :owner
300
287
 
@@ -305,7 +292,8 @@ module RubyIndexer
305
292
  @owner = owner
306
293
  end
307
294
 
308
- sig { abstract.returns(T::Array[Entry::Signature]) }
295
+ # @abstract
296
+ #: -> Array[Signature]
309
297
  def signatures; end
310
298
 
311
299
  #: -> String
@@ -818,11 +818,22 @@ module RubyIndexer
818
818
  )
819
819
  # Find the first class entry that has a parent class. Notice that if the developer makes a mistake and inherits
820
820
  # from two different classes in different files, we simply ignore it
821
- superclass = if singleton_levels > 0
822
- self[attached_class_name]&.find { |n| n.is_a?(Entry::Class) && n.parent_class }
823
- else
824
- namespace_entries.find { |n| n.is_a?(Entry::Class) && n.parent_class }
825
- end #: as Entry::Class?
821
+ possible_parents = singleton_levels > 0 ? self[attached_class_name] : namespace_entries
822
+ superclass = nil #: Entry::Class?
823
+
824
+ possible_parents&.each do |n|
825
+ # Ignore non class entries
826
+ next unless n.is_a?(Entry::Class)
827
+
828
+ parent_class = n.parent_class
829
+ next unless parent_class
830
+
831
+ # Always set the superclass, but break early if we found one that isn't `::Object` (meaning we found an explicit
832
+ # parent class and not the implicit default). Note that when setting different parents to the same class, which
833
+ # is invalid, we pick whatever is the first one we find
834
+ superclass = n
835
+ break if parent_class != "::Object"
836
+ end
826
837
 
827
838
  if superclass
828
839
  # If the user makes a mistake and creates a class that inherits from itself, this method would throw a stack
@@ -3,11 +3,8 @@
3
3
 
4
4
  module RubyIndexer
5
5
  class ReferenceFinder
6
- class Target
7
- extend T::Helpers
8
-
9
- abstract!
10
- end
6
+ # @abstract
7
+ class Target; end
11
8
 
12
9
  class ConstTarget < Target
13
10
  #: String
@@ -35,10 +32,14 @@ module RubyIndexer
35
32
  #: String
36
33
  attr_reader :name
37
34
 
38
- #: (String name) -> void
39
- def initialize(name)
35
+ #: Array[String]
36
+ attr_reader :owner_ancestors
37
+
38
+ #: (String name, Array[String] owner_ancestors) -> void
39
+ def initialize(name, owner_ancestors)
40
40
  super()
41
41
  @name = name
42
+ @owner_ancestors = owner_ancestors
42
43
  end
43
44
  end
44
45
 
@@ -325,7 +326,10 @@ module RubyIndexer
325
326
  def collect_instance_variable_references(name, location, declaration)
326
327
  return unless @target.is_a?(InstanceVariableTarget) && name == @target.name
327
328
 
328
- @references << Reference.new(name, location, declaration: declaration)
329
+ receiver_type = Index.actual_nesting(@stack, nil).join("::")
330
+ if @target.owner_ancestors.include?(receiver_type)
331
+ @references << Reference.new(name, location, declaration: declaration)
332
+ end
329
333
  end
330
334
  end
331
335
  end
@@ -728,6 +728,30 @@ module RubyIndexer
728
728
  assert_equal(["A", "ALIAS"], @index.linearized_ancestors_of("A"))
729
729
  end
730
730
 
731
+ def test_linearizing_ancestors_for_classes_with_overridden_parents
732
+ index(<<~RUBY)
733
+ # Find the re-open of a class first, without specifying a parent
734
+ class Child
735
+ end
736
+
737
+ # Now, find the actual definition of the class, which includes a parent
738
+ class Parent; end
739
+ class Child < Parent
740
+ end
741
+ RUBY
742
+
743
+ assert_equal(
744
+ [
745
+ "Child",
746
+ "Parent",
747
+ "Object",
748
+ "Kernel",
749
+ "BasicObject",
750
+ ],
751
+ @index.linearized_ancestors_of("Child"),
752
+ )
753
+ end
754
+
731
755
  def test_resolving_an_inherited_method
732
756
  index(<<~RUBY)
733
757
  module Foo
@@ -236,5 +236,29 @@ module RubyIndexer
236
236
  assert_instance_of(Entry::SingletonClass, owner)
237
237
  assert_equal("Foo::<Class:Foo>", owner&.name)
238
238
  end
239
+
240
+ def test_class_instance_variable_comments
241
+ index(<<~RUBY)
242
+ class Foo
243
+ # Documentation for @a
244
+ @a = "Hello" #: String
245
+ @b = "World" # trailing comment
246
+ @c = "!"
247
+ end
248
+ end
249
+ RUBY
250
+
251
+ assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:2-4:2-6")
252
+ entry = @index["@a"]&.first #: as Entry::InstanceVariable
253
+ assert_equal("Documentation for @a", entry.comments)
254
+
255
+ assert_entry("@b", Entry::InstanceVariable, "/fake/path/foo.rb:3-4:3-6")
256
+ entry = @index["@b"]&.first #: as Entry::InstanceVariable
257
+ assert_empty(entry.comments)
258
+
259
+ assert_entry("@c", Entry::InstanceVariable, "/fake/path/foo.rb:4-4:4-6")
260
+ entry = @index["@c"]&.first #: as Entry::InstanceVariable
261
+ assert_empty(entry.comments)
262
+ end
239
263
  end
240
264
  end
@@ -950,6 +950,23 @@ module RubyIndexer
950
950
  assert_predicate(entry, :public?)
951
951
  end
952
952
 
953
+ def test_handling_attr
954
+ index(<<~RUBY)
955
+ class Foo
956
+ attr :bar
957
+ attr :baz, true
958
+ attr :qux, false
959
+ end
960
+ RUBY
961
+
962
+ assert_entry("bar", Entry::Accessor, "/fake/path/foo.rb:1-8:1-11")
963
+ assert_no_entry("bar=")
964
+ assert_entry("baz", Entry::Accessor, "/fake/path/foo.rb:2-8:2-11")
965
+ assert_entry("baz=", Entry::Accessor, "/fake/path/foo.rb:2-8:2-11")
966
+ assert_entry("qux", Entry::Accessor, "/fake/path/foo.rb:3-8:3-11")
967
+ assert_no_entry("qux=")
968
+ end
969
+
953
970
  private
954
971
 
955
972
  #: (Entry::Method entry, String call_string) -> void
@@ -216,22 +216,43 @@ module RubyIndexer
216
216
  assert_equal(11, refs[2].location.start_line)
217
217
  end
218
218
 
219
- def test_finds_instance_variable_read_references
220
- refs = find_instance_variable_references("@foo", <<~RUBY)
219
+ def test_finds_instance_variable_references
220
+ refs = find_instance_variable_references("@name", ["Foo"], <<~RUBY)
221
221
  class Foo
222
- def foo
223
- @foo
222
+ def initialize
223
+ @name = "foo"
224
+ end
225
+ def name
226
+ @name
227
+ end
228
+ def name_capital
229
+ @name[0]
230
+ end
231
+ end
232
+
233
+ class Bar
234
+ def initialize
235
+ @name = "foo"
236
+ end
237
+ def name
238
+ @name
224
239
  end
225
240
  end
226
241
  RUBY
227
- assert_equal(1, refs.size)
242
+ assert_equal(3, refs.size)
228
243
 
229
- assert_equal("@foo", refs[0].name)
244
+ assert_equal("@name", refs[0].name)
230
245
  assert_equal(3, refs[0].location.start_line)
246
+
247
+ assert_equal("@name", refs[1].name)
248
+ assert_equal(6, refs[1].location.start_line)
249
+
250
+ assert_equal("@name", refs[2].name)
251
+ assert_equal(9, refs[2].location.start_line)
231
252
  end
232
253
 
233
254
  def test_finds_instance_variable_write_references
234
- refs = find_instance_variable_references("@foo", <<~RUBY)
255
+ refs = find_instance_variable_references("@foo", ["Foo"], <<~RUBY)
235
256
  class Foo
236
257
  def write
237
258
  @foo = 1
@@ -252,26 +273,70 @@ module RubyIndexer
252
273
  assert_equal(7, refs[4].location.start_line)
253
274
  end
254
275
 
255
- def test_finds_instance_variable_references_ignore_context
256
- refs = find_instance_variable_references("@name", <<~RUBY)
257
- class Foo
276
+ def test_finds_instance_variable_references_in_owner_ancestors
277
+ refs = find_instance_variable_references("@name", ["Foo", "Base", "Top", "Parent"], <<~RUBY)
278
+ module Base
279
+ def change_name(name)
280
+ @name = name
281
+ end
258
282
  def name
283
+ @name
284
+ end
285
+
286
+ module ::Top
287
+ def name
288
+ @name
289
+ end
290
+ end
291
+ end
292
+
293
+ class Parent
294
+ def initialize
295
+ @name = "parent"
296
+ end
297
+ def name_capital
298
+ @name[0]
299
+ end
300
+ end
301
+
302
+ class Foo < Parent
303
+ include Base
304
+ def initialize
259
305
  @name = "foo"
260
306
  end
307
+ def name
308
+ @name
309
+ end
261
310
  end
311
+
262
312
  class Bar
263
313
  def name
264
314
  @name = "bar"
265
315
  end
266
316
  end
267
317
  RUBY
268
- assert_equal(2, refs.size)
318
+ assert_equal(7, refs.size)
269
319
 
270
320
  assert_equal("@name", refs[0].name)
271
321
  assert_equal(3, refs[0].location.start_line)
272
322
 
273
323
  assert_equal("@name", refs[1].name)
274
- assert_equal(8, refs[1].location.start_line)
324
+ assert_equal(6, refs[1].location.start_line)
325
+
326
+ assert_equal("@name", refs[2].name)
327
+ assert_equal(11, refs[2].location.start_line)
328
+
329
+ assert_equal("@name", refs[3].name)
330
+ assert_equal(18, refs[3].location.start_line)
331
+
332
+ assert_equal("@name", refs[4].name)
333
+ assert_equal(21, refs[4].location.start_line)
334
+
335
+ assert_equal("@name", refs[5].name)
336
+ assert_equal(28, refs[5].location.start_line)
337
+
338
+ assert_equal("@name", refs[6].name)
339
+ assert_equal(31, refs[6].location.start_line)
275
340
  end
276
341
 
277
342
  def test_accounts_for_reopened_classes
@@ -310,8 +375,8 @@ module RubyIndexer
310
375
  find_references(target, source)
311
376
  end
312
377
 
313
- def find_instance_variable_references(instance_variable_name, source)
314
- target = ReferenceFinder::InstanceVariableTarget.new(instance_variable_name)
378
+ def find_instance_variable_references(instance_variable_name, owner_ancestors, source)
379
+ target = ReferenceFinder::InstanceVariableTarget.new(instance_variable_name, owner_ancestors)
315
380
  find_references(target, source)
316
381
  end
317
382
 
@@ -19,12 +19,8 @@ module RubyLsp
19
19
  # end
20
20
  # end
21
21
  # ```
22
+ # @abstract
22
23
  class Addon
23
- extend T::Sig
24
- extend T::Helpers
25
-
26
- abstract!
27
-
28
24
  @addons = [] #: Array[Addon]
29
25
  @addon_classes = [] #: Array[singleton(Addon)]
30
26
  # Add-on instances that have declared a handler to accept file watcher events
@@ -178,21 +174,25 @@ module RubyLsp
178
174
 
179
175
  # Each add-on should implement `MyAddon#activate` and use to perform any sort of initialization, such as
180
176
  # reading information into memory or even spawning a separate process
181
- sig { abstract.params(global_state: GlobalState, outgoing_queue: Thread::Queue).void }
177
+ # @abstract
178
+ #: (GlobalState, Thread::Queue) -> void
182
179
  def activate(global_state, outgoing_queue); end
183
180
 
184
181
  # Each add-on should implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
185
182
  # child process
186
- sig { abstract.void }
183
+ # @abstract
184
+ #: -> void
187
185
  def deactivate; end
188
186
 
189
187
  # Add-ons should override the `name` method to return the add-on name
190
- sig { abstract.returns(String) }
188
+ # @abstract
189
+ #: -> String
191
190
  def name; end
192
191
 
193
192
  # Add-ons should override the `version` method to return a semantic version string representing the add-on's
194
193
  # version. This is used for compatibility checks
195
- sig { abstract.returns(String) }
194
+ # @abstract
195
+ #: -> String
196
196
  def version; end
197
197
 
198
198
  # Handle a response from a window/showMessageRequest request. Add-ons must include the addon_name as part of the
@@ -2,12 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
+ # @abstract
5
6
  class BaseServer
6
- extend T::Sig
7
- extend T::Helpers
8
-
9
- abstract!
10
-
11
7
  #: (**untyped options) -> void
12
8
  def initialize(**options)
13
9
  @test_mode = options[:test_mode] #: bool?
@@ -130,10 +126,12 @@ module RubyLsp
130
126
  @incoming_queue << message
131
127
  end
132
128
 
133
- sig { abstract.params(message: T::Hash[Symbol, T.untyped]).void }
129
+ # @abstract
130
+ #: (Hash[Symbol, untyped] message) -> void
134
131
  def process_message(message); end
135
132
 
136
- sig { abstract.void }
133
+ # @abstract
134
+ #: -> void
137
135
  def shutdown; end
138
136
 
139
137
  #: (Integer id, String message, ?type: Integer) -> void
@@ -2,21 +2,16 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
+ # @abstract
5
6
  #: [ParseResultType]
6
7
  class Document
7
- extend T::Sig
8
- extend T::Helpers
9
8
  extend T::Generic
10
9
 
11
- class LocationNotFoundError < StandardError; end
12
-
13
10
  # This maximum number of characters for providing expensive features, like semantic highlighting and diagnostics.
14
11
  # This is the same number used by the TypeScript extension in VS Code
15
12
  MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES = 100_000
16
13
  EMPTY_CACHE = Object.new.freeze #: Object
17
14
 
18
- abstract!
19
-
20
15
  #: ParseResultType
21
16
  attr_reader :parse_result
22
17
 
@@ -63,7 +58,8 @@ module RubyLsp
63
58
  self.class == other.class && uri == other.uri && @source == other.source
64
59
  end
65
60
 
66
- sig { abstract.returns(Symbol) }
61
+ # @abstract
62
+ #: -> Symbol
67
63
  def language_id; end
68
64
 
69
65
  #: [T] (String request_name) { (Document[ParseResultType] document) -> T } -> T
@@ -122,10 +118,12 @@ module RubyLsp
122
118
  end
123
119
 
124
120
  # Returns `true` if the document was parsed and `false` if nothing needed parsing
125
- sig { abstract.returns(T::Boolean) }
121
+ # @abstract
122
+ #: -> bool
126
123
  def parse!; end
127
124
 
128
- sig { abstract.returns(T::Boolean) }
125
+ # @abstract
126
+ #: -> bool
129
127
  def syntax_error?; end
130
128
 
131
129
  #: -> bool
@@ -150,12 +148,8 @@ module RubyLsp
150
148
  Scanner.new(@source, @encoding)
151
149
  end
152
150
 
151
+ # @abstract
153
152
  class Edit
154
- extend T::Sig
155
- extend T::Helpers
156
-
157
- abstract!
158
-
159
153
  #: Hash[Symbol, untyped]
160
154
  attr_reader :range
161
155
 
@@ -180,7 +174,7 @@ module RubyLsp
180
174
  def initialize(source, encoding)
181
175
  @current_line = 0 #: Integer
182
176
  @pos = 0 #: Integer
183
- @source = source.codepoints #: Array[Integer]
177
+ @bytes_or_codepoints = encoding == Encoding::UTF_8 ? source.bytes : source.codepoints #: Array[Integer]
184
178
  @encoding = encoding
185
179
  end
186
180
 
@@ -189,23 +183,40 @@ module RubyLsp
189
183
  def find_char_position(position)
190
184
  # Find the character index for the beginning of the requested line
191
185
  until @current_line == position[:line]
192
- until LINE_BREAK == @source[@pos]
193
- @pos += 1
186
+ @pos += 1 until LINE_BREAK == @bytes_or_codepoints[@pos]
187
+ @pos += 1
188
+ @current_line += 1
189
+ end
194
190
 
195
- if @pos >= @source.length
196
- # Pack the code points back into the original string to provide context in the error message
197
- raise LocationNotFoundError, "Requested position: #{position}\nSource:\n\n#{@source.pack("U*")}"
191
+ # For UTF-8, the code unit length is the same as bytes, but we want to return the character index
192
+ requested_position = if @encoding == Encoding::UTF_8
193
+ character_offset = 0
194
+ i = @pos
195
+
196
+ # Each group of bytes is a character. We advance based on the number of bytes to count how many full
197
+ # characters we have in the requested offset
198
+ while i < @pos + position[:character] && i < @bytes_or_codepoints.length
199
+ byte = @bytes_or_codepoints[i] #: as !nil
200
+ i += if byte < 0x80 # 1-byte character
201
+ 1
202
+ elsif byte < 0xE0 # 2-byte character
203
+ 2
204
+ elsif byte < 0xF0 # 3-byte character
205
+ 3
206
+ else # 4-byte character
207
+ 4
198
208
  end
209
+
210
+ character_offset += 1
199
211
  end
200
212
 
201
- @pos += 1
202
- @current_line += 1
213
+ @pos + character_offset
214
+ else
215
+ @pos + position[:character]
203
216
  end
204
217
 
205
218
  # The final position is the beginning of the line plus the requested column. If the encoding is UTF-16, we also
206
219
  # need to adjust for surrogate pairs
207
- requested_position = @pos + position[:character]
208
-
209
220
  if @encoding == Encoding::UTF_16LE
210
221
  requested_position -= utf_16_character_position_correction(@pos, requested_position)
211
222
  end
@@ -220,7 +231,7 @@ module RubyLsp
220
231
  utf16_unicode_correction = 0
221
232
 
222
233
  until current_position == requested_position
223
- codepoint = @source[current_position]
234
+ codepoint = @bytes_or_codepoints[current_position]
224
235
  utf16_unicode_correction += 1 if codepoint && codepoint > SURROGATE_PAIR_START
225
236
 
226
237
  current_position += 1
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
- #: [ParseResultType = Prism::ParseResult]
5
+ #: [ParseResultType = Prism::ParseLexResult]
6
6
  class ERBDocument < Document
7
7
  #: String
8
8
  attr_reader :host_language_source
@@ -31,11 +31,16 @@ module RubyLsp
31
31
  @host_language_source = scanner.host_language
32
32
  # Use partial script to avoid syntax errors in ERB files where keywords may be used without the full context in
33
33
  # which they will be evaluated
34
- @parse_result = Prism.parse(scanner.ruby, partial_script: true)
34
+ @parse_result = Prism.parse_lex(scanner.ruby, partial_script: true)
35
35
  @code_units_cache = @parse_result.code_units_cache(@encoding)
36
36
  true
37
37
  end
38
38
 
39
+ #: -> Prism::ProgramNode
40
+ def ast
41
+ @parse_result.value.first
42
+ end
43
+
39
44
  # @override
40
45
  #: -> bool
41
46
  def syntax_error?
@@ -53,7 +58,7 @@ module RubyLsp
53
58
  char_position, _ = find_index_by_position(position)
54
59
 
55
60
  RubyDocument.locate(
56
- @parse_result.value,
61
+ ast,
57
62
  char_position,
58
63
  code_units_cache: @code_units_cache,
59
64
  node_types: node_types,