ruby-lsp 0.17.7 → 0.17.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 671bab4fdbd77d1980781f55874d6cd448e1dcd11a6f534a176dfb00b098d2eb
4
- data.tar.gz: bd12fd2ef58588b3b5386fec48c0f561140efff180988f1971b67f635f0e1042
3
+ metadata.gz: f3deccbc9952a61d46392be2f050a4684b108e7cd3da0085a4e8997168dfc021
4
+ data.tar.gz: d1b9a804d9e67aa2d23b39f12c79c6e1d5ba940ec360170623f7e0870e7d6c78
5
5
  SHA512:
6
- metadata.gz: f3a2a3115e1745ad7263b7e113ef13795f9a1ed1ee118128a9ab2aaad703b17551acbd929f820700e6650efb76421418a13a78270ac8d3aaff080edc46731aee
7
- data.tar.gz: d29bb4d6c60727eef1a10905762a6d0c8946b7afc8aac6148ef970c8d752365f1577403497702f52a7211689cde7940b3d7cd1e1e0eb03d038e0dc6f7ceed738
6
+ metadata.gz: fe2e99145d0f268644a5d8feeff49146e635998ab8ff3eff18f47b1f555bb249b296966d0a431c930ab6ebca58032ed954a338f7c075300869545711bcfae50e
7
+ data.tar.gz: 36f253a202755a49e33b8d6c4906aa0acfebe9fe52772519684d433649d7fbadfee186274eba94027017535e55438333759477e2dbb87a7da7e9ead0bc98aaa7
data/README.md CHANGED
@@ -78,7 +78,12 @@ default gems, except for
78
78
  - Gems that only appear under the `:development` group
79
79
  - All Ruby files under `test/**/*.rb`
80
80
 
