ruby-lsp 0.21.0 → 0.22.0

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: a22b940469b6910a05ada8b39dbbc2ad918e26e41de0f2f287e79df46b8e46fe
4
- data.tar.gz: a57e250627b82a42ba286fd9b14338c48fb04b5a2e76c889d28d93ea55f96f90
3
+ metadata.gz: e48cef95e466e2a75943921fc08ede1a449e07096e44078ca063bdbee6d3c54b
4
+ data.tar.gz: c0ce6a0636645674e9e9a87219124b622b1280420c14c4bd12f8f0f0aafc3f32
5
5
  SHA512:
6
- metadata.gz: 0661e457f068016e397436e76c9d48d8d4e2f718fb47268c6fe57797b1374e4669eb4c01b7a45ca26d8fe39dbc79f308797864e73aa31ba21e013ac3c2c97265
7
- data.tar.gz: 709e136e94d223cb12fcb8a082d6193023fecc324aa1908f7a9dc2d9c3b612d6935cfe0362e7ba22fdd02ec3e6c1113a2a44e2a0f15230ff4698075a145d5e0d
6
+ metadata.gz: 89d02237ecce7e784bb5275ba4d5fc30b64981ea337b078f3117037b98d8f734e0d7e6266078e76023125dd52bbcb6ded8d04fdac1c29eb8292fd29e401cece4
7
+ data.tar.gz: 7245d82a481bd9e6a56d0cce9ad03796bd1a6d9e770beb5632fcaf7669ab0af9e9c0c95f0b671d844231af718add21b98011665ac3355fab5647ab12378b60bf
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.21.0
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