ruby-lsp 0.21.2 → 0.22.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03751a627d3f3b8d671f72c7533b14c5b74b21846fa8be6402a7d2e20f9cdefc
4
- data.tar.gz: 948d342270018a4b92868cd0fe284cf7664a630c567c640a5c1c1e50c079e7a9
3
+ metadata.gz: e48cef95e466e2a75943921fc08ede1a449e07096e44078ca063bdbee6d3c54b
4
+ data.tar.gz: c0ce6a0636645674e9e9a87219124b622b1280420c14c4bd12f8f0f0aafc3f32
5
5
  SHA512:
6
- metadata.gz: 42f13540d7a8bf5b87f85ea00a93581980c85ae662adbc5cbb62a180d1195fc0e701dae9dc329d36d6ae15a1c422ce9dd39b5af4ff06e10af6c1bc3608787d8d
7
- data.tar.gz: b16dc974e8e6437d031763924d5fd6acfe4b2b028e7155ca7fb674585916cc805ea5285c1d849b8a0b6cdc958641e0689d75273d7ad67f0267a0fe6dfba55272
6
+ metadata.gz: 89d02237ecce7e784bb5275ba4d5fc30b64981ea337b078f3117037b98d8f734e0d7e6266078e76023125dd52bbcb6ded8d04fdac1c29eb8292fd29e401cece4
7
+ data.tar.gz: 7245d82a481bd9e6a56d0cce9ad03796bd1a6d9e770beb5632fcaf7669ab0af9e9c0c95f0b671d844231af718add21b98011665ac3355fab5647ab12378b60bf
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.21.2
1
+ 0.22.0
data/exe/ruby-lsp CHANGED
@@ -54,7 +54,7 @@ rescue OptionParser::InvalidOption => e
54
54
  exit(1)
55
55
  end
56
56
 
57
- # When we're running without bundler, then we need to make sure the custom bundle is fully configured and re-execute
57
+ # When we're running without bundler, then we need to make sure the composed bundle is fully configured and re-execute
58
58
  # using `BUNDLE_GEMFILE=.ruby-lsp/Gemfile bundle exec ruby-lsp` so that we have access to the gems that are a part of
59
59
  # the application's bundle
60
60
  if ENV["BUNDLE_GEMFILE"].nil?
@@ -74,9 +74,6 @@ rescue StandardError => e
74
74
  # If Bundler.setup fails, we need to restore the original $LOAD_PATH so that we can still require the Ruby LSP server
75
75
  # in degraded mode
76
76
  $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
77
- ensure
78
- require "fileutils"
79
- FileUtils.rm(bundle_env_path) if File.exist?(bundle_env_path)
80
77
  end
81
78
 
82
79
  error_path = File.join(".ruby-lsp", "install_error")
@@ -109,6 +109,12 @@ module RubyIndexer
109
109
 
110
110
  indexables = T.let([], T::Array[IndexablePath])
111
111
 
112
+ # Handle top level files separately. The path below is an optimization to prevent descending down directories that
113
+ # are going to be excluded anyway, so we need to handle top level scripts separately
114
+ Dir.glob(File.join(@workspace_path, "*.rb"), flags).each do |path|
115
+ indexables << IndexablePath.new(nil, path)
116
+ end
117
+
112
118
  # Add user specified patterns
113
119
  @included_patterns.each do |pattern|
114
120
  load_path_entry = T.let(nil, T.nilable(String))
@@ -18,13 +18,12 @@ module RubyIndexer
18
18
  parse_result: Prism::ParseResult,
19
19
  file_path: String,
20
20
  collect_comments: T::Boolean,
21
- enhancements: T::Array[Enhancement],
22
21
  ).void
23
22
  end
24
- def initialize(index, dispatcher, parse_result, file_path, collect_comments: false, enhancements: [])
23
+ def initialize(index, dispatcher, parse_result, file_path, collect_comments: false)
25
24
  @index = index
26
25
  @file_path = file_path
