ruby-lsp 0.17.11 → 0.17.13

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: 13815c41a1b7509782277cb5dac09481130e18efc4253a01cede31ca41b335a7
4
- data.tar.gz: 40e6584ff6ff743ea1e62a29d6090d80bf318ae3dd5d90808de1b35723bea063
3
+ metadata.gz: 042725d7afce428b5c024933a7101151f2f69c57cbb40b4f938384c13d6c974b
4
+ data.tar.gz: bec8636d402451e1009e87ddd98e6fdedc062643e614b596a0698ffcfcc9b271
5
5
  SHA512:
6
- metadata.gz: 74da4b254b598cd4b10701b65c49d045cc33be83330399e67602f8e43597c9e54567715343a67fa720b31084de540e498cc3d5525e64679500d52b4842e44cf7
7
- data.tar.gz: 313695187e34ceb34c50d320bbe0abe0bda18d99157e8f4710faf8f72bc5028abd813511665fd4eac274dd73606eebb1c901b949fa307a8f925c2bfaa39b0c45
6
+ metadata.gz: 6a6adb40a9ccaaf916f46c041f3eb2cc7a81be73c5d3bf7c05a45472cd9d08e5a9dd04d804b8e15f091645d420037e324368ccbcc619311a1c60c81f241c1b89
7
+ data.tar.gz: d6b19c8d02cfab8dca9e4d675e8ee549e8b392631a6080757c0413da29a07d1db411d9360b7553a798805d9e6faac7318863e35ba664488c5eb41c869a7e734a
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.11
1
+ 0.17.13
data/exe/ruby-lsp CHANGED
@@ -112,20 +112,20 @@ if options[:time_index]
112
112
  end
113
113
 
114
114
  if options[:doctor]
115
+ index = RubyIndexer::Index.new
116
+
115
117
  if File.exist?(".index.yml")
116
118
  begin
117
119
  config = YAML.parse_file(".index.yml").to_ruby
118
120
  rescue => e
119
121
  abort("Error parsing config: #{e.message}")
120
122
  end
121
- RubyIndexer.configuration.apply_config(config)
123
+ index.configuration.apply_config(config)
122
124
  end
123
125
 
124
- index = RubyIndexer::Index.new
125
-
126
126
  puts "Globbing for indexable files"
127
127
 
128
- RubyIndexer.configuration.indexables.each do |indexable|
128
+ index.configuration.indexables.each do |indexable|
129
129
  puts "indexing: #{indexable.full_path}"
130
130
  index.index_single(indexable)
131
131
  end
data/exe/ruby-lsp-check CHANGED
@@ -44,7 +44,7 @@ puts "\n"
44
44
  puts "Verifying that indexing executes successfully. This may take a while..."
45
45
 
46
46
  index = RubyIndexer::Index.new
47
- indexables = RubyIndexer.configuration.indexables
47
+ indexables = index.configuration.indexables
48
48
 
49
49
  indexables.each_with_index do |indexable, i|
50
50
  index.index_single(indexable)
@@ -8,12 +8,22 @@ module RubyIndexer
8
8
  OBJECT_NESTING = T.let(["Object"].freeze, T::Array[String])
9
9
  BASIC_OBJECT_NESTING = T.let(["BasicObject"].freeze, T::Array[String])
10
10
 
11
+ sig { returns(T::Array[String]) }
12
+ attr_reader :indexing_errors
13
+
11
14
  sig do
12
- params(index: Index, dispatcher: Prism::Dispatcher, parse_result: Prism::ParseResult, file_path: String).void
15
+ params(
16
+ index: Index,
17
+ dispatcher: Prism::Dispatcher,
18
+ parse_result: Prism::ParseResult,
19
+ file_path: String,
20
+ enhancements: T::Array[Enhancement],
21
+ ).void
13
22
  end
14
- def initialize(index, dispatcher, parse_result, file_path)
23
+ def initialize(index, dispatcher, parse_result, file_path, enhancements: [])
15
24
  @index = index
16
25
  @file_path = file_path
26
+ @enhancements = enhancements
17
27
  @visibility_stack = T.let([Entry::Visibility::PUBLIC], T::Array[Entry::Visibility])
