ruby-lsp 0.17.7 → 0.17.9

Sign up to get free protection for your applications and to get access to all the features.
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