81
- By creating a `.index.yml` file, these configurations can be overridden and tuned. Note that indexing dependent behavior, such as definition, hover, completion or workspace symbol will be impacted by the configurations placed here.
81
+ This behaviour can be overridden and tuned. Learn how to configure it [for VS Code](vscode/README.md#Indexing-Configuration) or [for other editors](EDITORS.md#Indexing-Configuration).
82
+
83
+ Note that indexing-dependent behavior, such as definition, hover, completion or workspace symbol will be impacted by
84
+ the configuration changes.
85
+
86
+ The older approach of using a `.index.yml` file has been deprecated and will be removed in a future release.
82
87
 
83
88
  ```yaml
84
89
  # Exclude files based on a given pattern. Often used to exclude test files or fixtures
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.7
1
+ 0.17.9
data/exe/ruby-lsp CHANGED
@@ -36,6 +36,10 @@ parser = OptionParser.new do |opts|
36
36
  options[:experimental] = true
37
37
  end
38
38
 
39
+ opts.on("--doctor", "Run troubleshooting steps") do
40
+ options[:doctor] = true
41
+ end
42
+
39
43
  opts.on("-h", "--help", "Print this help") do
40
44
  puts opts.help
41
45
  puts
@@ -107,4 +111,25 @@ if options[:time_index]
107
111
  return
108
112
  end
109
113
 
114
+ if options[:doctor]
115
+ if File.exist?(".index.yml")
116
+ begin
117
+ config = YAML.parse_file(".index.yml").to_ruby
118
+ rescue => e
119
+ abort("Error parsing config: #{e.message}")
120
+ end
121
+ RubyIndexer.configuration.apply_config(config)
122
+ end
123
+
124
+ index = RubyIndexer::Index.new
125
+
126
+ puts "Globbing for indexable files"
127
+
128
+ RubyIndexer.configuration.indexables.each do |indexable|
129
+ puts "indexing: #{indexable.full_path}"
130
+ index.index_single(indexable)
131
+ end
132
+ return
133
+ end
134
+
110
135
  RubyLsp::Server.new.start
@@ -71,7 +71,7 @@ module RubyIndexer
71
71
 
72
72
  superclass = node.superclass
73
73
 
74
- nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
74
+ nesting = actual_nesting(name)
75
75
 
76
76
  parent_class = case superclass
77
77
  when Prism::ConstantReadNode, Prism::ConstantPathNode
@@ -119,8 +119,7 @@ module RubyIndexer
119
119
 
120
120
  comments = collect_comments(node)
121
121
 
122
- nesting = name.start_with?("::") ? [name.delete_prefix("::")] : @stack + [name.delete_prefix("::")]
123
- entry = Entry::Module.new(nesting, @file_path, node.location, constant_path.location, comments)
122
+ entry = Entry::Module.new(actual_nesting(name), @file_path, node.location, constant_path.location, comments)
124
123
 
125
124
  @owner_stack << entry
126
125
  @index.add(entry)
@@ -709,5 +708,19 @@ module RubyIndexer
709
708
  :"(#{names_with_commas})"
710
709
  end
711
710
  end
711
+
712
+ sig { params(name: String).returns(T::Array[String]) }
713
+ def actual_nesting(name)
714
+ nesting = @stack + [name]
715
+ corrected_nesting = []
716
+
717
+ nesting.reverse_each do |name|
718
+ corrected_nesting.prepend(name.delete_prefix("::"))
719
+
720
+ break if name.start_with?("::")
721
+ end
722
+
723
+ corrected_nesting
724
+ end
712
725
  end
713
726
  end
@@ -84,6 +84,34 @@ module RubyIndexer
84
84
  @require_paths_tree.search(query)
85
85
  end
86
86
 
87
+ # Searches for a constant based on an unqualified name and returns the first possible match regardless of whether
88
+ # there are more possible matching entries
89
+ sig do
90
+ params(
91
+ name: String,
92
+ ).returns(T.nilable(T::Array[T.any(
93
+ Entry::Namespace,
94
+ Entry::Alias,
95
+ Entry::UnresolvedAlias,
96
+ Entry::Constant,
97
+ )]))
98
+ end
99
+ def first_unqualified_const(name)
100
+ _name, entries = @entries.find do |const_name, _entries|
101
+ const_name.end_with?(name)
102
+ end
103
+
104
+ T.cast(
105
+ entries,
106
+ T.nilable(T::Array[T.any(
107
+ Entry::Namespace,
108
+ Entry::Alias,
109
+ Entry::UnresolvedAlias,
110
+ Entry::Constant,
111
+ )]),
112
+ )
113
+ end
114
+
87
115
  # Searches entries in the index based on an exact prefix, intended for providing autocomplete. All possible matches
88
116
  # to the prefix are returned. The return is an array of arrays, where each entry is the array of entries for a given
89
117
  # name match. For example:
@@ -37,45 +37,33 @@ module RubyIndexer
37
37
  sig { params(declaration: RBS::AST::Declarations::Base, pathname: Pathname).void }
38
38
  def process_declaration(declaration, pathname)
39
39
  case declaration
40
- when RBS::AST::Declarations::Class
41
- handle_class_declaration(declaration, pathname)
42
- when RBS::AST::Declarations::Module
43
- handle_module_declaration(declaration, pathname)
40
+ when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module
41
+ handle_class_or_module_declaration(declaration, pathname)
44
42
  else # rubocop:disable Style/EmptyElse
45
43
  # Other kinds not yet handled
46
44
  end
47
45
  end
48
46
 
49
- sig { params(declaration: RBS::AST::Declarations::Class, pathname: Pathname).void }
50
- def handle_class_declaration(declaration, pathname)
51
- nesting = [declaration.name.name.to_s]
52
- file_path = pathname.to_s
53
- location = to_ruby_indexer_location(declaration.location)
54
- comments = Array(declaration.comment&.string)
55
- parent_class = declaration.super_class&.name&.name&.to_s
56
- class_entry = Entry::Class.new(nesting, file_path, location, location, comments, parent_class)
57
- add_declaration_mixins_to_entry(declaration, class_entry)
58
- @index.add(class_entry)
59
- declaration.members.each do |member|
60
- next unless member.is_a?(RBS::AST::Members::MethodDefinition)
61
-
62
- handle_method(member, class_entry)
63
- end
47
+ sig do
48
+ params(declaration: T.any(RBS::AST::Declarations::Class, RBS::AST::Declarations::Module), pathname: Pathname).void
64
49
  end
65
-
66
- sig { params(declaration: RBS::AST::Declarations::Module, pathname: Pathname).void }
67
- def handle_module_declaration(declaration, pathname)
50
+ def handle_class_or_module_declaration(declaration, pathname)
68
51
  nesting = [declaration.name.name.to_s]
69
52
  file_path = pathname.to_s
70
53
  location = to_ruby_indexer_location(declaration.location)
71
54
  comments = Array(declaration.comment&.string)
72
- module_entry = Entry::Module.new(nesting, file_path, location, location, comments)
73
- add_declaration_mixins_to_entry(declaration, module_entry)
74
- @index.add(module_entry)
55
+ entry = if declaration.is_a?(RBS::AST::Declarations::Class)
56
+ parent_class = declaration.super_class&.name&.name&.to_s
57
+ Entry::Class.new(nesting, file_path, location, location, comments, parent_class)
58
+ else
59
+ Entry::Module.new(nesting, file_path, location, location, comments)
60
+ end
61
+ add_declaration_mixins_to_entry(declaration, entry)
62
+ @index.add(entry)
75
63
  declaration.members.each do |member|
76
64
  next unless member.is_a?(RBS::AST::Members::MethodDefinition)
77
65
 
78
- handle_method(member, module_entry)
66
+ handle_method(member, entry)
79
67
  end
80
68
  end
81
69
 
@@ -546,5 +546,43 @@ module RubyIndexer
546
546
  assert_equal(7, name_location.start_column)
547
547
  assert_equal(10, name_location.end_column)
548
548
  end
549
+
550
+ def test_indexing_namespaces_inside_top_level_references
551
+ index(<<~RUBY)
552
+ module ::Foo
553
+ class Bar
554
+ end
555
+ end
556
+ RUBY
557
+
558
+ # We want to explicitly verify that we didn't introduce the leading `::` by accident, but `Index#[]` deletes the
559
+ # prefix when we use `refute_entry`
560
+ entries = @index.instance_variable_get(:@entries)
561
+ refute(entries.key?("::Foo"))
562
+ refute(entries.key?("::Foo::Bar"))
563
+ assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:3-3")
564
+ assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
565
+ end
566
+
567
+ def test_indexing_namespaces_inside_nested_top_level_references
568
+ index(<<~RUBY)
569
+ class Baz
570
+ module ::Foo
571
+ class Bar
572
+ end
573
+
574
+ class ::Qux
575
+ end
576
+ end
577
+ end
578
+ RUBY
579
+
580
+ refute_entry("Baz::Foo")
581
+ refute_entry("Baz::Foo::Bar")
582
+ assert_entry("Baz", Entry::Class, "/fake/path/foo.rb:0-0:8-3")
583
+ assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:1-2:7-5")
584
+ assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:2-4:3-7")
585
+ assert_entry("Qux", Entry::Class, "/fake/path/foo.rb:5-4:6-7")
586
+ end
549
587
  end
550
588
  end
@@ -1542,6 +1542,21 @@ module RubyIndexer
1542
1542
  assert_empty(@index.method_completion_candidates("bar", "Foo"))
1543
1543
  end
1544
1544
 
1545
+ def test_first_unqualified_const
1546
+ index(<<~RUBY)
1547
+ module Foo
1548
+ class Bar; end
1549
+ end
1550
+
1551
+ module Baz
1552
+ class Bar; end
1553
+ end
1554
+ RUBY
1555
+
1556
+ entry = T.must(@index.first_unqualified_const("Bar")&.first)
1557
+ assert_equal("Foo::Bar", entry.name)
1558
+ end
1559
+
1545
1560
  def test_completion_does_not_duplicate_overridden_methods
1546
1561
  index(<<~RUBY)
1547
1562
  class Foo
@@ -53,7 +53,7 @@ module RubyLsp
53
53
 
54
54
  # We don't want to try to parse documents on text synchronization notifications
55
55
  @store.get(parsed_uri).parse unless method.start_with?("textDocument/did")
56
- rescue Errno::ENOENT
56
+ rescue Store::NonExistingDocumentError
57
57
  # If we receive a request for a file that no longer exists, we don't want to fail
58
58
  end
59
59
  end
@@ -10,6 +10,16 @@ module RubyLsp
10
10
  end
11
11
  end
12
12
 
13
+ class SorbetLevel < T::Enum
14
+ enums do
15
+ None = new("none")
16
+ Ignore = new("ignore")
17
+ False = new("false")
18
+ True = new("true")
19
+ Strict = new("strict")
20
+ end
21
+ end
22
+
13
23
  extend T::Sig
14
24
  extend T::Helpers
15
25
 
@@ -213,10 +223,23 @@ module RubyLsp
213
223
  NodeContext.new(closest, parent, nesting_nodes, call_node)
214
224
  end
215
225
 
216
- sig { returns(T::Boolean) }
217
- def sorbet_sigil_is_true_or_higher
218
- parse_result.magic_comments.any? do |comment|
219
- comment.key == "typed" && ["true", "strict", "strong"].include?(comment.value)
226
+ sig { returns(SorbetLevel) }
227
+ def sorbet_level
228
+ sigil = parse_result.magic_comments.find do |comment|
229
+ comment.key == "typed"
230
+ end&.value
231
+
232
+ case sigil
233
+ when "ignore"
234
+ SorbetLevel::Ignore
235
+ when "false"
236
+ SorbetLevel::False
237
+ when "true"
238
+ SorbetLevel::True
239
+ when "strict", "strong"
240
+ SorbetLevel::Strict
241
+ else
242
+ SorbetLevel::None
220
243
  end
221
244
  end
222
245
 
@@ -36,10 +36,10 @@ module RubyLsp
36
36
  @test_library = T.let("minitest", String)
37
37
  @has_type_checker = T.let(true, T::Boolean)
38
38
  @index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
39
- @type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
40
39
  @supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
41
40
  @supports_watching_files = T.let(false, T::Boolean)
42
41
  @experimental_features = T.let(false, T::Boolean)
42
+ @type_inferrer = T.let(TypeInferrer.new(@index, @experimental_features), TypeInferrer)
43
43
  end
44
44
 
45
45
  sig { params(identifier: String, instance: Requests::Support::Formatter).void }
@@ -90,6 +90,7 @@ module RubyLsp
90
90
  end
91
91
 
92
92
  @experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
93
+ @type_inferrer.experimental_features = @experimental_features
93
94
  end
94
95
 
95
96
  sig { returns(String) }
@@ -7,12 +7,56 @@ module RubyLsp
7
7
  extend T::Sig
8
8
  include Requests::Support::Common
9
9
 
10
+ KEYWORDS = [
11
+ "alias",
12
+ "and",
13
+ "begin",
14
+ "BEGIN",
15
+ "break",
16
+ "case",
17
+ "class",
18
+ "def",
19
+ "defined?",
20
+ "do",
21
+ "else",
22
+ "elsif",
23
+ "end",
24
+ "END",
25
+ "ensure",
26
+ "false",
27
+ "for",
28
+ "if",
29
+ "in",
30
+ "module",
31
+ "next",
32
+ "nil",
33
+ "not",
34
+ "or",
35
+ "redo",
36
+ "rescue",
37
+ "retry",
38
+ "return",
39
+ "self",
40
+ "super",
41
+ "then",
42
+ "true",
43
+ "undef",
44
+ "unless",
45
+ "until",
46
+ "when",
47
+ "while",
48
+ "yield",
49
+ "__ENCODING__",
50
+ "__FILE__",
51
+ "__LINE__",
52
+ ].freeze
53
+
10
54
  sig do
11
55
  params(
12
56
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
13
57
  global_state: GlobalState,
14
58
  node_context: NodeContext,
15
- typechecker_enabled: T::Boolean,
59
+ sorbet_level: Document::SorbetLevel,
16
60
  dispatcher: Prism::Dispatcher,
17
61
  uri: URI::Generic,
18
62
  trigger_character: T.nilable(String),
@@ -22,7 +66,7 @@ module RubyLsp
22
66
  response_builder,
23
67
  global_state,
24
68
  node_context,
25
- typechecker_enabled,
69
+ sorbet_level,
26
70
  dispatcher,
27
71
  uri,
28
72
  trigger_character
@@ -32,7 +76,7 @@ module RubyLsp
32
76
  @index = T.let(global_state.index, RubyIndexer::Index)
33
77
  @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
34
78
  @node_context = node_context
35
- @typechecker_enabled = typechecker_enabled
79
+ @sorbet_level = sorbet_level
36
80
  @uri = uri
37
81
  @trigger_character = trigger_character
38
82
 
@@ -53,7 +97,9 @@ module RubyLsp
53
97
  # Handle completion on regular constant references (e.g. `Bar`)
54
98
  sig { params(node: Prism::ConstantReadNode).void }
55
99
  def on_constant_read_node_enter(node)
56
- return if @global_state.has_type_checker
100
+ # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
101
+ # no sigil, Sorbet will still provide completion for constants
102
+ return if @sorbet_level != Document::SorbetLevel::Ignore
57
103
 
58
104
  name = constant_name(node)
59
105
  return if name.nil?
@@ -74,7 +120,9 @@ module RubyLsp
74
120
  # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
75
121
  sig { params(node: Prism::ConstantPathNode).void }
76
122
  def on_constant_path_node_enter(node)
77
- return if @global_state.has_type_checker
123
+ # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
124
+ # no sigil, Sorbet will still provide completion for constants
125
+ return if @sorbet_level != Document::SorbetLevel::Ignore
78
126
 
79
127
  name = constant_name(node)
80
128
  return if name.nil?
@@ -84,28 +132,32 @@ module RubyLsp
84
132
 
85
133
  sig { params(node: Prism::CallNode).void }
86
134
  def on_call_node_enter(node)
87
- receiver = node.receiver
88
-
89
- # When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke singleton
90
- # methods). However, in addition to providing method completion, we also need to show possible constant
91
- # completions
92
- if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
93
- node.call_operator == "::"
94
-
95
- name = constant_name(receiver)
96
-
97
- if name
98
- start_loc = node.location
99
- end_loc = T.must(node.call_operator_loc)
100
-
101
- constant_path_completion(
102
- "#{name}::",
103
- Interface::Range.new(
104
- start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
105
- end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
106
- ),
107
- )
108
- return
135
+ # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
136
+ # no sigil, Sorbet will still provide completion for constants
137
+ if @sorbet_level == Document::SorbetLevel::Ignore
138
+ receiver = node.receiver
139
+
140
+ # When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke
141
+ # singleton methods). However, in addition to providing method completion, we also need to show possible
142
+ # constant completions
143
+ if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
144
+ node.call_operator == "::"
145
+
146
+ name = constant_name(receiver)
147
+
148
+ if name
149
+ start_loc = node.location
150
+ end_loc = T.must(node.call_operator_loc)
151
+
152
+ constant_path_completion(
153
+ "#{name}::",
154
+ Interface::Range.new(
155
+ start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
156
+ end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
157
+ ),
158
+ )
159
+ return
160
+ end
109
161
  end
110
162
  end
111
163
 
@@ -118,7 +170,7 @@ module RubyLsp
118
170
  when "require_relative"
119
171
  complete_require_relative(node)
120
172
  else
121
- complete_methods(node, name) unless @typechecker_enabled
173
+ complete_methods(node, name)
122
174
  end
123
175
  end
124
176
 
@@ -203,10 +255,14 @@ module RubyLsp
203
255
 
204
256
  sig { params(name: String, location: Prism::Location).void }
205
257
  def handle_instance_variable_completion(name, location)
258
+ # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
259
+ # to provide all features for them
260
+ return if @sorbet_level == Document::SorbetLevel::Strict
261
+
206
262
  type = @type_inferrer.infer_receiver_type(@node_context)
207
263
  return unless type
208
264
 
209
- @index.instance_variable_completion_candidates(name, type).each do |entry|
265
+ @index.instance_variable_completion_candidates(name, type.name).each do |entry|
210
266
  variable_name = entry.name
211
267
 
212
268
  label_details = Interface::CompletionItemLabelDetails.new(
@@ -277,7 +333,15 @@ module RubyLsp
277
333
 
278
334
  sig { params(node: Prism::CallNode, name: String).void }
279
335
  def complete_methods(node, name)
280
- add_local_completions(node, name)
336
+ # If the node has a receiver, then we don't need to provide local nor keyword completions. Sorbet can provide
337
+ # local and keyword completion for any file with a Sorbet level of true or higher
338
+ if !sorbet_level_true_or_higher?(@sorbet_level) && !node.receiver
339
+ add_local_completions(node, name)
340
+ add_keyword_completions(node, name)
341
+ end
342
+
343
+ # Sorbet can provide completion for methods invoked on self on typed true or higher files
344
+ return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
281
345
 
282
346
  type = @type_inferrer.infer_receiver_type(@node_context)
283
347
  return unless type
@@ -302,8 +366,11 @@ module RubyLsp
302
366
 
303
367
  return unless range
304
368
 
305
- @index.method_completion_candidates(method_name, type).each do |entry|
369
+ guessed_type = type.name
370
+
371
+ @index.method_completion_candidates(method_name, type.name).each do |entry|
306
372
  entry_name = entry.name
373
+ owner_name = entry.owner&.name
307
374
 
308
375
  label_details = Interface::CompletionItemLabelDetails.new(
309
376
  description: entry.file_name,
@@ -316,7 +383,8 @@ module RubyLsp
316
383
  text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
317
384
  kind: Constant::CompletionItemKind::METHOD,
318
385
  data: {
319
- owner_name: entry.owner&.name,
386
+ owner_name: owner_name,
387
+ guessed_type: guessed_type,
320
388
  },
321
389
  )
322
390
  end
@@ -326,11 +394,6 @@ module RubyLsp
326
394
 
327
395
  sig { params(node: Prism::CallNode, name: String).void }
328
396
  def add_local_completions(node, name)
329
- return if @global_state.has_type_checker
330
-
331
- # If the call node has a receiver, then it cannot possibly be a local variable
332
- return if node.receiver
333
-
334
397
  range = range_from_location(T.must(node.message_loc))
335
398
 
336
399
  @node_context.locals_for_scope.each do |local|
@@ -349,6 +412,24 @@ module RubyLsp
349
412
  end
350
413
  end
351
414
 
415
+ sig { params(node: Prism::CallNode, name: String).void }
416
+ def add_keyword_completions(node, name)
417
+ range = range_from_location(T.must(node.message_loc))
418
+
419
+ KEYWORDS.each do |keyword|
420
+ next unless keyword.start_with?(name)
421
+
422
+ @response_builder << Interface::CompletionItem.new(
423
+ label: keyword,
424
+ text_edit: Interface::TextEdit.new(range: range, new_text: keyword),
425
+ kind: Constant::CompletionItemKind::KEYWORD,
426
+ data: {
427
+ skip_resolve: true,
428
+ },
429
+ )
430
+ end
431
+ end
432
+
352
433
  sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
353
434
  def build_completion(label, node)
354
435
  # We should use the content location as we only replace the content and not the delimiters of the string