27
- @enhancements = enhancements
26
+ @enhancements = T.let(Enhancement.all(self), T::Array[Enhancement])
28
27
  @visibility_stack = T.let([Entry::Visibility::PUBLIC], T::Array[Entry::Visibility])
29
28
  @comments_by_line = T.let(
30
29
  parse_result.comments.to_h do |c|
@@ -37,6 +36,7 @@ module RubyIndexer
37
36
  parse_result.code_units_cache(@index.configuration.encoding),
38
37
  T.any(T.proc.params(arg0: Integer).returns(Integer), Prism::CodeUnitsCache),
39
38
  )
39
+ @source_lines = T.let(parse_result.source.lines, T::Array[String])
40
40
 
41
41
  # The nesting stack we're currently inside. Used to determine the fully qualified name of constants, but only
42
42
  # stored by unresolved aliases which need the original nesting to be lazily resolved
@@ -85,15 +85,9 @@ module RubyIndexer
85
85
 
86
86
  sig { params(node: Prism::ClassNode).void }
87
87
  def on_class_node_enter(node)
88
- @visibility_stack.push(Entry::Visibility::PUBLIC)
89
88
  constant_path = node.constant_path
90
- name = constant_path.slice
91
-
92
- comments = collect_comments(node)
93
-
94
89
  superclass = node.superclass
95
-
96
- nesting = actual_nesting(name)
90
+ nesting = actual_nesting(constant_path.slice)
97
91
 
98
92
  parent_class = case superclass
99
93
  when Prism::ConstantReadNode, Prism::ConstantPathNode
@@ -112,53 +106,29 @@ module RubyIndexer
112
106
  end
113
107
  end
114
108
 
115
- entry = Entry::Class.new(
109
+ add_class(
116
110
  nesting,
117
- @file_path,
118
- Location.from_prism_location(node.location, @code_units_cache),
119
- Location.from_prism_location(constant_path.location, @code_units_cache),
120
- comments,
121
- parent_class,
111
+ node.location,
112
+ constant_path.location,
113
+ parent_class_name: parent_class,
114
+ comments: collect_comments(node),
122
115
  )
123
-
124
- @owner_stack << entry
125
- @index.add(entry)
126
- @stack << name
127
116
  end
128
117
 
129
118
  sig { params(node: Prism::ClassNode).void }
130
119
  def on_class_node_leave(node)
131
- @stack.pop
132
- @owner_stack.pop
133
- @visibility_stack.pop
120
+ pop_namespace_stack
134
121
  end
135
122
 
136
123
  sig { params(node: Prism::ModuleNode).void }
137
124
  def on_module_node_enter(node)
138
- @visibility_stack.push(Entry::Visibility::PUBLIC)
139
125
  constant_path = node.constant_path
140
- name = constant_path.slice
141
-
142
- comments = collect_comments(node)
143
-
144
- entry = Entry::Module.new(
145
- actual_nesting(name),
146
- @file_path,
147
- Location.from_prism_location(node.location, @code_units_cache),
148
- Location.from_prism_location(constant_path.location, @code_units_cache),
149
- comments,
150
- )
151
-
152
- @owner_stack << entry
153
- @index.add(entry)
154
- @stack << name
126
+ add_module(constant_path.slice, node.location, constant_path.location, comments: collect_comments(node))
155
127
  end
156
128
 
157
129
  sig { params(node: Prism::ModuleNode).void }
158
130
  def on_module_node_leave(node)
159
- @stack.pop
160
- @owner_stack.pop
161
- @visibility_stack.pop
131
+ pop_namespace_stack
162
132
  end
163
133
 
164
134
  sig { params(node: Prism::SingletonClassNode).void }
@@ -200,9 +170,7 @@ module RubyIndexer
200
170
 
201
171
  sig { params(node: Prism::SingletonClassNode).void }
202
172
  def on_singleton_class_node_leave(node)
203
- @stack.pop
204
- @owner_stack.pop
205
- @visibility_stack.pop
173
+ pop_namespace_stack
206
174
  end
207
175
 
208
176
  sig { params(node: Prism::MultiWriteNode).void }
@@ -317,7 +285,7 @@ module RubyIndexer
317
285
  end
318
286
 
319
287
  @enhancements.each do |enhancement|
320
- enhancement.on_call_node_enter(@owner_stack.last, node, @file_path, @code_units_cache)
288
+ enhancement.on_call_node_enter(node)
321
289
  rescue StandardError => e
322
290
  @indexing_errors << <<~MSG
323
291
  Indexing error in #{@file_path} with '#{enhancement.class.name}' on call node enter enhancement: #{e.message}
@@ -338,7 +306,7 @@ module RubyIndexer
338
306
  end
339
307
 
340
308
  @enhancements.each do |enhancement|
341
- enhancement.on_call_node_leave(@owner_stack.last, node, @file_path, @code_units_cache)
309
+ enhancement.on_call_node_leave(node)
342
310
  rescue StandardError => e
343
311
  @indexing_errors << <<~MSG
344
312
  Indexing error in #{@file_path} with '#{enhancement.class.name}' on call node leave enhancement: #{e.message}
@@ -463,6 +431,98 @@ module RubyIndexer
463
431
  )
