ruby-lsp 0.16.6 → 0.16.7

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: d725a0a2fbd58820e2a87efae85564498221feb8410292055b0caafd889284e6
4
- data.tar.gz: 5376ca8a9d08ca0883fca3aa19a1a178102eb60f49ab3fe5f07bb1a58ba63ddf
3
+ metadata.gz: ebc166637de670ea02144e74ccb2e99ae1a9824c9f99995380d81bed99ff6a29
4
+ data.tar.gz: 48b7ef9364f4d1a8044042c8476414e87da5b38ca4871683c16d06636fc8e411
5
5
  SHA512:
6
- metadata.gz: 81fbb2e5018ad55d8888c98e277e7ac6ed8b45314627b1a3e3dabde2cb8aac0284dc70487a694226e5a08817feb82b64daadfcb955e9642b288e575189a56015
7
- data.tar.gz: f32ef084e4412752cc32df55da0dd7b985d3826db110bf91ba3473773043c50345ceb597432483a0a75f1790cabfca796fa9581d3ba86edff96a8642fb2312f2
6
+ metadata.gz: 80c4f791e7dfef66623f5190eaba4dc76c745a27f74238d912c34711c408803ec952d5ec3e8041ebb6410b99c74d2f3d8274db5dffa8f187e48a7032a3960086
7
+ data.tar.gz: 941209321fb263b9a22cbe27102e8761c6ef127902e2b74f760fec1a8701d96e5bcef5f01c704e7215e061437cac2577c38e100464cccfe03909aa42ee1e318e
data/README.md CHANGED
@@ -33,7 +33,7 @@ The Ruby LSP features include
33
33
  - Completion for classes, modules, constants and require paths
34
34
  - Fuzzy search classes, modules and constants anywhere in the project and its dependencies (workspace symbol)
35
35
 
36
- Adding method support for definition, completion, hover and workspace symbol is planned, but not yet completed.
36
+ Adding method support for definition, completion, hover and workspace symbol is partially supported, but not yet complete. Follow progress in https://github.com/Shopify/ruby-lsp/issues/899
37
37
 
