ruby-lsp 0.17.16 → 0.17.17

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: fc4416ea8876d8207b6c1955cd47af5f3312a0f0dc3644563bfd35138a903535
4
- data.tar.gz: 5961dd8ca49539c20049509c0afed8b2442c3d16ec12d1fb84c9e45dbd027760
3
+ metadata.gz: e4b4c9204834ddf87c84009003c5eef5872211eaef6ed64022a93a2749e1849e
4
+ data.tar.gz: 0d770cf2f67f90fb974a9fca08d9fb4dea1a73d668984ff96e5cc5785d7c5766
5
5
  SHA512:
6
- metadata.gz: 4b77547221c1750871ff6e1a3635b0ae6f945b86862f484d20eb7587a1feb9830e1d1d7770f16c2e7efeb70e6a953529041f8acc8fe3d7c30940687d20563f96
7
- data.tar.gz: 1093a1a6ac45e8627f26a6c9dbb0017079d2804904fe9a4303e3698d4131c6eeedf2bbafac93e6475f676860624b9c53d482c52cfe8380e67ed05ce525e99957
6
+ metadata.gz: 3534227d67761a6738c5e2ae4a06a08964a0836c4a30c9d306917efdd93aae1285d6ed03992e88486a44e5d3f7e7271997e56bbb12ab908599687c6a1e622cb4
7
+ data.tar.gz: 5345bcf362b6206a0be392eebe5db2175fe9d06b1a38f113fe11d979e5fdcc48851cb47e5f016ae49726ffc73432e81ef801522588cee79ebb36fe545e02d965
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.16
1
+ 0.17.17
data/exe/ruby-lsp CHANGED
@@ -98,16 +98,17 @@ if options[:debug]
98
98
  end
99
99
 
100
100
  if options[:time_index]
101
- require "benchmark"
102
-
103
101
  index = RubyIndexer::Index.new
104
102
 
105
- result = Benchmark.realtime { index.index_all }
103
+ time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
104
+ index.index_all
105
+ elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - time_start
106
+
106
107
  entries = index.instance_variable_get(:@entries)
107
108
  entries_by_entry_type = entries.values.flatten.group_by(&:class)
108
109
 
109
110
  puts <<~MSG
110
- Ruby LSP v#{RubyLsp::VERSION}: Indexing took #{result.round(5)} seconds and generated:
111
+ Ruby LSP v#{RubyLsp::VERSION}: Indexing took #{elapsed_time.round(5)} seconds and generated:
111
112
  - #{entries_by_entry_type.sort_by { |k, _| k.to_s }.map { |k, v| "#{k.name.split("::").last}: #{v.size}" }.join("\n- ")}
112
113
  MSG
113
114
  return
@@ -232,7 +232,7 @@ module RubyIndexer
232
232
  next unless ancestor_index && (!existing_entry || ancestor_index < existing_entry_index)
233
233
 
234
234
  if entry.is_a?(Entry::UnresolvedMethodAlias)
235
- resolved_alias = resolve_method_alias(entry, receiver_name)
235
+ resolved_alias = resolve_method_alias(entry, receiver_name, [])
236
236
  hash[entry_name] = [resolved_alias, ancestor_index] if resolved_alias.is_a?(Entry::MethodAlias)
237
237
  else
238
238
  hash[entry_name] = [entry, ancestor_index]
@@ -394,16 +394,18 @@ module RubyIndexer
394
394
  real_parts.join("::")
395
395
  end
396
396
 
397
- # Attempts to find methods for a resolved fully qualified receiver name.
397
+ # Attempts to find methods for a resolved fully qualified receiver name. Do not provide the `seen_names` parameter
398
+ # as it is used only internally to prevent infinite loops when resolving circular aliases
398
399
  # Returns `nil` if the method does not exist on that receiver
399
400
  sig do