464
432
  end
465
433
 
434
+ sig do
435
+ params(
436
+ name: String,
437
+ node_location: Prism::Location,
438
+ signatures: T::Array[Entry::Signature],
439
+ visibility: Entry::Visibility,
440
+ comments: T.nilable(String),
441
+ ).void
442
+ end
443
+ def add_method(name, node_location, signatures, visibility: Entry::Visibility::PUBLIC, comments: nil)
444
+ location = Location.from_prism_location(node_location, @code_units_cache)
445
+
446
+ @index.add(Entry::Method.new(
447
+ name,
448
+ @file_path,
449
+ location,
450
+ location,
451
+ comments,
452
+ signatures,
453
+ visibility,
454
+ @owner_stack.last,
455
+ ))
456
+ end
457
+
458
+ sig do
459
+ params(
460
+ name: String,
461
+ full_location: Prism::Location,
462
+ name_location: Prism::Location,
463
+ comments: T.nilable(String),
464
+ ).void
465
+ end
466
+ def add_module(name, full_location, name_location, comments: nil)
467
+ location = Location.from_prism_location(full_location, @code_units_cache)
468
+ name_loc = Location.from_prism_location(name_location, @code_units_cache)
469
+
470
+ entry = Entry::Module.new(
471
+ actual_nesting(name),
472
+ @file_path,
473
+ location,
474
+ name_loc,
475
+ comments,
476
+ )
477
+
478
+ advance_namespace_stack(name, entry)
479
+ end
480
+
481
+ sig do
482
+ params(
483
+ name_or_nesting: T.any(String, T::Array[String]),
484
+ full_location: Prism::Location,
485
+ name_location: Prism::Location,
486
+ parent_class_name: T.nilable(String),
487
+ comments: T.nilable(String),
488
+ ).void
489
+ end
490
+ def add_class(name_or_nesting, full_location, name_location, parent_class_name: nil, comments: nil)
491
+ nesting = name_or_nesting.is_a?(Array) ? name_or_nesting : actual_nesting(name_or_nesting)
492
+ entry = Entry::Class.new(
493
+ nesting,
494
+ @file_path,
495
+ Location.from_prism_location(full_location, @code_units_cache),
496
+ Location.from_prism_location(name_location, @code_units_cache),
497
+ comments,
498
+ parent_class_name,
499
+ )
500
+
501
+ advance_namespace_stack(T.must(nesting.last), entry)
502
+ end
503
+
504
+ sig { params(block: T.proc.params(index: Index, base: Entry::Namespace).void).void }
505
+ def register_included_hook(&block)
506
+ owner = @owner_stack.last
507
+ return unless owner
508
+
509
+ @index.register_included_hook(owner.name) do |index, base|
510
+ block.call(index, base)
511
+ end
512
+ end
513
+
514
+ sig { void }
515
+ def pop_namespace_stack
516
+ @stack.pop
517
+ @owner_stack.pop
518
+ @visibility_stack.pop
519
+ end
520
+
521
+ sig { returns(T.nilable(Entry::Namespace)) }
522
+ def current_owner
523
+ @owner_stack.last
524
+ end
525
+
466
526
  private