38
38
  See complete information about features [here](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
39
39
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.16.6
1
+ 0.16.7
data/exe/ruby-lsp CHANGED
@@ -18,6 +18,10 @@ parser = OptionParser.new do |opts|
18
18
  options[:debug] = true
19
19
  end
20
20
 
21
+ opts.on("--time-index", "Measure the time it takes to index the project") do
22
+ options[:time_index] = true
23
+ end
24
+
21
25
  opts.on(
22
26
  "--branch [BRANCH]",
23
27
  "Launch the Ruby LSP using the specified branch rather than the release version",
@@ -80,14 +84,27 @@ if options[:debug]
80
84
  end
81
85
 
82
86
  begin
83
- original_stdout = $stdout
84
- $stdout = $stderr
87
+ ENV.delete("RUBY_DEBUG_IRB_CONSOLE")
85
88
  require "debug/open_nonstop"
86
89
  rescue LoadError
87
90
  $stderr.puts("You need to install the debug gem to use the --debug flag")
88
- ensure
89
- $stdout = original_stdout
90
91
  end
91
92
  end
92
93
 
94
+ if options[:time_index]
95
+ require "benchmark"
96
+
97
+ index = RubyIndexer::Index.new
98
+
99
+ result = Benchmark.realtime { index.index_all }
100
+ entries = index.instance_variable_get(:@entries)
101
+ entries_by_entry_type = entries.values.flatten.group_by(&:class)
102
+
103
+ puts <<~MSG
104
+ Ruby LSP v#{RubyLsp::VERSION}: Indexing took #{result.round(5)} seconds and generated:
105
+ - #{entries_by_entry_type.map { |k, v| "#{k.name.split("::").last}: #{v.size}" }.join("\n- ")}
106
+ MSG
107
+ return
108
+ end
109
+
93
110
  RubyLsp::Server.new.start
data/exe/ruby-lsp-check CHANGED
@@ -47,9 +47,7 @@ index = RubyIndexer::Index.new
47
47
  indexables = RubyIndexer.configuration.indexables
48
48
 
49
49
  indexables.each_with_index do |indexable, i|
50
- result = Prism.parse(File.read(indexable.full_path))
51
- collector = RubyIndexer::Collector.new(index, result, indexable.full_path)
52
- collector.collect(result.value)
50
+ index.index_single(indexable)
53
51
  rescue => e
54
52
  errors[indexable.full_path] = e
55
53
  ensure
data/exe/ruby-lsp-doctor CHANGED
@@ -19,8 +19,5 @@ puts "Globbing for indexable files"
19
19
 
20
20
  RubyIndexer.configuration.indexables.each do |indexable|
21
21
  puts "indexing: #{indexable.full_path}"
22
- content = File.read(indexable.full_path)
23
- result = Prism.parse(content)
24
- collector = RubyIndexer::Collector.new(index, result, indexable.full_path)
25
- collector.collect(result.value)
22
+ index.index_single(indexable)
26
23
  end
data/lib/core_ext/uri.rb CHANGED
@@ -11,6 +11,9 @@ module URI
11
11
  # On Windows, if the path begins with the disk name, we need to add a leading slash to make it a valid URI
12
12
  escaped_path = if /^[A-Z]:/i.match?(path)
13
13
  DEFAULT_PARSER.escape("/#{path}")
14
+ elsif path.start_with?("//?/")
15
+ # Some paths on Windows start with "//?/". This is a special prefix that allows for long file paths
16
+ DEFAULT_PARSER.escape(path.delete_prefix("//?"))
14
17
  else
15
18
  DEFAULT_PARSER.escape(path)
16
19
  end
@@ -2,79 +2,103 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyIndexer
5
- class Collector
5
+ class DeclarationListener
6
6
  extend T::Sig
7
7
 
8
- LEAVE_EVENT = T.let(Object.new.freeze, Object)
9
-
10
- sig { params(index: Index, parse_result: Prism::ParseResult, file_path: String).void }
11
- def initialize(index, parse_result, file_path)
8
+ sig do
9
+ params(index: Index, dispatcher: Prism::Dispatcher, parse_result: Prism::ParseResult, file_path: String).void
10
+ end
11
+ def initialize(index, dispatcher, parse_result, file_path)
12
12
  @index = index
13
13
  @file_path = file_path
14
- @stack = T.let([], T::Array[String])
15
14
  @comments_by_line = T.let(
16
15
  parse_result.comments.to_h do |c|
17
16
  [c.location.start_line, c]
18
17
  end,
19
18
  T::Hash[Integer, Prism::Comment],
20
19
  )
21
- @queue = T.let([], T::Array[Object])
22
- @current_owner = T.let(nil, T.nilable(Entry::Namespace))
20
+ @inside_def = T.let(false, T::Boolean)
21
+
22
+ # The nesting stack we're currently inside. Used to determine the fully qualified name of constants, but only
23
+ # stored by unresolved aliases which need the original nesting to be lazily resolved
24
+ @stack = T.let([], T::Array[String])
23
25
 
24
- super()
26
+ # A stack of namespace entries that represent where we currently are. Used to properly assign methods to an owner
27
+ @owner_stack = T.let([], T::Array[Entry::Namespace])
28
+
29
+ dispatcher.register(
30
+ self,
31
+ :on_class_node_enter,
32
+ :on_class_node_leave,
33
+ :on_module_node_enter,
34
+ :on_module_node_leave,
35
+ :on_def_node_enter,
36
+ :on_def_node_leave,
37
+ :on_call_node_enter,
38
+ :on_multi_write_node_enter,
39
+ :on_constant_path_write_node_enter,
40
+ :on_constant_path_or_write_node_enter,
41
+ :on_constant_path_operator_write_node_enter,
42
+ :on_constant_path_and_write_node_enter,
43
+ :on_constant_or_write_node_enter,
44
+ :on_constant_write_node_enter,
45
+ :on_constant_or_write_node_enter,
46
+ :on_constant_and_write_node_enter,
47
+ :on_constant_operator_write_node_enter,
48
+ )
25
49
  end
26
50
 
27
- sig { params(node: Prism::Node).void }
28
- def collect(node)
29
- @queue = [node]
30
-
31
- until @queue.empty?
32
- node_or_event = @queue.shift
33
-
34
- case node_or_event
35
- when Prism::ProgramNode
36
- @queue << node_or_event.statements
37
- when Prism::StatementsNode
38
- T.unsafe(@queue).prepend(*node_or_event.body)
39
- when Prism::ClassNode
40
- add_class_entry(node_or_event)
41
- when Prism::ModuleNode
42
- add_module_entry(node_or_event)
43
- when Prism::MultiWriteNode
44
- handle_multi_write_node(node_or_event)
45
- when Prism::ConstantPathWriteNode
46
- handle_constant_path_write_node(node_or_event)
47
- when Prism::ConstantPathOrWriteNode
48
- handle_constant_path_or_write_node(node_or_event)
49
- when Prism::ConstantPathOperatorWriteNode
50
- handle_constant_path_operator_write_node(node_or_event)
51
- when Prism::ConstantPathAndWriteNode
52
- handle_constant_path_and_write_node(node_or_event)
53
- when Prism::ConstantWriteNode
54
- handle_constant_write_node(node_or_event)
55
- when Prism::ConstantOrWriteNode
56
- name = fully_qualify_name(node_or_event.name.to_s)
57
- add_constant(node_or_event, name)
58
- when Prism::ConstantAndWriteNode
59
- name = fully_qualify_name(node_or_event.name.to_s)
60
- add_constant(node_or_event, name)
61
- when Prism::ConstantOperatorWriteNode
62
- name = fully_qualify_name(node_or_event.name.to_s)
63
- add_constant(node_or_event, name)
64
- when Prism::CallNode
65
- handle_call_node(node_or_event)
66
- when Prism::DefNode
67
- handle_def_node(node_or_event)
68
- when LEAVE_EVENT
69
- @stack.pop
70
- end
51
+ sig { params(node: Prism::ClassNode).void }
52
+ def on_class_node_enter(node)
53
+ name = node.constant_path.location.slice
54
+
55
+ comments = collect_comments(node)
56
+
57
+ superclass = node.superclass
58
+ parent_class = case superclass
59
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
60
+ superclass.slice
71
61
  end
62
+
63
+ entry = Entry::Class.new(
64
+ fully_qualify_name(name),
65
+ @file_path,
66
+ node.location,
67
+ comments,
68
+ parent_class,
69
+ )
70
+
71
+ @owner_stack << entry
72
+ @index << entry
73
+ @stack << name
72
74
  end
73
75
 
74
- private
76
+ sig { params(node: Prism::ClassNode).void }
77
+ def on_class_node_leave(node)
78
+ @stack.pop
79
+ @owner_stack.pop
80
+ end
81
+
82
+ sig { params(node: Prism::ModuleNode).void }
83
+ def on_module_node_enter(node)
84
+ name = node.constant_path.location.slice
85
+
86
+ comments = collect_comments(node)
87
+ entry = Entry::Module.new(fully_qualify_name(name), @file_path, node.location, comments)
88
+
89
+ @owner_stack << entry
90
+ @index << entry
91
+ @stack << name
92
+ end
93
+
94
+ sig { params(node: Prism::ModuleNode).void }
95
+ def on_module_node_leave(node)
96
+ @stack.pop
97
+ @owner_stack.pop
98
+ end
75
99
 
76
100
  sig { params(node: Prism::MultiWriteNode).void }
77
- def handle_multi_write_node(node)
101
+ def on_multi_write_node_enter(node)
78
102
  value = node.value
79
103
  values = value.is_a?(Prism::ArrayNode) && value.opening_loc ? value.elements : []
80
104
 
@@ -94,7 +118,7 @@ module RubyIndexer
94
118
  end
95
119
 
96
120
  sig { params(node: Prism::ConstantPathWriteNode).void }
97
- def handle_constant_path_write_node(node)
121
+ def on_constant_path_write_node_enter(node)
98
122
  # ignore variable constants like `var::FOO` or `self.class::FOO`
99
123
  target = node.target
100
124
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
@@ -104,7 +128,7 @@ module RubyIndexer
104
128
  end
105
129
 
106
130
  sig { params(node: Prism::ConstantPathOrWriteNode).void }
107
- def handle_constant_path_or_write_node(node)
131
+ def on_constant_path_or_write_node_enter(node)
108
132
  # ignore variable constants like `var::FOO` or `self.class::FOO`
109
133
  target = node.target
110
134
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
@@ -114,7 +138,7 @@ module RubyIndexer
114
138
  end
115
139
 
116
140
  sig { params(node: Prism::ConstantPathOperatorWriteNode).void }
117
- def handle_constant_path_operator_write_node(node)
141
+ def on_constant_path_operator_write_node_enter(node)
118
142
  # ignore variable constants like `var::FOO` or `self.class::FOO`
119
143
  target = node.target
120
144
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
@@ -124,7 +148,7 @@ module RubyIndexer
124
148
  end
125
149
 
126
150
  sig { params(node: Prism::ConstantPathAndWriteNode).void }
127
- def handle_constant_path_and_write_node(node)
151
+ def on_constant_path_and_write_node_enter(node)
128
152
  # ignore variable constants like `var::FOO` or `self.class::FOO`
129
153
  target = node.target
130
154
  return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
@@ -134,13 +158,31 @@ module RubyIndexer
134
158
  end
135
159
 
136
160
  sig { params(node: Prism::ConstantWriteNode).void }
137
- def handle_constant_write_node(node)
161
+ def on_constant_write_node_enter(node)
162
+ name = fully_qualify_name(node.name.to_s)
163
+ add_constant(node, name)
164
+ end
165
+
166
+ sig { params(node: Prism::ConstantOrWriteNode).void }
167
+ def on_constant_or_write_node_enter(node)
168
+ name = fully_qualify_name(node.name.to_s)
169
+ add_constant(node, name)
170
+ end
171
+
172
+ sig { params(node: Prism::ConstantAndWriteNode).void }
173
+ def on_constant_and_write_node_enter(node)
174
+ name = fully_qualify_name(node.name.to_s)
175
+ add_constant(node, name)
176
+ end
177
+
178
+ sig { params(node: Prism::ConstantOperatorWriteNode).void }
179
+ def on_constant_operator_write_node_enter(node)
138
180
  name = fully_qualify_name(node.name.to_s)
139
181
  add_constant(node, name)
140
182
  end
141
183
 
142
184
  sig { params(node: Prism::CallNode).void }
143
- def handle_call_node(node)
185
+ def on_call_node_enter(node)
144
186
  message = node.name
145
187
 
146
188
  case message
@@ -153,14 +195,15 @@ module RubyIndexer
153
195
  when :attr_accessor
154
196
  handle_attribute(node, reader: true, writer: true)
155
197
  when :include
156
- handle_include(node)
198
+ handle_module_operation(node, :included_modules)
157
199
  when :prepend
158
- handle_prepend(node)
200
+ handle_module_operation(node, :prepended_modules)
159
201
  end
160
202
  end
161
203
 
162
204
  sig { params(node: Prism::DefNode).void }
163
- def handle_def_node(node)
205
+ def on_def_node_enter(node)
206
+ @inside_def = true
164
207
  method_name = node.name.to_s
165
208
  comments = collect_comments(node)
166
209
  case node.receiver
@@ -171,7 +214,7 @@ module RubyIndexer
171
214
  node.location,
172
215
  comments,
173
216
  node.parameters,
174
- @current_owner,
217
+ @owner_stack.last,
175
218
  )
176
219
  when Prism::SelfNode
177
220
  @index << Entry::SingletonMethod.new(
@@ -180,11 +223,18 @@ module RubyIndexer
180
223
  node.location,
181
224
  comments,
182
225
  node.parameters,
183
- @current_owner,
226
+ @owner_stack.last,
184
227
  )
185
228
  end
186
229
  end
187
230
 
231
+ sig { params(node: Prism::DefNode).void }
232
+ def on_def_node_leave(node)
233
+ @inside_def = false
234
+ end
235
+
236
+ private
237
+
188
238
  sig { params(node: Prism::CallNode).void }
189
239
  def handle_private_constant(node)
190
240
  arguments = node.arguments&.arguments
@@ -240,62 +290,16 @@ module RubyIndexer
240
290
 
241
291
  # If the right hand side is another constant assignment, we need to visit it because that constant has to be
242
292
  # indexed too
243
- @queue.prepend(value)
244
293
  Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
245
294
  when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
246
295
  Prism::ConstantPathAndWriteNode
247
296
 
248
- @queue.prepend(value)
249
297
  Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
250
298
  else
251
299
  Entry::Constant.new(name, @file_path, node.location, comments)
252
300
  end
253
301
  end
254
302
 
255
- sig { params(node: Prism::ModuleNode).void }
256
- def add_module_entry(node)
257
- name = node.constant_path.location.slice
258
- unless /^[A-Z:]/.match?(name)
259
- @queue << node.body
260
- return
261
- end
262
-
263
- comments = collect_comments(node)
264
- @current_owner = Entry::Module.new(fully_qualify_name(name), @file_path, node.location, comments)
265
- @index << @current_owner
266
- @stack << name
267
- @queue.prepend(node.body, LEAVE_EVENT)
268
- end
269
-
270
- sig { params(node: Prism::ClassNode).void }
271
- def add_class_entry(node)
272
- name = node.constant_path.location.slice
273
-
274
- unless /^[A-Z:]/.match?(name)
275
- @queue << node.body
276
- return
277
- end
278
-
279
- comments = collect_comments(node)
280
-
281
- superclass = node.superclass
282
- parent_class = case superclass
283
- when Prism::ConstantReadNode, Prism::ConstantPathNode
284
- superclass.slice
285
- end
286
-
287
- @current_owner = Entry::Class.new(
288
- fully_qualify_name(name),
289
- @file_path,
290
- node.location,
291
- comments,
292
- parent_class,
293
- )
294
- @index << @current_owner
295
- @stack << name
296
- @queue.prepend(node.body, LEAVE_EVENT)
297
- end
298
-
299
303
  sig { params(node: Prism::Node).returns(T::Array[String]) }
300
304
  def collect_comments(node)
301
305
  comments = []
@@ -350,24 +354,17 @@ module RubyIndexer
350
354
 
351
355
  next unless name && loc
352
356
 
353
- @index << Entry::Accessor.new(name, @file_path, loc, comments, @current_owner) if reader
354
- @index << Entry::Accessor.new("#{name}=", @file_path, loc, comments, @current_owner) if writer
357
+ @index << Entry::Accessor.new(name, @file_path, loc, comments, @owner_stack.last) if reader
358
+ @index << Entry::Accessor.new("#{name}=", @file_path, loc, comments, @owner_stack.last) if writer
355
359
  end
356
360
  end
357
361
 
358
- sig { params(node: Prism::CallNode).void }
359
- def handle_include(node)
360
- handle_module_operation(node, :included_modules)
361
- end
362
-
363
- sig { params(node: Prism::CallNode).void }
364
- def handle_prepend(node)
365
- handle_module_operation(node, :prepended_modules)
366
- end
367
-
368
362
  sig { params(node: Prism::CallNode, operation: Symbol).void }
369
363
  def handle_module_operation(node, operation)
370
- return unless @current_owner
364
+ return if @inside_def
365
+
366
+ owner = @owner_stack.last
367
+ return unless owner
371
368
 
372
369
  arguments = node.arguments&.arguments
373
370
  return unless arguments
@@ -381,7 +378,7 @@ module RubyIndexer
381
378
  # If a constant path reference is dynamic or missing parts, we can't
382
379
  # index it
383
380
  end
384
- collection = operation == :included_modules ? @current_owner.included_modules : @current_owner.prepended_modules
381
+ collection = operation == :included_modules ? owner.included_modules : owner.prepended_modules
385
382
  collection.concat(names)
386
383
  end
387
384
  end
@@ -185,9 +185,11 @@ module RubyIndexer
185
185
  sig { params(indexable_path: IndexablePath, source: T.nilable(String)).void }
186
186
  def index_single(indexable_path, source = nil)
187
187
  content = source || File.read(indexable_path.full_path)
188
+ dispatcher = Prism::Dispatcher.new
189
+
188
190
  result = Prism.parse(content)
189
- collector = Collector.new(self, result, indexable_path.full_path)
190
- collector.collect(result.value)
191
+ DeclarationListener.new(self, dispatcher, result, indexable_path.full_path)
192
+ dispatcher.dispatch(result.value)
191
193
 
192
194
  require_path = indexable_path.require_path
193
195
  @require_paths_tree.insert(require_path, indexable_path) if require_path
@@ -5,7 +5,7 @@ require "yaml"
5
5
  require "did_you_mean"
6
6
 
7
7
  require "ruby_indexer/lib/ruby_indexer/indexable_path"
8
- require "ruby_indexer/lib/ruby_indexer/collector"
8
+ require "ruby_indexer/lib/ruby_indexer/declaration_listener"
9
9
  require "ruby_indexer/lib/ruby_indexer/index"
10
10
  require "ruby_indexer/lib/ruby_indexer/entry"
11
11
  require "ruby_indexer/lib/ruby_indexer/configuration"
@@ -14,6 +14,15 @@ module RubyIndexer
14
14
  assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
15
15
  end
16
16
 
17
+ def test_conditional_class
18
+ index(<<~RUBY)
19
+ class Foo
20
+ end if condition
21
+ RUBY
22
+
23
+ assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
24
+ end
25
+
17
26
  def test_class_with_statements
18
27
  index(<<~RUBY)
19
28
  class Foo
@@ -60,7 +69,23 @@ module RubyIndexer
60
69
  end
61
70
  RUBY
62
71
 
72
+ assert_entry("self::Bar", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
73
+ end
74
+
75
+ def test_dynamically_namespaced_class_doesnt_affect_other_classes
76
+ index(<<~RUBY)
77
+ class Foo
78
+ class self::Bar
79
+ end
80
+
81
+ class Bar
82
+ end
83
+ end
84
+ RUBY
85
+
63
86
  refute_entry("self::Bar")
87
+ assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:6-3")
88
+ assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:4-2:5-5")
64
89
  end
65
90
 
66
91
  def test_empty_statements_module
@@ -72,6 +97,15 @@ module RubyIndexer
72
97
  assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
73
98
  end
74
99
 
100
+ def test_conditional_module
101
+ index(<<~RUBY)
102
+ module Foo
103
+ end if condition
104
+ RUBY
105
+
106
+ assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
107
+ end
108
+
75
109
  def test_module_with_statements
76
110
  index(<<~RUBY)
77
111
  module Foo
@@ -106,7 +140,23 @@ module RubyIndexer
106
140
  end
107
141
  RUBY
108
142
 
109
- refute_entry("self::Bar")
143
+ assert_entry("self::Bar", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
144
+ end
145
+
146
+ def test_dynamically_namespaced_module_doesnt_affect_other_modules
147
+ index(<<~RUBY)
148
+ module Foo
149
+ class self::Bar
150
+ end
151
+
152
+ module Bar
153
+ end
154
+ end
155
+ RUBY
156
+
157
+ assert_entry("Foo::self::Bar", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
158
+ assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:6-3")
159
+ assert_entry("Foo::Bar", Entry::Module, "/fake/path/foo.rb:4-2:5-5")
110
160
  end
111
161
 
112
162
  def test_nested_modules_and_classes
@@ -12,10 +12,13 @@ module RubyIndexer
12
12
  class ::Bar
13
13
  FOO = 2
14
14
  end
15
+
16
+ BAR = 3 if condition
15
17
  RUBY
16
18
 
17
19
  assert_entry("FOO", Entry::Constant, "/fake/path/foo.rb:0-0:0-7")
18
20
  assert_entry("Bar::FOO", Entry::Constant, "/fake/path/foo.rb:3-2:3-9")
21
+ assert_entry("BAR", Entry::Constant, "/fake/path/foo.rb:6-0:6-7")
19
22
  end
20
23
 
21
24
  def test_constant_or_writes
@@ -16,6 +16,17 @@ module RubyIndexer
16
16
  assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
17
17
  end
18
18
 
19
+ def test_conditional_method
20
+ index(<<~RUBY)
21
+ class Foo
22
+ def bar
23
+ end if condition
24
+ end
25
+ RUBY
26
+
27
+ assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
28
+ end
29
+
19
30
  def test_singleton_method_using_self_receiver
20
31
  index(<<~RUBY)
21
32
  class Foo
@@ -38,6 +49,28 @@ module RubyIndexer
38
49
  assert_no_entry("bar")
39
50
  end
40
51
 
52
+ def test_method_under_dynamic_class_or_module
53
+ index(<<~RUBY)
54
+ module Foo
55
+ class self::Bar
56
+ def bar
57
+ end
58
+ end
59
+ end
60
+
61
+ module Bar
62
+ def bar
63
+ end
64
+ end
65
+ RUBY
66
+
67
+ assert_equal(2, @index["bar"].length)
68
+ first_entry = T.must(@index["bar"].first)
69
+ assert_equal("Foo::self::Bar", first_entry.owner.name)
70
+ second_entry = T.must(@index["bar"].last)
71
+ assert_equal("Bar", second_entry.owner.name)
72
+ end
73
+
41
74
  def test_method_with_parameters
42
75
  index(<<~RUBY)
43
76
  class Foo
@@ -285,5 +318,28 @@ module RubyIndexer
285
318
 
286
319
  assert_no_entry("bar")
287
320
  end
321
+
322
+ def test_properly_tracks_multiple_levels_of_nesting
323
+ index(<<~RUBY)
324
+ module Foo
325
+ def first; end
326
+
327
+ module Bar
328
+ def second; end
329
+ end
330
+
331
+ def third; end
332
+ end
333
+ RUBY
334
+
335
+ entry = T.must(@index["first"]&.first)
336
+ assert_equal("Foo", T.must(entry.owner).name)
337
+
338
+ entry = T.must(@index["second"]&.first)
339
+ assert_equal("Foo::Bar", T.must(entry.owner).name)
340
+
341
+ entry = T.must(@index["third"]&.first)
342
+ assert_equal("Foo", T.must(entry.owner).name)
343
+ end
288
344
  end
289
345
  end
@@ -42,6 +42,8 @@ module RubyLsp
42
42
  @group_stack = T.let([], T::Array[String])
43
43
  @group_id = T.let(1, Integer)
44
44
  @group_id_stack = T.let([], T::Array[Integer])
45
+ # We want to avoid adding code lenses for nested definitions
46
+ @def_depth = T.let(0, Integer)
45
47
 
46
48
  dispatcher.register(
47
49
  self,
@@ -50,6 +52,7 @@ module RubyLsp
50
52
  :on_module_node_enter,
51
53
  :on_module_node_leave,
52
54
  :on_def_node_enter,
55
+ :on_def_node_leave,
53
56
  :on_call_node_enter,
54
57
  :on_call_node_leave,
55
58
  )
@@ -88,6 +91,9 @@ module RubyLsp
88
91
 
89
92
  sig { params(node: Prism::DefNode).void }
90
93
  def on_def_node_enter(node)
94
+ @def_depth += 1
95
+ return if @def_depth > 1
96
+
91
97
  class_name = @group_stack.last
92
98
  return unless class_name&.end_with?("Test")
93
99
 
@@ -105,6 +111,11 @@ module RubyLsp
105
111
  end
106
112
  end
107
113
 
114
+ sig { params(node: Prism::DefNode).void }
115
+ def on_def_node_leave(node)
116
+ @def_depth -= 1
117
+ end
118
+
108
119
  sig { params(node: Prism::ModuleNode).void }
109
120
  def on_module_node_enter(node)
110
121
  if (path = namespace_constant_name(node))
@@ -47,7 +47,7 @@ module RubyLsp
47
47
  @response_builder << build_entry_completion(
48
48
  complete_name,
49
49
  name,
50
- node,
50
+ range_from_location(node.location),
51
51
  entries,
52
52
  top_level?(complete_name),
53
53
  )
@@ -62,6 +62,53 @@ module RubyLsp
62
62
  name = constant_name(node)
63
63
  return if name.nil?
64
64
 
65
+ constant_path_completion(name, range_from_location(node.location))
66
+ end
67
+
68
+ sig { params(node: Prism::CallNode).void }
69
+ def on_call_node_enter(node)
70
+ receiver = node.receiver
71
+
72
+ # When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke singleton
73
+ # methods). However, in addition to providing method completion, we also need to show possible constant
74
+ # completions
75
+ if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
76
+ node.call_operator == "::"
77
+
78
+ name = constant_name(receiver)
79
+
80
+ if name
81
+ start_loc = node.location
82
+ end_loc = T.must(node.call_operator_loc)
83
+
84
+ constant_path_completion(
85
+ "#{name}::",
86
+ Interface::Range.new(
87
+ start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
88
+ end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
89
+ ),
90
+ )
91
+ return
92
+ end
93
+ end
94
+
95
+ name = node.message
96
+ return unless name
97
+
98
+ case name
99
+ when "require"
100
+ complete_require(node)
101
+ when "require_relative"
102
+ complete_require_relative(node)
103
+ else
104
+ complete_self_receiver_method(node, name) if !@typechecker_enabled && self_receiver?(node)
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ sig { params(name: String, range: Interface::Range).void }
111
+ def constant_path_completion(name, range)
65
112
  top_level_reference = if name.start_with?("::")
66
113
  name = name.delete_prefix("::")
67
114
  true
@@ -71,8 +118,13 @@ module RubyLsp
71
118
 
72
119
  # If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
73
120
  # order to find which possible constants match the desired search
74
- *namespace, incomplete_name = name.split("::")
75
- aliased_namespace = T.must(namespace).join("::")
121
+ aliased_namespace = if name.end_with?("::")
122
+ name.delete_suffix("::")
123
+ else
124
+ *namespace, incomplete_name = name.split("::")
125
+ T.must(namespace).join("::")
126
+ end
127
+
76
128
  namespace_entries = @index.resolve(aliased_namespace, @nesting)
77
129
  return unless namespace_entries
78
130
 
@@ -88,37 +140,19 @@ module RubyLsp
88
140
  first_entry = T.must(entries.first)
89
141
  next if first_entry.visibility == :private && !first_entry.name.start_with?("#{@nesting}::")
90
142
 
91
- constant_name = T.must(first_entry.name.split("::").last)
92
-
143
+ constant_name = first_entry.name.delete_prefix("#{real_namespace}::")
93
144
  full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
94
145
 
95
146
  @response_builder << build_entry_completion(
96
147
  full_name,
97
148
  name,
98
- node,
149
+ range,
99
150
  entries,
100
151
  top_level_reference || top_level?(T.must(entries.first).name),
101
152
  )
102
153
  end
103
154
  end
104
155
 
105
- sig { params(node: Prism::CallNode).void }
106
- def on_call_node_enter(node)
107
- name = node.message
108
- return unless name
109
-
110
- case name
111
- when "require"
112
- complete_require(node)
113
- when "require_relative"
114
- complete_require_relative(node)
115
- else
116
- complete_self_receiver_method(node, name) if !@typechecker_enabled && self_receiver?(node)
117
- end
118
- end
119
-
120
- private
121
-
122
156
  sig { params(node: Prism::CallNode).void }
123
157
  def complete_require(node)
124
158
  arguments_node = node.arguments
@@ -223,12 +257,12 @@ module RubyLsp
223
257
  params(
224
258
  real_name: String,
225
259
  incomplete_name: String,
226
- node: Prism::Node,
260
+ range: Interface::Range,
227
261
  entries: T::Array[RubyIndexer::Entry],
228
262
  top_level: T::Boolean,
229
263
  ).returns(Interface::CompletionItem)
230
264
  end
231
- def build_entry_completion(real_name, incomplete_name, node, entries, top_level)
265
+ def build_entry_completion(real_name, incomplete_name, range, entries, top_level)
232
266
  first_entry = T.must(entries.first)
233
267
  kind = case first_entry
234
268
  when RubyIndexer::Entry::Class
@@ -263,20 +297,22 @@ module RubyLsp
263
297
  #
264
298
  # Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
265
299
  # end
266
- @nesting.each do |namespace|
267
- prefix = "#{namespace}::"
268
- shortened_name = insertion_text.delete_prefix(prefix)
269
-
270
- # If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
271
- conflict_name = "#{@nesting.join("::")}::#{shortened_name}"
272
- break if real_name != conflict_name && @index[conflict_name]
273
-
274
- insertion_text = shortened_name
275
-
276
- # If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
277
- # `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in their
278
- # typing
279
- filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
300
+ unless @nesting.join("::").start_with?(incomplete_name)
301
+ @nesting.each do |namespace|
302
+ prefix = "#{namespace}::"
303
+ shortened_name = insertion_text.delete_prefix(prefix)
304
+
305
+ # If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
306
+ conflict_name = "#{@nesting.join("::")}::#{shortened_name}"
307
+ break if real_name != conflict_name && @index[conflict_name]
308
+
309
+ insertion_text = shortened_name
310
+
311
+ # If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
312
+ # `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in
313
+ # their typing
314
+ filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
315
+ end
280
316
  end
281
317
 
282
318
  # When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
@@ -286,7 +322,7 @@ module RubyLsp
286
322
  label: real_name,
287
323
  filter_text: filter_text,
288
324
  text_edit: Interface::TextEdit.new(
289
- range: range_from_node(node),
325
+ range: range,
290
326
  new_text: insertion_text,
291
327
  ),
292
328
  kind: kind,
@@ -30,6 +30,7 @@ module RubyLsp
30
30
  dispatcher.register(
31
31
  self,
32
32
  :on_call_node_enter,
33
+ :on_block_argument_node_enter,
33
34
  :on_constant_read_node_enter,
34
35
  :on_constant_path_node_enter,
35
36
  )
@@ -42,10 +43,21 @@ module RubyLsp
42
43
  if message == :require || message == :require_relative
43
44
  handle_require_definition(node)
44
45
  else
45
- handle_method_definition(node)
46
+ handle_method_definition(message.to_s, self_receiver?(node))
46
47
  end
47
48
  end
48
49
 
50
+ sig { params(node: Prism::BlockArgumentNode).void }
51
+ def on_block_argument_node_enter(node)
52
+ expression = node.expression
53
+ return unless expression.is_a?(Prism::SymbolNode)
54
+
55
+ value = expression.value
56
+ return unless value
57
+
58
+ handle_method_definition(value, false)
59
+ end
60
+
49
61
  sig { params(node: Prism::ConstantPathNode).void }
50
62
  def on_constant_path_node_enter(node)
51
63
  name = constant_name(node)
@@ -64,12 +76,9 @@ module RubyLsp
64
76
 
65
77
  private
66
78
 
67
- sig { params(node: Prism::CallNode).void }
68
- def handle_method_definition(node)
69
- message = node.message
70
- return unless message
71
-
72
- methods = if self_receiver?(node)
79
+ sig { params(message: String, self_receiver: T::Boolean).void }
80
+ def handle_method_definition(message, self_receiver)
81
+ methods = if self_receiver
73
82
  @index.resolve_method(message, @nesting.join("::"))
74
83
  else
75
84
  # If the method doesn't have a receiver, then we provide a few candidates to jump to
@@ -271,7 +271,7 @@ module RubyLsp
271
271
  def on_constant_path_node_enter(node)
272
272
  return unless matches?(node, CONSTANT_PATH_NODES)
273
273
 
274
- add_highlight(Constant::DocumentHighlightKind::READ, node.location)
274
+ add_highlight(Constant::DocumentHighlightKind::READ, node.name_loc)
275
275
  end
276
276
 
277
277
  sig { params(node: Prism::ConstantReadNode).void }
@@ -30,7 +30,7 @@ module RubyLsp
30
30
  lookup[spec.name] = {}
31
31
  lookup[spec.name][spec.version.to_s] = {}
32
32
 
33
- Dir.glob("**/*.rb", base: "#{spec.full_gem_path}/").each do |path|
33
+ Dir.glob("**/*.rb", base: "#{spec.full_gem_path.delete_prefix("//?/")}/").each do |path|
34
34
  lookup[spec.name][spec.version.to_s][path] = "#{spec.full_gem_path}/#{path}"
35
35
  end
36
36
  end
@@ -58,6 +58,7 @@ module RubyLsp
58
58
  :on_constant_operator_write_node_enter,
59
59
  :on_constant_or_write_node_enter,
60
60
  :on_constant_target_node_enter,
61
+ :on_constant_path_node_enter,
61
62
  :on_local_variable_and_write_node_enter,
62
63
  :on_local_variable_operator_write_node_enter,
63
64
  :on_local_variable_or_write_node_enter,
@@ -302,17 +303,64 @@ module RubyLsp
302
303
  def on_class_node_enter(node)
303
304
  return unless visible?(node, @range)
304
305
 
305
- @response_builder.add_token(node.constant_path.location, :class, [:declaration])
306
+ constant_path = node.constant_path
307
+
308
+ if constant_path.is_a?(Prism::ConstantReadNode)
309
+ @response_builder.add_token(constant_path.location, :class, [:declaration])
310
+ else
311
+ each_constant_path_part(constant_path) do |part|
312
+ loc = case part
313
+ when Prism::ConstantPathNode
314
+ part.name_loc
315
+ when Prism::ConstantReadNode
316
+ part.location
317
+ end
318
+ next unless loc
319
+
320
+ @response_builder.add_token(loc, :class, [:declaration])
321
+ end
322
+ end
306
323
 
307
324
  superclass = node.superclass
308
- @response_builder.add_token(superclass.location, :class) if superclass
325
+
326
+ if superclass.is_a?(Prism::ConstantReadNode)
327
+ @response_builder.add_token(superclass.location, :class)
328
+ elsif superclass
329
+ each_constant_path_part(superclass) do |part|
330
+ loc = case part
331
+ when Prism::ConstantPathNode
332
+ part.name_loc
333
+ when Prism::ConstantReadNode
334
+ part.location
335
+ end
336
+ next unless loc
337
+
338
+ @response_builder.add_token(loc, :class)
339
+ end
340
+ end
309
341
  end
310
342
 
311
343
  sig { params(node: Prism::ModuleNode).void }
312
344
  def on_module_node_enter(node)
313
345
  return unless visible?(node, @range)
314
346
 
315
- @response_builder.add_token(node.constant_path.location, :namespace, [:declaration])
347
+ constant_path = node.constant_path
348
+
349
+ if constant_path.is_a?(Prism::ConstantReadNode)
350
+ @response_builder.add_token(constant_path.location, :namespace, [:declaration])
351
+ else
352
+ each_constant_path_part(constant_path) do |part|
353
+ loc = case part
354
+ when Prism::ConstantPathNode
355
+ part.name_loc
356
+ when Prism::ConstantReadNode
357
+ part.location
358
+ end
359
+ next unless loc
360
+
361
+ @response_builder.add_token(loc, :namespace, [:declaration])
362
+ end
363
+ end
316
364
  end
317
365
 
318
366
  sig { params(node: Prism::ImplicitNode).void }
@@ -327,6 +375,14 @@ module RubyLsp
327
375
  @inside_implicit_node = false
328
376
  end
329
377
 
378
+ sig { params(node: Prism::ConstantPathNode).void }
379
+ def on_constant_path_node_enter(node)
380
+ return if @inside_implicit_node
381
+ return unless visible?(node, @range)
382
+
383
+ @response_builder.add_token(node.name_loc, :namespace)
384
+ end
385
+
330
386
  private
331
387
 
332
388
  # Textmate provides highlighting for a subset of these special Ruby-specific methods. We want to utilize that
@@ -11,6 +11,7 @@ module RubyLsp
11
11
  # suggests possible completions according to what the developer is typing.
12
12
  #
13
13
  # Currently supported targets:
14
+ #
14
15
  # - Classes
15
16
  # - Modules
16
17
  # - Constants
@@ -34,7 +35,7 @@ module RubyLsp
34
35
  def provider
35
36
  Interface::CompletionOptions.new(
36
37
  resolve_provider: true,
37
- trigger_characters: ["/", "\"", "'"],
38
+ trigger_characters: ["/", "\"", "'", ":"],
38
39
  completion_item: {
39
40
  labelDetailsSupport: true,
40
41
  },
@@ -36,20 +36,27 @@ module RubyLsp
36
36
  @item = item
37
37
  end
38
38
 
39
- sig { override.returns(Interface::CompletionItem) }
39
+ sig { override.returns(T::Hash[Symbol, T.untyped]) }
40
40
  def perform
41
+ # Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
42
+ # a completion resolve request must always return the original completion item without modifying ANY fields
43
+ # other than label details and documentation. If we modify anything, the completion behaviour might be broken.
44
+ #
45
+ # For example, forgetting to return the `insertText` included in the original item will make the editor use the
46
+ # `label` for the text edit instead
41
47
  label = @item[:label]
42
48
  entries = @index[label] || []
43
- Interface::CompletionItem.new(
44
- label: label,
45
- label_details: Interface::CompletionItemLabelDetails.new(
46
- description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
47
- ),
48
- documentation: Interface::MarkupContent.new(
49
- kind: "markdown",
50
- value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
51
- ),
49
+
50
+ @item[:labelDetails] = Interface::CompletionItemLabelDetails.new(
51
+ description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
52
+ )
53
+
54
+ @item[:documentation] = Interface::MarkupContent.new(
55
+ kind: "markdown",
56
+ value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
52
57
  )
58
+
59
+ @item
53
60
  end
54
61
  end
55
62
  end
@@ -12,6 +12,7 @@ module RubyLsp
12
12
  # definition of the symbol under the cursor.
13
13
  #
14
14
  # Currently supported targets:
15
+ #
15
16
  # - Classes
16
17
  # - Modules
17
18
  # - Constants
@@ -47,7 +48,7 @@ module RubyLsp
47
48
 
48
49
  target, parent, nesting = document.locate_node(
49
50
  position,
50
- node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode],
51
+ node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::BlockArgumentNode],
51
52
  )
52
53
 
53
54
  if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
@@ -50,12 +50,12 @@ module RubyLsp
50
50
  params(
51
51
  node: Prism::Node,
52
52
  title: String,
53
- command_name: String,
53
+ command_name: T.nilable(String),
54
54
  arguments: T.nilable(T::Array[T.untyped]),
55
55
  data: T.nilable(T::Hash[T.untyped, T.untyped]),
56
56
  ).returns(Interface::CodeLens)
57
57
  end
58
- def create_code_lens(node, title:, command_name:, arguments:, data:)
58
+ def create_code_lens(node, title:, command_name: nil, arguments: nil, data: nil)
59
59
  range = range_from_node(node)
60
60
 
61
61
  Interface::CodeLens.new(
@@ -167,6 +167,24 @@ module RubyLsp
167
167
  constant_name(path)
168
168
  end
169
169
  end
170
+
171
+ # Iterates over each part of a constant path, so that we can easily push response items for each section of the
172
+ # name. For example, for `Foo::Bar::Baz`, this method will invoke the block with `Foo`, then `Bar` and finally
173
+ # `Baz`.
174
+ sig do
175
+ params(
176
+ node: Prism::Node,
177
+ block: T.proc.params(part: Prism::Node).void,
178
+ ).void
179
+ end
180
+ def each_constant_path_part(node, &block)
181
+ current = T.let(node, T.nilable(Prism::Node))
182
+
183
+ while current.is_a?(Prism::ConstantPathNode)
184
+ block.call(current)
185
+ current = current.parent
186
+ end
187
+ end
170
188
  end
171
189
  end
172
190
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.6
4
+ version: 0.16.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-26 00:00:00.000000000 Z
11
+ date: 2024-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -30,20 +30,20 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.23.0
33
+ version: 0.29.0
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: '0.28'
36
+ version: '0.30'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 0.23.0
43
+ version: 0.29.0
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.28'
46
+ version: '0.30'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sorbet-runtime
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -78,8 +78,8 @@ files:
78
78
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
79
79
  - lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
80
80
  - lib/ruby-lsp.rb
81
- - lib/ruby_indexer/lib/ruby_indexer/collector.rb
82
81
  - lib/ruby_indexer/lib/ruby_indexer/configuration.rb
82
+ - lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb
83
83
  - lib/ruby_indexer/lib/ruby_indexer/entry.rb
84
84
  - lib/ruby_indexer/lib/ruby_indexer/index.rb
85
85
  - lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb
@@ -178,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
178
  - !ruby/object:Gem::Version
179
179
  version: '0'
180
180
  requirements: []
181
- rubygems_version: 3.5.9
181
+ rubygems_version: 3.5.10
182
182
  signing_key:
183
183
  specification_version: 4
184
184
  summary: An opinionated language server for Ruby