400
401
  params(
401
402
  method_name: String,
402
403
  receiver_name: String,
404
+ seen_names: T::Array[String],
403
405
  inherited_only: T::Boolean,
404
406
  ).returns(T.nilable(T::Array[T.any(Entry::Member, Entry::MethodAlias)]))
405
407
  end
406
- def resolve_method(method_name, receiver_name, inherited_only: false)
408
+ def resolve_method(method_name, receiver_name, seen_names = [], inherited_only: false)
407
409
  method_entries = self[method_name]
408
410
  return unless method_entries
409
411
 
@@ -418,7 +420,7 @@ module RubyIndexer
418
420
  when Entry::UnresolvedMethodAlias
419
421
  # Resolve aliases lazily as we find them
420
422
  if entry.owner&.name == ancestor
421
- resolved_alias = resolve_method_alias(entry, receiver_name)
423
+ resolved_alias = resolve_method_alias(entry, receiver_name, seen_names)
422
424
  resolved_alias if resolved_alias.is_a?(Entry::MethodAlias)
423
425
  end
424
426
  end
@@ -614,6 +616,17 @@ module RubyIndexer
614
616
  singleton
615
617
  end
616
618
 
619
+ sig do
620
+ type_parameters(:T).params(
621
+ path: String,
622
+ type: T::Class[T.all(T.type_parameter(:T), Entry)],
623
+ ).returns(T.nilable(T::Array[T.type_parameter(:T)]))
624
+ end
625
+ def entries_for(path, type)
626
+ entries = @files_to_entries[path]
627
+ entries&.grep(type)
628
+ end
629
+
617
630
  private
618
631
 
619
632
  # Runs the registered included hooks
@@ -919,16 +932,21 @@ module RubyIndexer
919
932
  params(
920
933
  entry: Entry::UnresolvedMethodAlias,
921
934
  receiver_name: String,
935
+ seen_names: T::Array[String],
922
936
  ).returns(T.any(Entry::MethodAlias, Entry::UnresolvedMethodAlias))
923
937
  end
924
- def resolve_method_alias(entry, receiver_name)
925
- return entry if entry.new_name == entry.old_name
938
+ def resolve_method_alias(entry, receiver_name, seen_names)
939
+ new_name = entry.new_name
940
+ return entry if new_name == entry.old_name
941
+ return entry if seen_names.include?(new_name)
942
+
943
+ seen_names << new_name
926
944
 
927
- target_method_entries = resolve_method(entry.old_name, receiver_name)
945
+ target_method_entries = resolve_method(entry.old_name, receiver_name, seen_names)
928
946
  return entry unless target_method_entries
929
947
 
930
948
  resolved_alias = Entry::MethodAlias.new(T.must(target_method_entries.first), entry)
931
- original_entries = T.must(@entries[entry.new_name])
949
+ original_entries = T.must(@entries[new_name])
932
950
  original_entries.delete(entry)
933
951
  original_entries << resolved_alias
934
952
  resolved_alias
@@ -1822,5 +1822,46 @@ module RubyIndexer
1822
1822
  @index.linearized_ancestors_of("Foo::Child::<Class:Child>"),
1823
1823
  )
1824
1824
  end