467
527
 
468
528
  sig do
@@ -661,8 +721,7 @@ module RubyIndexer
661
721
  comments = +""
662
722
 
663
723
  start_line = node.location.start_line - 1
664
- start_line -= 1 unless @comments_by_line.key?(start_line)
665
-
724
+ start_line -= 1 unless comment_exists_at?(start_line)
666
725
  start_line.downto(1) do |line|
667
726
  comment = @comments_by_line[line]
668
727
  break unless comment
@@ -683,6 +742,11 @@ module RubyIndexer
683
742
  comments
684
743
  end
685
744
 
745
+ sig { params(line: Integer).returns(T::Boolean) }
746
+ def comment_exists_at?(line)
747
+ @comments_by_line.key?(line) || !@source_lines[line - 1].to_s.strip.empty?
748
+ end
749
+
686
750
  sig { params(name: String).returns(String) }
687
751
  def fully_qualify_name(name)
688
752
  if @stack.empty? || name.start_with?("::")
@@ -746,16 +810,22 @@ module RubyIndexer
746
810
  return unless arguments
747
811
 
748
812
  arguments.each do |node|
749
- next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
750
-
751
- case operation
752
- when :include
753
- owner.mixin_operations << Entry::Include.new(node.full_name)
754
- when :prepend
755
- owner.mixin_operations << Entry::Prepend.new(node.full_name)
756
- when :extend
813
+ next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode) ||
814
+ (node.is_a?(Prism::SelfNode) && operation == :extend)
815
+
816
+ if node.is_a?(Prism::SelfNode)
757
817
  singleton = @index.existing_or_new_singleton_class(owner.name)
758
- singleton.mixin_operations << Entry::Include.new(node.full_name)
818
+ singleton.mixin_operations << Entry::Include.new(owner.name)
819
+ else
820
+ case operation
821
+ when :include
822
+ owner.mixin_operations << Entry::Include.new(node.full_name)
823
+ when :prepend
824
+ owner.mixin_operations << Entry::Prepend.new(node.full_name)
825
+ when :extend
826
+ singleton = @index.existing_or_new_singleton_class(owner.name)
827
+ singleton.mixin_operations << Entry::Include.new(node.full_name)
828
+ end
759
829
  end
760
830
  rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
761
831
  Prism::ConstantPathNode::MissingNodesInConstantPathError
@@ -910,5 +980,13 @@ module RubyIndexer
910
980
 
911
981
  corrected_nesting
912
982
  end
983
+
984
+ sig { params(short_name: String, entry: Entry::Namespace).void }
985
+ def advance_namespace_stack(short_name, entry)
986
+ @visibility_stack.push(Entry::Visibility::PUBLIC)
987
+ @owner_stack << entry
988
+ @index.add(entry)
989
+ @stack << short_name
990
+ end
913
991
  end
914
992
  end
@@ -8,38 +8,41 @@ module RubyIndexer
8
8
 
9
9
  abstract!
10
10
 
11
- sig { params(index: Index).void }
12
- def initialize(index)
13
- @index = index
11
+ @enhancements = T.let([], T::Array[T::Class[Enhancement]])
12
+
13
+ class << self
14
+ extend T::Sig
15
+
16
+ sig { params(child: T::Class[Enhancement]).void }
17
+ def inherited(child)
18
+ @enhancements << child
19
+ super
20
+ end
21
+
22
+ sig { params(listener: DeclarationListener).returns(T::Array[Enhancement]) }
23
+ def all(listener)
24
+ @enhancements.map { |enhancement| enhancement.new(listener) }
25
+ end
26
+
27
+ # Only available for testing purposes
28
+ sig { void }
29
+ def clear
30
+ @enhancements.clear
31
+ end
32
+ end
33
+
34
+ sig { params(listener: DeclarationListener).void }
35
+ def initialize(listener)
36
+ @listener = listener
14
37
  end