18
28
  @comments_by_line = T.let(
19
29
  parse_result.comments.to_h do |c|
@@ -29,6 +39,7 @@ module RubyIndexer
29
39
 
30
40
  # A stack of namespace entries that represent where we currently are. Used to properly assign methods to an owner
31
41
  @owner_stack = T.let([], T::Array[Entry::Namespace])
42
+ @indexing_errors = T.let([], T::Array[String])
32
43
 
33
44
  dispatcher.register(
34
45
  self,
@@ -279,6 +290,12 @@ module RubyIndexer
279
290
  when :private
280
291
  @visibility_stack.push(Entry::Visibility::PRIVATE)
281
292
  end
293
+
294
+ @enhancements.each do |enhancement|
295
+ enhancement.on_call_node(@index, @owner_stack.last, node, @file_path)
296
+ rescue StandardError => e
297
+ @indexing_errors << "Indexing error in #{@file_path} with '#{enhancement.class.name}' enhancement: #{e.message}"
298
+ end
282
299
  end
283
300
 
284
301
  sig { params(node: Prism::CallNode).void }
@@ -535,7 +552,7 @@ module RubyIndexer
535
552
  comment_content = comment.location.slice.chomp
536
553
 
537
554
  # invalid encodings would raise an "invalid byte sequence" exception
538
- if !comment_content.valid_encoding? || comment_content.match?(RubyIndexer.configuration.magic_comment_regex)
555
+ if !comment_content.valid_encoding? || comment_content.match?(@index.configuration.magic_comment_regex)
539
556
  next
540
557
  end
541
558
 
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyIndexer
5
+ module Enhancement
6
+ extend T::Sig
7
+ extend T::Helpers
8
+
9
+ interface!
10
+
11
+ requires_ancestor { Object }
12
+
13
+ # The `on_extend` indexing enhancement is invoked whenever an extend is encountered in the code. It can be used to
14
+ # register for an included callback, similar to what `ActiveSupport::Concern` does in order to auto-extend the
15
+ # `ClassMethods` modules
16
+ sig do
17
+ abstract.params(
18
+ index: Index,
19
+ owner: T.nilable(Entry::Namespace),
20
+ node: Prism::CallNode,
21
+ file_path: String,
22
+ ).void
23
+ end
24
+ def on_call_node(index, owner, node, file_path); end
25
+ end
26
+ end
@@ -342,6 +342,19 @@ module RubyIndexer
342
342
 
343
343
  "(#{first_signature.format})"
344
344
  end
345
+
346
+ sig { returns(String) }
347
+ def formatted_signatures
348
+ overloads_count = signatures.size
349
+ case overloads_count
350
+ when 1
351
+ ""
352
+ when 2
353
+ "\n(+1 overload)"
354
+ else
355
+ "\n(+#{overloads_count - 1} overloads)"
356
+ end
357
+ end
345
358
  end
346
359
 
347
360
  class Accessor < Member
@@ -542,6 +555,16 @@ module RubyIndexer
542
555
  def decorated_parameters
543
556
  @target.decorated_parameters
544
557
  end
558
+
559
+ sig { returns(String) }
560
+ def formatted_signatures
561
+ @target.formatted_signatures
562
+ end
563
+
564
+ sig { returns(T::Array[Signature]) }
565
+ def signatures
566
+ @target.signatures
567
+ end
545
568
  end
546
569
 
547
570
  # Ruby doesn't support method overloading, so a method will have only one signature.
@@ -11,6 +11,9 @@ module RubyIndexer
11
11
  # The minimum Jaro-Winkler similarity score for an entry to be considered a match for a given fuzzy search query
12
12
  ENTRY_SIMILARITY_THRESHOLD = 0.7
13
13
 
14
+ sig { returns(Configuration) }
15
+ attr_reader :configuration
16
+
14
17
  sig { void }
15
18
  def initialize
16
19
  # Holds all entries in the index using the following format:
@@ -35,6 +38,29 @@ module RubyIndexer
35
38
 
36
39
  # Holds the linearized ancestors list for every namespace
37
40
  @ancestors = T.let({}, T::Hash[String, T::Array[String]])
41
+
42
+ # List of classes that are enhancing the index
43
+ @enhancements = T.let([], T::Array[Enhancement])
44
+
45
+ # Map of module name to included hooks that have to be executed when we include the given module
46
+ @included_hooks = T.let(
47
+ {},
48
+ T::Hash[String, T::Array[T.proc.params(index: Index, base: Entry::Namespace).void]],
49
+ )
50
+
51
+ @configuration = T.let(RubyIndexer::Configuration.new, Configuration)
52
+ end
53
+
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
+ # Register an included `hook` that will be executed when `module_name` is included into any namespace
61
+ sig { params(module_name: String, hook: T.proc.params(index: Index, base: Entry::Namespace).void).void }
62
+ def register_included_hook(module_name, &hook)
63
+ (@included_hooks[module_name] ||= []) << hook
38
64
  end
39
65
 
40
66
  sig { params(indexable: IndexablePath).void }
@@ -275,7 +301,7 @@ module RubyIndexer
275
301
  block: T.nilable(T.proc.params(progress: Integer).returns(T::Boolean)),
276
302
  ).void
277
303
  end
278
- def index_all(indexable_paths: RubyIndexer.configuration.indexables, &block)
304
+ def index_all(indexable_paths: @configuration.indexables, &block)
279
305
  RBSIndexer.new(self).index_ruby_core
280
306
  # Calculate how many paths are worth 1% of progress
281
307
  progress_step = (indexable_paths.length / 100.0).ceil
@@ -296,11 +322,25 @@ module RubyIndexer
296
322
  dispatcher = Prism::Dispatcher.new
297
323
 
298
324
  result = Prism.parse(content)
299
- DeclarationListener.new(self, dispatcher, result, indexable_path.full_path)
325
+ listener = DeclarationListener.new(
326
+ self,
327
+ dispatcher,
328
+ result,
329
+ indexable_path.full_path,
330
+ enhancements: @enhancements,
331
+ )
300
332
  dispatcher.dispatch(result.value)
301
333
 
334
+ indexing_errors = listener.indexing_errors.uniq
335
+
302
336
  require_path = indexable_path.require_path
303
337
  @require_paths_tree.insert(require_path, indexable_path) if require_path
338
+
339
+ if indexing_errors.any?
340
+ indexing_errors.each do |error|
341
+ $stderr.puts error
342
+ end
343
+ end
304
344
  rescue Errno::EISDIR, Errno::ENOENT
305
345
  # If `path` is a directory, just ignore it and continue indexing. If the file doesn't exist, then we also ignore
306
346
  # it
@@ -457,6 +497,12 @@ module RubyIndexer
457
497
  end
458
498
  end
459
499
 
500
+ # We only need to run included hooks when linearizing singleton classes. Included hooks are typically used to add
501
+ # new singleton methods or to extend a module through an include. There's no need to support instance methods, the
502
+ # inclusion of another module or the prepending of another module, because those features are already a part of
503
+ # Ruby and can be used directly without any metaprogramming
504
+ run_included_hooks(attached_class_name, nesting) if singleton_levels > 0
505
+
460
506
  linearize_mixins(ancestors, namespaces, nesting)
461
507
  linearize_superclass(
462
508
  ancestors,
@@ -570,6 +616,34 @@ module RubyIndexer
570
616
 
571
617
  private
572
618
 
619
+ # Runs the registered included hooks
620
+ sig { params(fully_qualified_name: String, nesting: T::Array[String]).void }
621
+ def run_included_hooks(fully_qualified_name, nesting)
622
+ return if @included_hooks.empty?
623
+
624
+ namespaces = self[fully_qualified_name]&.grep(Entry::Namespace)
625
+ return unless namespaces
626
+
627
+ namespaces.each do |namespace|
628
+ namespace.mixin_operations.each do |operation|
629
+ next unless operation.is_a?(Entry::Include)
630
+
631
+ # First we resolve the include name, so that we know the actual module being referred to in the include
632
+ resolved_modules = resolve(operation.module_name, nesting)
633
+ next unless resolved_modules
634
+
635
+ module_name = T.must(resolved_modules.first).name
636
+
637
+ # Then we grab any hooks registered for that module
638
+ hooks = @included_hooks[module_name]
639
+ next unless hooks
640
+
641
+ # We invoke the hooks with the index and the namespace that included the module
642
+ hooks.each { |hook| hook.call(self, namespace) }
643
+ end
644
+ end
645
+ end
646
+
573
647
  # Linearize mixins for an array of namespace entries. This method will mutate the `ancestors` array with the
574
648
  # linearized ancestors of the mixins
575
649
  sig do
@@ -6,6 +6,7 @@ require "did_you_mean"
6
6
 
7
7
  require "ruby_indexer/lib/ruby_indexer/indexable_path"
8
8
  require "ruby_indexer/lib/ruby_indexer/declaration_listener"
9
+ require "ruby_indexer/lib/ruby_indexer/enhancement"
9
10
  require "ruby_indexer/lib/ruby_indexer/index"
10
11
  require "ruby_indexer/lib/ruby_indexer/entry"
11
12
  require "ruby_indexer/lib/ruby_indexer/configuration"
@@ -14,12 +15,4 @@ require "ruby_indexer/lib/ruby_indexer/location"
14
15
  require "ruby_indexer/lib/ruby_indexer/rbs_indexer"
15
16
 
16
17
  module RubyIndexer
17
- @configuration = T.let(Configuration.new, Configuration)
18
-
19
- class << self
20
- extend T::Sig
21
-
22
- sig { returns(Configuration) }
23
- attr_reader :configuration
24
- end
25
18
  end
@@ -108,7 +108,7 @@ module RubyIndexer
108
108
  end
109
109
 
110
110
  def test_magic_comments_regex
111
- regex = RubyIndexer.configuration.magic_comment_regex
111
+ regex = @config.magic_comment_regex
112
112
 
113
113
  [
114
114
  "# frozen_string_literal:",
@@ -0,0 +1,197 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "test_case"
5
+
6
+ module RubyIndexer
7
+ class EnhancementTest < TestCase
8
+ def test_enhancing_indexing_included_hook
9
+ enhancement_class = Class.new do
10
+ include Enhancement
11
+
12
+ def on_call_node(index, owner, node, file_path)
13
+ return unless owner
14
+ return unless node.name == :extend
15
+
16
+ arguments = node.arguments&.arguments
17
+ return unless arguments
18
+
19
+ location = node.location
20
+
21
+ arguments.each do |node|
22
+ next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
23
+
24
+ module_name = node.full_name
25
+ next unless module_name == "ActiveSupport::Concern"
26
+
27
+ index.register_included_hook(owner.name) do |index, base|
28
+ class_methods_name = "#{owner.name}::ClassMethods"
29
+
30
+ if index.indexed?(class_methods_name)
31
+ singleton = index.existing_or_new_singleton_class(base.name)
32
+ singleton.mixin_operations << Entry::Include.new(class_methods_name)
33
+ end
34
+ end
35
+
36
+ index.add(Entry::Method.new(
37
+ "new_method",
38
+ file_path,
39
+ location,
40
+ location,
41
+ [],
42
+ [Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
43
+ Entry::Visibility::PUBLIC,
44
+ owner,
45
+ ))
46
+ rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
47
+ Prism::ConstantPathNode::MissingNodesInConstantPathError
48
+ # Do nothing
49
+ end
50
+ end
51
+ end
52
+
53
+ @index.register_enhancement(enhancement_class.new)
54
+ index(<<~RUBY)
55
+ module ActiveSupport
56
+ module Concern
57
+ def self.extended(base)
58
+ base.class_eval("def new_method(a); end")
59
+ end
60
+ end
61
+ end
62
+
63
+ module ActiveRecord
64
+ module Associations
65
+ extend ActiveSupport::Concern
66
+
67
+ module ClassMethods
68
+ def belongs_to(something); end
69
+ end
70
+ end
71
+
72
+ class Base
73
+ include Associations
74
+ end
75
+ end
76
+
77
+ class User < ActiveRecord::Base
78
+ end
79
+ RUBY
80
+
81
+ assert_equal(
82
+ [
83
+ "User::<Class:User>",
84
+ "ActiveRecord::Base::<Class:Base>",
85
+ "ActiveRecord::Associations::ClassMethods",
86
+ "Object::<Class:Object>",
87
+ "BasicObject::<Class:BasicObject>",
88
+ "Class",
89
+ "Module",
90
+ "Object",
91
+ "Kernel",
92
+ "BasicObject",
93
+ ],
94
+ @index.linearized_ancestors_of("User::<Class:User>"),
95
+ )
96
+
97
+ assert_entry("new_method", Entry::Method, "/fake/path/foo.rb:10-4:10-33")
98
+ end
99
+
100
+ def test_enhancing_indexing_configuration_dsl
101
+ enhancement_class = Class.new do
102
+ include Enhancement
103
+
104
+ def on_call_node(index, owner, node, file_path)
105
+ return unless owner
106
+
107
+ name = node.name
108
+ return unless name == :has_many
109
+
110
+ arguments = node.arguments&.arguments
111
+ return unless arguments
112
+
113
+ association_name = arguments.first
114
+ return unless association_name.is_a?(Prism::SymbolNode)
115
+
116
+ location = association_name.location
117
+
118
+ index.add(Entry::Method.new(
119
+ T.must(association_name.value),
120
+ file_path,
121
+ location,
122
+ location,
123
+ [],
124
+ [],
125
+ Entry::Visibility::PUBLIC,
126
+ owner,
127
+ ))
128
+ end
129
+ end
130
+
131
+ @index.register_enhancement(enhancement_class.new)
132
+ index(<<~RUBY)
133
+ module ActiveSupport
134
+ module Concern
135
+ def self.extended(base)
136
+ base.class_eval("def new_method(a); end")
137
+ end
138
+ end
139
+ end
140
+
141
+ module ActiveRecord
142
+ module Associations
143
+ extend ActiveSupport::Concern
144
+
145
+ module ClassMethods
146
+ def belongs_to(something); end
147
+ end
148
+ end
149
+
150
+ class Base
151
+ include Associations
152
+ end
153
+ end
154
+
155
+ class User < ActiveRecord::Base
156
+ has_many :posts
157
+ end
158
+ RUBY
159
+
160
+ assert_entry("posts", Entry::Method, "/fake/path/foo.rb:23-11:23-17")
161
+ end
162
+
163
+ def test_error_handling_in_enhancement
164
+ enhancement_class = Class.new do
165
+ include Enhancement
166
+
167
+ def on_call_node(index, owner, node, file_path)
168
+ raise "Error"
169
+ end
170
+
171
+ class << self
172
+ def name
173
+ "TestEnhancement"
174
+ end
175
+ end
176
+ end
177
+
178
+ @index.register_enhancement(enhancement_class.new)
179
+
180
+ _stdout, stderr = capture_io do
181
+ index(<<~RUBY)
182
+ module ActiveSupport
183
+ module Concern
184
+ def self.extended(base)
185
+ base.class_eval("def new_method(a); end")
186
+ end
187
+ end
188
+ end
189
+ RUBY
190
+ end
191
+
192
+ assert_match(%r{Indexing error in /fake/path/foo\.rb with 'TestEnhancement' enhancement}, stderr)
193
+ # The module should still be indexed
194
+ assert_entry("ActiveSupport::Concern", Entry::Module, "/fake/path/foo.rb:1-2:5-5")
195
+ end
196
+ end
197
+ end
@@ -49,15 +49,18 @@ module RubyLsp
49
49
  super
50
50
  end
51
51
 
52
- # Discovers and loads all addons. Returns the list of activated addons
53
- sig { params(global_state: GlobalState, outgoing_queue: Thread::Queue).returns(T::Array[Addon]) }
52
+ # Discovers and loads all addons. Returns a list of errors when trying to require addons
53
+ sig do
54
+ params(global_state: GlobalState, outgoing_queue: Thread::Queue).returns(T::Array[StandardError])
55
+ end
54
56
  def load_addons(global_state, outgoing_queue)
55
57
  # Require all addons entry points, which should be placed under
56
58
  # `some_gem/lib/ruby_lsp/your_gem_name/addon.rb`
57
- Gem.find_files("ruby_lsp/**/addon.rb").each do |addon|
59
+ errors = Gem.find_files("ruby_lsp/**/addon.rb").filter_map do |addon|
58
60
  require File.expand_path(addon)
61
+ nil
59
62
  rescue => e
60
- $stderr.puts(e.full_message)
63
+ e
61
64
  end
62
65
 
63
66
  # Instantiate all discovered addon classes
@@ -71,6 +74,8 @@ module RubyLsp
71
74
  rescue => e
72
75
  addon.add_error(e)
73
76
  end
77
+
78
+ errors
74
79
  end
75
80
 
76
81
  # Intended for use by tests for addons
@@ -65,7 +65,7 @@ module RubyLsp
65
65
  when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
66
66
  process_message(message)
67
67
  when "shutdown"
68
- $stderr.puts("Shutting down Ruby LSP...")
68
+ send_log_message("Shutting down Ruby LSP...")
69
69
 
70
70
  shutdown
71
71
 
@@ -76,7 +76,7 @@ module RubyLsp
76
76
  when "exit"
77
77
  @mutex.synchronize do
78
78
  status = @incoming_queue.closed? ? 0 : 1
79
- $stderr.puts("Shutdown complete with status #{status}")
79
+ send_log_message("Shutdown complete with status #{status}")
80
80
  exit(status)
81
81
  end
82
82
  else
@@ -145,5 +145,10 @@ module RubyLsp
145
145
  def send_empty_response(id)
146
146
  send_message(Result.new(id: id, response: nil))
147
147
  end
148
+
149
+ sig { params(message: String, type: Integer).void }
150
+ def send_log_message(message, type: Constant::MessageType::LOG)
151
+ send_message(Notification.window_log_message(message, type: Constant::MessageType::LOG))
152
+ end
148
153
  end
149
154
  end
@@ -10,16 +10,6 @@ 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
-
23
13
  extend T::Sig
24
14
  extend T::Helpers
25
15
 
@@ -223,26 +213,6 @@ module RubyLsp
223
213
  NodeContext.new(closest, parent, nesting_nodes, call_node)
224
214
  end
225
215
 
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
243
- end
244
- end
245
-
246
216
  class Scanner
247
217
  extend T::Sig
248
218