1825
+
1826
+ def test_resolving_circular_method_aliases_on_class_reopen
1827
+ index(<<~RUBY)
1828
+ class Foo
1829
+ alias bar ==
1830
+ def ==(other) = true
1831
+ end
1832
+
1833
+ class Foo
1834
+ alias == bar
1835
+ end
1836
+ RUBY
1837
+
1838
+ method = @index.resolve_method("==", "Foo").first
1839
+ assert_kind_of(Entry::Method, method)
1840
+ assert_equal("==", method.name)
1841
+
1842
+ candidates = @index.method_completion_candidates("=", "Foo")
1843
+ assert_equal(["==", "==="], candidates.map(&:name))
1844
+ end
1845
+
1846
+ def test_entries_for
1847
+ index(<<~RUBY)
1848
+ class Foo; end
1849
+
1850
+ module Bar
1851
+ def my_def; end
1852
+ def self.my_singleton_def; end
1853
+ end
1854
+ RUBY
1855
+
1856
+ entries = @index.entries_for("/fake/path/foo.rb", Entry)
1857
+ assert_equal(["Foo", "Bar", "my_def", "Bar::<Class:Bar>", "my_singleton_def"], entries.map(&:name))
1858
+
1859
+ entries = @index.entries_for("/fake/path/foo.rb", RubyIndexer::Entry::Namespace)
1860
+ assert_equal(["Foo", "Bar", "Bar::<Class:Bar>"], entries.map(&:name))
1861
+ end
1862
+
1863
+ def test_entries_for_returns_nil_if_no_matches
1864
+ assert_nil(@index.entries_for("non_existing_file.rb", Entry::Namespace))
1865
+ end
1825
1866
  end
1826
1867
  end
@@ -30,6 +30,8 @@ module RubyLsp
30
30
  # Addon instances that have declared a handler to accept file watcher events
31
31
  @file_watcher_addons = T.let([], T::Array[Addon])
32
32
 
33
+ AddonNotFoundError = Class.new(StandardError)
34
+
33
35
  class << self
34
36
  extend T::Sig
35
37
 
@@ -78,11 +80,10 @@ module RubyLsp
78
80
  errors
79
81
  end
80
82
 
81
- # Intended for use by tests for addons
82
83
  sig { params(addon_name: String).returns(Addon) }
83
84
  def get(addon_name)
84
85
  addon = addons.find { |addon| addon.name == addon_name }
85
- raise "Could not find addon '#{addon_name}'" unless addon
86
+ raise AddonNotFoundError, "Could not find addon '#{addon_name}'" unless addon
86
87
 
87
88
  addon
88
89
  end
@@ -40,6 +40,12 @@ module RubyLsp
40
40
  @supports_watching_files = T.let(false, T::Boolean)
41
41
  @experimental_features = T.let(false, T::Boolean)
42
42
  @type_inferrer = T.let(TypeInferrer.new(@index, @experimental_features), TypeInferrer)
43
+ @addon_settings = T.let({}, T::Hash[String, T.untyped])
44
+ end
45
+
46
+ sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
47
+ def settings_for_addon(addon_name)
48
+ @addon_settings[addon_name]
43
49
  end
44
50
 
45
51
  sig { params(identifier: String, instance: Requests::Support::Formatter).void }
@@ -119,6 +125,12 @@ module RubyLsp
119
125
  @experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
120
126
  @type_inferrer.experimental_features = @experimental_features
121
127
 
128
+ addon_settings = options.dig(:initializationOptions, :addonSettings)
129
+ if addon_settings
130
+ addon_settings.transform_keys!(&:to_s)
131
+ @addon_settings.merge!(addon_settings)
132
+ end
133
+
122
134
  notifications
123
135
  end
124
136
 
@@ -242,7 +242,7 @@ module RubyLsp
242
242
  body = statements.body
243
243
  return if body.empty?
244
244
 
245
- add_lines_range(node.location.start_line, T.must(body.last).location.end_line)
245
+ add_lines_range(node.location.start_line, body.last.location.end_line)
246
246
  end
247
247
 
248
248
  sig { params(node: Prism::Node).void }
@@ -41,6 +41,7 @@ module RubyLsp
41
41
  :on_block_node_leave,
42
42
  :on_self_node_enter,
43
43
  :on_module_node_enter,
44
+ :on_local_variable_write_node_enter,
44
45
  :on_local_variable_read_node_enter,
45
46
  :on_block_parameter_node_enter,
46
47
  :on_required_keyword_parameter_node_enter,
@@ -49,6 +50,9 @@ module RubyLsp
49
50
  :on_optional_parameter_node_enter,