15
38
 
16
39
  # The `on_extend` indexing enhancement is invoked whenever an extend is encountered in the code. It can be used to
17
40
  # register for an included callback, similar to what `ActiveSupport::Concern` does in order to auto-extend the
18
41
  # `ClassMethods` modules
19
- sig do
20
- overridable.params(
21
- owner: T.nilable(Entry::Namespace),
22
- node: Prism::CallNode,
23
- file_path: String,
24
- code_units_cache: T.any(
25
- T.proc.params(arg0: Integer).returns(Integer),
26
- Prism::CodeUnitsCache,
27
- ),
28
- ).void
29
- end
30
- def on_call_node_enter(owner, node, file_path, code_units_cache); end
31
-
32
- sig do
33
- overridable.params(
34
- owner: T.nilable(Entry::Namespace),
35
- node: Prism::CallNode,
36
- file_path: String,
37
- code_units_cache: T.any(
38
- T.proc.params(arg0: Integer).returns(Integer),
39
- Prism::CodeUnitsCache,
40
- ),
41
- ).void
42
- end
43
- def on_call_node_leave(owner, node, file_path, code_units_cache); end
42
+ sig { overridable.params(node: Prism::CallNode).void }
43
+ def on_call_node_enter(node); end # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
44
+
45
+ sig { overridable.params(node: Prism::CallNode).void }
46
+ def on_call_node_leave(node); end # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
44
47
  end
45
48
  end
@@ -7,6 +7,7 @@ module RubyIndexer
7
7
 
8
8
  class UnresolvableAliasError < StandardError; end
9
9
  class NonExistingNamespaceError < StandardError; end
10
+ class IndexNotEmptyError < StandardError; end
10
11
 
11
12
  # The minimum Jaro-Winkler similarity score for an entry to be considered a match for a given fuzzy search query
12
13
  ENTRY_SIMILARITY_THRESHOLD = 0.7
@@ -39,9 +40,6 @@ module RubyIndexer
39
40
  # Holds the linearized ancestors list for every namespace
40
41
  @ancestors = T.let({}, T::Hash[String, T::Array[String]])
41
42
 
42
- # List of classes that are enhancing the index
43
- @enhancements = T.let([], T::Array[Enhancement])
44
-
45
43
  # Map of module name to included hooks that have to be executed when we include the given module
46
44
  @included_hooks = T.let(
47
45
  {},
@@ -51,12 +49,6 @@ module RubyIndexer
51
49
  @configuration = T.let(RubyIndexer::Configuration.new, Configuration)
52
50
  end
53
51
 
54
- # Register an enhancement to the index. Enhancements must conform to the `Enhancement` interface
55
- sig { params(enhancement: Enhancement).void }
56
- def register_enhancement(enhancement)
57
- @enhancements << enhancement
58
- end
59
-
60
52
  # Register an included `hook` that will be executed when `module_name` is included into any namespace
61
53
  sig { params(module_name: String, hook: T.proc.params(index: Index, base: Entry::Namespace).void).void }
62
54
  def register_included_hook(module_name, &hook)
@@ -360,6 +352,15 @@ module RubyIndexer
360
352
  ).void
361
353
  end
362
354
  def index_all(indexable_paths: @configuration.indexables, &block)
355
+ # When troubleshooting an indexing issue, e.g. through irb, it's not obvious that `index_all` will augment the
356
+ # existing index values, meaning it may contain 'stale' entries. This check ensures that the user is aware of this
357
+ # behavior and can take appropriate action.
358
+ # binding.break
359
+ if @entries.any?
360
+ raise IndexNotEmptyError,
361
+ "The index is not empty. To prevent invalid entries, `index_all` can only be called once."
362
+ end
363
+
363
364
  RBSIndexer.new(self).index_ruby_core
364
365
  # Calculate how many paths are worth 1% of progress
365
366
  progress_step = (indexable_paths.length / 100.0).ceil
@@ -386,7 +387,6 @@ module RubyIndexer
386
387
  result,
387
388
  indexable_path.full_path,
388
389
  collect_comments: collect_comments,
389
- enhancements: @enhancements,
390
390
  )
391
391
  dispatcher.dispatch(result.value)
392
392
 
@@ -302,10 +302,10 @@ module RubyIndexer
302
302
  RUBY
303
303
 
304
304
  b_const = @index["A::B"].first
305
- assert_equal(Entry::Visibility::PRIVATE, b_const.visibility)
305
+ assert_predicate(b_const, :private?)
306
306
 
307
307
  c_const = @index["A::C"].first
308
- assert_equal(Entry::Visibility::PRIVATE, c_const.visibility)
308
+ assert_predicate(c_const, :private?)
309
309
 
310
310
  d_const = @index["A::D"].first
311
311
  assert_equal(Entry::Visibility::PUBLIC, d_const.visibility)
@@ -160,5 +160,15 @@ module RubyIndexer
160
160
  )
161
161
  end
162
162
  end
163
+
164
+ def test_includes_top_level_files
165
+ Dir.mktmpdir do |dir|
166
+ FileUtils.touch(File.join(dir, "find_me.rb"))
167
+ @config.workspace_path = dir
168
+
169
+ indexables = @config.indexables
170
+ assert(indexables.find { |i| File.basename(i.full_path) == "find_me.rb" })
171
+ end
172
+ end
163
173
  end
164
174
  end
@@ -130,13 +130,13 @@ module RubyIndexer
130
130
  RUBY
131
131
 
132
132
  b_const = @index["A::B"].first
133
- assert_equal(Entry::Visibility::PRIVATE, b_const.visibility)
133
+ assert_predicate(b_const, :private?)
134
134
 
135
135
  c_const = @index["A::C"].first
136
- assert_equal(Entry::Visibility::PRIVATE, c_const.visibility)
136
+ assert_predicate(c_const, :private?)
137
137
 
138
138
  d_const = @index["A::D"].first
139
- assert_equal(Entry::Visibility::PUBLIC, d_const.visibility)
139
+ assert_predicate(d_const, :public?)
140
140
  end
141
141
 
142
142
  def test_marking_constants_as_private_reopening_namespaces
@@ -163,13 +163,13 @@ module RubyIndexer
163
163
  RUBY
164
164
 
165
165
  a_const = @index["A::B::CONST_A"].first
166
- assert_equal(Entry::Visibility::PRIVATE, a_const.visibility)
166
+ assert_predicate(a_const, :private?)
167
167
 
168
168
  b_const = @index["A::B::CONST_B"].first
169
- assert_equal(Entry::Visibility::PRIVATE, b_const.visibility)
169
+ assert_predicate(b_const, :private?)
170
170
 
171
171
  c_const = @index["A::B::CONST_C"].first
172
- assert_equal(Entry::Visibility::PRIVATE, c_const.visibility)
172
+ assert_predicate(c_const, :private?)
173
173
  end
174
174
 
175
175
  def test_marking_constants_as_private_with_receiver
@@ -187,10 +187,10 @@ module RubyIndexer
187
187
  RUBY
188
188
 
189
189
  a_const = @index["A::B::CONST_A"].first
190
- assert_equal(Entry::Visibility::PRIVATE, a_const.visibility)
190
+ assert_predicate(a_const, :private?)
191
191
 
192
192
  b_const = @index["A::B::CONST_B"].first
193
- assert_equal(Entry::Visibility::PRIVATE, b_const.visibility)
193
+ assert_predicate(b_const, :private?)
194
194
  end
195
195
 
196
196
  def test_indexing_constant_aliases