50
51
  :on_required_parameter_node_enter,
51
52
  :on_rest_parameter_node_enter,
53
+ :on_local_variable_and_write_node_enter,
54
+ :on_local_variable_operator_write_node_enter,
55
+ :on_local_variable_or_write_node_enter,
52
56
  :on_local_variable_target_node_enter,
53
57
  :on_block_local_variable_node_enter,
54
58
  :on_match_write_node_enter,
@@ -164,6 +168,12 @@ module RubyLsp
164
168
  @response_builder.add_token(node.location, :variable, [:default_library])
165
169
  end
166
170
 
171
+ sig { params(node: Prism::LocalVariableWriteNode).void }
172
+ def on_local_variable_write_node_enter(node)
173
+ type = @current_scope.type_for(node.name)
174
+ @response_builder.add_token(node.name_loc, type) if type == :parameter
175
+ end
176
+
167
177
  sig { params(node: Prism::LocalVariableReadNode).void }
168
178
  def on_local_variable_read_node_enter(node)
169
179
  return if @inside_implicit_node
@@ -177,6 +187,24 @@ module RubyLsp
177
187
  @response_builder.add_token(node.location, @current_scope.type_for(node.name))
178
188
  end
179
189
 
190
+ sig { params(node: Prism::LocalVariableAndWriteNode).void }
191
+ def on_local_variable_and_write_node_enter(node)
192
+ type = @current_scope.type_for(node.name)
193
+ @response_builder.add_token(node.name_loc, type) if type == :parameter
194
+ end
195
+
196
+ sig { params(node: Prism::LocalVariableOperatorWriteNode).void }
197
+ def on_local_variable_operator_write_node_enter(node)
198
+ type = @current_scope.type_for(node.name)
199
+ @response_builder.add_token(node.name_loc, type) if type == :parameter
200
+ end
201
+
202
+ sig { params(node: Prism::LocalVariableOrWriteNode).void }
203
+ def on_local_variable_or_write_node_enter(node)
204
+ type = @current_scope.type_for(node.name)
205
+ @response_builder.add_token(node.name_loc, type) if type == :parameter
206
+ end
207
+
180
208
  sig { params(node: Prism::LocalVariableTargetNode).void }
181
209
  def on_local_variable_target_node_enter(node)
182
210
  # If we're inside a regex capture, Prism will add LocalVariableTarget nodes for each captured variable.
@@ -58,14 +58,16 @@ module RubyLsp
58
58
  formatted_text = @active_formatter.run_formatting(@uri, @document)
59
59
  return unless formatted_text
60
60
 
61
+ lines = @document.source.lines
61
62
  size = @document.source.size
63
+
62
64
  return if formatted_text.size == size && formatted_text == @document.source
63
65
 
64
66
  [
65
67
  Interface::TextEdit.new(
66
68
  range: Interface::Range.new(
67
69
  start: Interface::Position.new(line: 0, character: 0),
68
- end: Interface::Position.new(line: size, character: size),
70
+ end: Interface::Position.new(line: lines.size, character: 0),
69
71
  ),
70
72
  new_text: formatted_text,
71
73
  ),
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.17.16
4
+ version: 0.17.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-26 00:00:00.000000000 Z
11
+ date: 2024-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -28,22 +28,16 @@ dependencies:
28
28
  name: prism
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 0.29.0
34
- - - "<"
31
+ - - "~>"
35
32
  - !ruby/object:Gem::Version
36
- version: '0.31'
33
+ version: '1.0'
37
34
  type: :runtime
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
40
37
  requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- version: 0.29.0
44
- - - "<"
38
+ - - "~>"
45
39
  - !ruby/object:Gem::Version
46
- version: '0.31'
40
+ version: '1.0'
47
41
  - !ruby/object:Gem::Dependency
48
42
  name: rbs
49
43
  requirement: !ruby/object:Gem::Requirement