ruby-lsp 0.18.3 → 0.19.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/core_ext/uri.rb +9 -4
  4. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +6 -0
  5. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +66 -8
  6. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +56 -32
  7. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +3 -2
  8. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +32 -8
  9. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +262 -0
  10. data/lib/ruby_indexer/ruby_indexer.rb +1 -0
  11. data/lib/ruby_indexer/test/classes_and_modules_test.rb +11 -0
  12. data/lib/ruby_indexer/test/constant_test.rb +8 -0
  13. data/lib/ruby_indexer/test/enhancements_test.rb +2 -0
  14. data/lib/ruby_indexer/test/instance_variables_test.rb +12 -0
  15. data/lib/ruby_indexer/test/method_test.rb +10 -0
  16. data/lib/ruby_indexer/test/rbs_indexer_test.rb +8 -0
  17. data/lib/ruby_indexer/test/reference_finder_test.rb +86 -0
  18. data/lib/ruby_lsp/addon.rb +79 -17
  19. data/lib/ruby_lsp/base_server.rb +6 -0
  20. data/lib/ruby_lsp/erb_document.rb +3 -2
  21. data/lib/ruby_lsp/global_state.rb +8 -0
  22. data/lib/ruby_lsp/internal.rb +3 -1
  23. data/lib/ruby_lsp/listeners/completion.rb +1 -1
  24. data/lib/ruby_lsp/listeners/hover.rb +57 -0
  25. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +24 -21
  26. data/lib/ruby_lsp/requests/completion_resolve.rb +29 -0
  27. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  28. data/lib/ruby_lsp/requests/rename.rb +189 -0
  29. data/lib/ruby_lsp/requests/support/common.rb +2 -2
  30. data/lib/ruby_lsp/requests/support/source_uri.rb +8 -1
  31. data/lib/ruby_lsp/scope.rb +47 -0
  32. data/lib/ruby_lsp/server.rb +64 -32
  33. data/lib/ruby_lsp/static_docs.rb +15 -0
  34. data/lib/ruby_lsp/store.rb +12 -0
  35. data/lib/ruby_lsp/test_helper.rb +1 -1
  36. data/lib/ruby_lsp/type_inferrer.rb +6 -1
  37. data/lib/ruby_lsp/utils.rb +3 -6
  38. data/static_docs/yield.md +81 -0
  39. metadata +19 -8
  40. data/lib/ruby_lsp/parameter_scope.rb +0 -33
@@ -0,0 +1,262 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyIndexer
5
+ class ReferenceFinder
6
+ extend T::Sig
7
+
8
+ class Reference
9
+ extend T::Sig
10
+
11
+ sig { returns(String) }
12
+ attr_reader :name
13
+
14
+ sig { returns(Prism::Location) }
15
+ attr_reader :location
16
+
17
+ sig { params(name: String, location: Prism::Location).void }
18
+ def initialize(name, location)
19
+ @name = name
20
+ @location = location
21
+ end
22
+ end
23
+
24
+ sig { returns(T::Array[Reference]) }
25
+ attr_reader :references
26
+
27
+ sig do
28
+ params(
29
+ fully_qualified_name: String,
30
+ index: RubyIndexer::Index,
31
+ dispatcher: Prism::Dispatcher,
32
+ ).void
33
+ end
34
+ def initialize(fully_qualified_name, index, dispatcher)
35
+ @fully_qualified_name = fully_qualified_name
36
+ @index = index
37
+ @stack = T.let([], T::Array[String])
38
+ @references = T.let([], T::Array[Reference])
39
+
40
+ dispatcher.register(
41
+ self,
42
+ :on_class_node_enter,
43
+ :on_class_node_leave,
44
+ :on_module_node_enter,
45
+ :on_module_node_leave,
46
+ :on_singleton_class_node_enter,
47
+ :on_singleton_class_node_leave,
48
+ :on_def_node_enter,
49
+ :on_def_node_leave,
50
+ :on_multi_write_node_enter,
51
+ :on_constant_path_write_node_enter,
52
+ :on_constant_path_or_write_node_enter,
53
+ :on_constant_path_operator_write_node_enter,
54
+ :on_constant_path_and_write_node_enter,
55
+ :on_constant_or_write_node_enter,
56
+ :on_constant_path_node_enter,
57
+ :on_constant_read_node_enter,
58
+ :on_constant_write_node_enter,
59
+ :on_constant_or_write_node_enter,
60
+ :on_constant_and_write_node_enter,
61
+ :on_constant_operator_write_node_enter,
62
+ )
63
+ end
64
+
65
+ sig { params(node: Prism::ClassNode).void }
66
+ def on_class_node_enter(node)
67
+ constant_path = node.constant_path
68
+ name = constant_path.slice
69
+ nesting = actual_nesting(name)
70
+
71
+ if nesting.join("::") == @fully_qualified_name
72
+ @references << Reference.new(name, constant_path.location)
73
+ end
74
+
75
+ @stack << name
76
+ end
77
+
78
+ sig { params(node: Prism::ClassNode).void }
79
+ def on_class_node_leave(node)
80
+ @stack.pop
81
+ end
82
+
83
+ sig { params(node: Prism::ModuleNode).void }
84
+ def on_module_node_enter(node)
85
+ constant_path = node.constant_path
86
+ name = constant_path.slice
87
+ nesting = actual_nesting(name)
88
+
89
+ if nesting.join("::") == @fully_qualified_name
90
+ @references << Reference.new(name, constant_path.location)
91
+ end
92
+
93
+ @stack << name
94
+ end
95
+
96
+ sig { params(node: Prism::ModuleNode).void }
97
+ def on_module_node_leave(node)
98
+ @stack.pop
99
+ end
100
+
101
+ sig { params(node: Prism::SingletonClassNode).void }
102
+ def on_singleton_class_node_enter(node)
103
+ expression = node.expression
104
+ return unless expression.is_a?(Prism::SelfNode)
105
+
106
+ @stack << "<Class:#{@stack.last}>"
107
+ end
108
+
109
+ sig { params(node: Prism::SingletonClassNode).void }
110
+ def on_singleton_class_node_leave(node)
111
+ @stack.pop
112
+ end
113
+
114
+ sig { params(node: Prism::ConstantPathNode).void }
115
+ def on_constant_path_node_enter(node)
116
+ name = constant_name(node)
117
+ return unless name
118
+
119
+ collect_constant_references(name, node.location)
120
+ end
121
+
122
+ sig { params(node: Prism::ConstantReadNode).void }
123
+ def on_constant_read_node_enter(node)
124
+ name = constant_name(node)
125
+ return unless name
126
+
127
+ collect_constant_references(name, node.location)
128
+ end
129
+
130
+ sig { params(node: Prism::MultiWriteNode).void }
131
+ def on_multi_write_node_enter(node)
132
+ [*node.lefts, *node.rest, *node.rights].each do |target|
133
+ case target
134
+ when Prism::ConstantTargetNode, Prism::ConstantPathTargetNode
135
+ collect_constant_references(target.name.to_s, target.location)
136
+ end
137
+ end
138
+ end
139
+
140
+ sig { params(node: Prism::ConstantPathWriteNode).void }
141
+ def on_constant_path_write_node_enter(node)
142
+ target = node.target
143
+ return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
144
+
145
+ name = constant_name(target)
146
+ return unless name
147
+
148
+ collect_constant_references(name, target.location)
149
+ end
150
+
151
+ sig { params(node: Prism::ConstantPathOrWriteNode).void }
152
+ def on_constant_path_or_write_node_enter(node)
153
+ target = node.target
154
+ return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
155
+
156
+ name = constant_name(target)
157
+ return unless name
158
+
159
+ collect_constant_references(name, target.location)
160
+ end
161
+
162
+ sig { params(node: Prism::ConstantPathOperatorWriteNode).void }
163
+ def on_constant_path_operator_write_node_enter(node)
164
+ target = node.target
165
+ return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
166
+
167
+ name = constant_name(target)
168
+ return unless name
169
+
170
+ collect_constant_references(name, target.location)
171
+ end
172
+
173
+ sig { params(node: Prism::ConstantPathAndWriteNode).void }
174
+ def on_constant_path_and_write_node_enter(node)
175
+ target = node.target
176
+ return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
177
+
178
+ name = constant_name(target)
179
+ return unless name
180
+
181
+ collect_constant_references(name, target.location)
182
+ end
183
+
184
+ sig { params(node: Prism::ConstantWriteNode).void }
185
+ def on_constant_write_node_enter(node)
186
+ collect_constant_references(node.name.to_s, node.name_loc)
187
+ end
188
+
189
+ sig { params(node: Prism::ConstantOrWriteNode).void }
190
+ def on_constant_or_write_node_enter(node)
191
+ collect_constant_references(node.name.to_s, node.name_loc)
192
+ end
193
+
194
+ sig { params(node: Prism::ConstantAndWriteNode).void }
195
+ def on_constant_and_write_node_enter(node)
196
+ collect_constant_references(node.name.to_s, node.name_loc)
197
+ end
198
+
199
+ sig { params(node: Prism::ConstantOperatorWriteNode).void }
200
+ def on_constant_operator_write_node_enter(node)
201
+ collect_constant_references(node.name.to_s, node.name_loc)
202
+ end
203
+
204
+ sig { params(node: Prism::DefNode).void }
205
+ def on_def_node_enter(node)
206
+ if node.receiver.is_a?(Prism::SelfNode)
207
+ @stack << "<Class:#{@stack.last}>"
208
+ end
209
+ end
210
+
211
+ sig { params(node: Prism::DefNode).void }
212
+ def on_def_node_leave(node)
213
+ if node.receiver.is_a?(Prism::SelfNode)
214
+ @stack.pop
215
+ end
216
+ end
217
+
218
+ private
219
+
220
+ sig { params(name: String).returns(T::Array[String]) }
221
+ def actual_nesting(name)
222
+ nesting = @stack + [name]
223
+ corrected_nesting = []
224
+
225
+ nesting.reverse_each do |name|
226
+ corrected_nesting.prepend(name.delete_prefix("::"))
227
+
228
+ break if name.start_with?("::")
229
+ end
230
+
231
+ corrected_nesting
232
+ end
233
+
234
+ sig { params(name: String, location: Prism::Location).void }
235
+ def collect_constant_references(name, location)
236
+ entries = @index.resolve(name, @stack)
237
+ return unless entries
238
+
239
+ entries.each do |entry|
240
+ next unless entry.name == @fully_qualified_name
241
+
242
+ @references << Reference.new(name, location)
243
+ end
244
+ end
245
+
246
+ sig do
247
+ params(
248
+ node: T.any(
249
+ Prism::ConstantPathNode,
250
+ Prism::ConstantReadNode,
251
+ Prism::ConstantPathTargetNode,
252
+ ),
253
+ ).returns(T.nilable(String))
254
+ end
255
+ def constant_name(node)
256
+ node.full_name
257
+ rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
258
+ Prism::ConstantPathNode::MissingNodesInConstantPathError
259
+ nil
260
+ end
261
+ end
262
+ end
@@ -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/reference_finder"
9
10
  require "ruby_indexer/lib/ruby_indexer/enhancement"
10
11
  require "ruby_indexer/lib/ruby_indexer/index"
11
12
  require "ruby_indexer/lib/ruby_indexer/entry"
@@ -159,6 +159,17 @@ module RubyIndexer
159
159
  assert_entry("Foo::Bar", Entry::Module, "/fake/path/foo.rb:4-2:5-5")
160
160
  end
161
161
 
162
+ def test_nested_modules_and_classes_with_multibyte_characters
163
+ index(<<~RUBY)
164
+ module A動物
165
+ class Bねこ; end
166
+ end
167
+ RUBY
168
+
169
+ assert_entry("A動物", Entry::Module, "/fake/path/foo.rb:0-0:2-3")
170
+ assert_entry("A動物::Bねこ", Entry::Class, "/fake/path/foo.rb:1-2:1-16")
171
+ end
172
+
162
173
  def test_nested_modules_and_classes
163
174
  index(<<~RUBY)
164
175
  module Foo
@@ -21,6 +21,14 @@ module RubyIndexer
21
21
  assert_entry("BAR", Entry::Constant, "/fake/path/foo.rb:6-0:6-7")
22
22
  end
23
23
 
24
+ def test_constant_with_multibyte_characters
25
+ index(<<~RUBY)
26
+ CONST_💎 = "Ruby"
27
+ RUBY
28
+
29
+ assert_entry("CONST_💎", Entry::Constant, "/fake/path/foo.rb:0-0:0-16")
30
+ end
31
+
24
32
  def test_constant_or_writes
25
33
  index(<<~RUBY)
26
34
  FOO ||= 1
@@ -39,6 +39,7 @@ module RubyIndexer
39
39
  location,
40
40
  location,
41
41
  nil,
42
+ index.configuration.encoding,
42
43
  [Entry::Signature.new([Entry::RequiredParameter.new(name: :a)])],
43
44
  Entry::Visibility::PUBLIC,
44
45
  owner,
@@ -121,6 +122,7 @@ module RubyIndexer
121
122
  location,
122
123
  location,
123
124
  nil,
125
+ index.configuration.encoding,
124
126
  [],
125
127
  Entry::Visibility::PUBLIC,
126
128
  owner,
@@ -25,6 +25,18 @@ module RubyIndexer
25
25
  assert_equal("Foo::Bar", owner.name)
26
26
  end
27
27
 
28
+ def test_instance_variable_with_multibyte_characters
29
+ index(<<~RUBY)
30
+ class Foo
31
+ def initialize
32
+ @あ = 1
33
+ end
34
+ end
35
+ RUBY
36
+
37
+ assert_entry("@あ", Entry::InstanceVariable, "/fake/path/foo.rb:2-4:2-6")
38
+ end
39
+
28
40
  def test_instance_variable_and_write
29
41
  index(<<~RUBY)
30
42
  module Foo
@@ -27,6 +27,16 @@ module RubyIndexer
27
27
  assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
28
28
  end
29
29
 
30
+ def test_method_with_multibyte_characters
31
+ index(<<~RUBY)
32
+ class Foo
33
+ def こんにちは; end
34
+ end
35
+ RUBY
36
+
37
+ assert_entry("こんにちは", Entry::Method, "/fake/path/foo.rb:1-2:1-16")
38
+ end
39
+
30
40
  def test_singleton_method_using_self_receiver
31
41
  index(<<~RUBY)
32
42
  class Foo
@@ -348,6 +348,14 @@ module RubyIndexer
348
348
  assert_includes(entry.comments, "Returns `true` if any element of `self` meets a given criterion.")
349
349
  end
350
350
 
351
+ def test_indexing_untyped_functions
352
+ entries = @index.resolve_method("call", "Method")
353
+
354
+ parameters = entries.first.signatures.first.parameters
355
+ assert_equal(1, parameters.length)
356
+ assert_instance_of(Entry::ForwardingParameter, parameters.first)
357
+ end
358
+
351
359
  private
352
360
 
353
361
  def parse_rbs_methods(rbs, method_name)
@@ -0,0 +1,86 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "test_helper"
5
+
6
+ module RubyIndexer
7
+ class ReferenceFinderTest < Minitest::Test
8
+ def test_finds_constant_references
9
+ refs = find_references("Foo::Bar", <<~RUBY)
10
+ module Foo
11
+ class Bar
12
+ end
13
+
14
+ Bar
15
+ end
16
+
17
+ Foo::Bar
18
+ RUBY
19
+
20
+ assert_equal("Bar", refs[0].name)
21
+ assert_equal(2, refs[0].location.start_line)
22
+
23
+ assert_equal("Bar", refs[1].name)
24
+ assert_equal(5, refs[1].location.start_line)
25
+
26
+ assert_equal("Foo::Bar", refs[2].name)
27
+ assert_equal(8, refs[2].location.start_line)
28
+ end
29
+
30
+ def test_finds_constant_references_inside_singleton_contexts
31
+ refs = find_references("Foo::<Class:Foo>::Bar", <<~RUBY)
32
+ class Foo
33
+ class << self
34
+ class Bar
35
+ end
36
+
37
+ Bar
38
+ end
39
+ end
40
+ RUBY
41
+
42
+ assert_equal("Bar", refs[0].name)
43
+ assert_equal(3, refs[0].location.start_line)
44
+
45
+ assert_equal("Bar", refs[1].name)
46
+ assert_equal(6, refs[1].location.start_line)
47
+ end
48
+
49
+ def test_finds_top_level_constant_references
50
+ refs = find_references("Bar", <<~RUBY)
51
+ class Bar
52
+ end
53
+
54
+ class Foo
55
+ ::Bar
56
+
57
+ class << self
58
+ ::Bar
59
+ end
60
+ end
61
+ RUBY
62
+
63
+ assert_equal("Bar", refs[0].name)
64
+ assert_equal(1, refs[0].location.start_line)
65
+
66
+ assert_equal("::Bar", refs[1].name)
67
+ assert_equal(5, refs[1].location.start_line)
68
+
69
+ assert_equal("::Bar", refs[2].name)
70
+ assert_equal(8, refs[2].location.start_line)
71
+ end
72
+
73
+ private
74
+
75
+ def find_references(fully_qualified_name, source)
76
+ file_path = "/fake.rb"
77
+ index = Index.new
78
+ index.index_single(IndexablePath.new(nil, file_path), source)
79
+ parse_result = Prism.parse(source)
80
+ dispatcher = Prism::Dispatcher.new
81
+ finder = ReferenceFinder.new(fully_qualified_name, index, dispatcher)
82
+ dispatcher.visit(parse_result.value)
83
+ finder.references.uniq(&:location)
84
+ end
85
+ end
86
+ end
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
- # To register an addon, inherit from this class and implement both `name` and `activate`
5
+ # To register an add-on, inherit from this class and implement both `name` and `activate`
6
6
  #
7
7
  # # Example
8
8
  #
@@ -14,7 +14,7 @@ module RubyLsp
14
14
  # end
15
15
  #
16
16
  # def name
17
- # "My addon name"
17
+ # "My add-on name"
18
18
  # end
19
19
  # end
20
20
  # end
@@ -27,11 +27,13 @@ module RubyLsp
27
27
 
28
28
  @addons = T.let([], T::Array[Addon])
29
29
  @addon_classes = T.let([], T::Array[T.class_of(Addon)])
30
- # Addon instances that have declared a handler to accept file watcher events
30
+ # Add-on instances that have declared a handler to accept file watcher events
31
31
  @file_watcher_addons = T.let([], T::Array[Addon])
32
32
 
33
33
  AddonNotFoundError = Class.new(StandardError)
34
34
 
35
+ class IncompatibleApiError < StandardError; end
36
+
35
37
  class << self
36
38
  extend T::Sig
37
39
 
@@ -51,15 +53,30 @@ module RubyLsp
51
53
  super
52
54
  end
53
55
 
54
- # Discovers and loads all addons. Returns a list of errors when trying to require addons
56
+ # Discovers and loads all add-ons. Returns a list of errors when trying to require add-ons
55
57
  sig do
56
- params(global_state: GlobalState, outgoing_queue: Thread::Queue).returns(T::Array[StandardError])
58
+ params(
59
+ global_state: GlobalState,
60
+ outgoing_queue: Thread::Queue,
61
+ include_project_addons: T::Boolean,
62
+ ).returns(T::Array[StandardError])
57
63
  end
58
- def load_addons(global_state, outgoing_queue)
59
- # Require all addons entry points, which should be placed under
60
- # `some_gem/lib/ruby_lsp/your_gem_name/addon.rb`
61
- errors = Gem.find_files("ruby_lsp/**/addon.rb").filter_map do |addon|
62
- require File.expand_path(addon)
64
+ def load_addons(global_state, outgoing_queue, include_project_addons: true)
65
+ # Require all add-ons entry points, which should be placed under
66
+ # `some_gem/lib/ruby_lsp/your_gem_name/addon.rb` or in the workspace under
67
+ # `your_project/ruby_lsp/project_name/addon.rb`
68
+ addon_files = Gem.find_files("ruby_lsp/**/addon.rb")
69
+
70
+ if include_project_addons
71
+ addon_files.concat(Dir.glob(File.join(global_state.workspace_path, "**", "ruby_lsp/**/addon.rb")))
72
+ end
73
+
74
+ errors = addon_files.filter_map do |addon_path|
75
+ # Avoid requiring this file twice. This may happen if you're working on the Ruby LSP itself and at the same
76
+ # time have `ruby-lsp` installed as a vendored gem
77
+ next if File.basename(File.dirname(addon_path)) == "ruby_lsp"
78
+
79
+ require File.expand_path(addon_path)
63
80
  nil
64
81
  rescue => e
65
82
  e
@@ -69,7 +86,7 @@ module RubyLsp
69
86
  self.addons = addon_classes.map(&:new)
70
87
  self.file_watcher_addons = addons.select { |addon| addon.respond_to?(:workspace_did_change_watched_files) }
71
88
 
72
- # Activate each one of the discovered addons. If any problems occur in the addons, we don't want to
89
+ # Activate each one of the discovered add-ons. If any problems occur in the add-ons, we don't want to
73
90
  # fail to boot the server
74
91
  addons.each do |addon|
75
92
  addon.activate(global_state, outgoing_queue)
@@ -80,13 +97,53 @@ module RubyLsp
80
97
  errors
81
98
  end
82
99
 
83
- sig { params(addon_name: String).returns(Addon) }
84
- def get(addon_name)
100
+ # Get a reference to another add-on object by name and version. If an add-on exports an API that can be used by
101
+ # other add-ons, this is the way to get access to that API.
102
+ #
103
+ # Important: if the add-on is not found, AddonNotFoundError will be raised. If the add-on is found, but its
104
+ # current version does not satisfy the given version constraint, then IncompatibleApiError will be raised. It is
105
+ # the responsibility of the add-ons using this API to handle these errors appropriately.
106
+ sig { params(addon_name: String, version_constraints: String).returns(Addon) }
107
+ def get(addon_name, *version_constraints)
108
+ if version_constraints.empty?
109
+ raise IncompatibleApiError, "Must specify version constraints when accessing other add-ons"
110
+ end
111
+
85
112
  addon = addons.find { |addon| addon.name == addon_name }
86
- raise AddonNotFoundError, "Could not find addon '#{addon_name}'" unless addon
113
+ raise AddonNotFoundError, "Could not find add-on '#{addon_name}'" unless addon
114
+
115
+ version_object = Gem::Version.new(addon.version)
116
+
117
+ unless version_constraints.all? { |constraint| Gem::Requirement.new(constraint).satisfied_by?(version_object) }
118
+ raise IncompatibleApiError,
119
+ "Constraints #{version_constraints.inspect} is incompatible with #{addon_name} version #{addon.version}"
120
+ end
87
121
 
88
122
  addon
89
123
  end
124
+
125
+ # Depend on a specific version of the Ruby LSP. This method should only be used if the add-on is distributed in a
126
+ # gem that does not have a runtime dependency on the ruby-lsp gem. This method should be invoked at the top of the
127
+ # `addon.rb` file before defining any classes or requiring any files. For example:
128
+ #
129
+ # ```ruby
130
+ # RubyLsp::Addon.depend_on_ruby_lsp!(">= 0.18.0")
131
+ #
132
+ # module MyGem
133
+ # class MyAddon < RubyLsp::Addon
134
+ # # ...
135
+ # end
136
+ # end
137
+ # ```
138
+ sig { params(version_constraints: String).void }
139
+ def depend_on_ruby_lsp!(*version_constraints)
140
+ version_object = Gem::Version.new(RubyLsp::VERSION)
141
+
142
+ unless version_constraints.all? { |constraint| Gem::Requirement.new(constraint).satisfied_by?(version_object) }
143
+ raise IncompatibleApiError,
144
+ "Add-on is not compatible with this version of the Ruby LSP. Skipping its activation"
145
+ end
146
+ end
90
147
  end
91
148
 
92
149
  sig { void }
@@ -118,20 +175,25 @@ module RubyLsp
118
175
  @errors.map(&:full_message).join("\n\n")
119
176
  end
120
177
 
121
- # Each addon should implement `MyAddon#activate` and use to perform any sort of initialization, such as
178
+ # Each add-on should implement `MyAddon#activate` and use to perform any sort of initialization, such as
122
179
  # reading information into memory or even spawning a separate process
123
180
  sig { abstract.params(global_state: GlobalState, outgoing_queue: Thread::Queue).void }
124
181
  def activate(global_state, outgoing_queue); end
125
182
 
126
- # Each addon should implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
183
+ # Each add-on should implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
127
184
  # child process
128
185
  sig { abstract.void }
129
186
  def deactivate; end
130
187
 
131
- # Addons should override the `name` method to return the addon name
188
+ # Add-ons should override the `name` method to return the add-on name
132
189
  sig { abstract.returns(String) }
133
190
  def name; end
134
191
 
192
+ # Add-ons should override the `version` method to return a semantic version string representing the add-on's
193
+ # version. This is used for compatibility checks
194
+ sig { abstract.returns(String) }
195
+ def version; end
196
+
135
197
  # Creates a new CodeLens listener. This method is invoked on every CodeLens request
136
198
  sig do
137
199
  overridable.params(
@@ -130,6 +130,12 @@ module RubyLsp
130
130
  sig { abstract.void }
131
131
  def shutdown; end
132
132
 
133
+ sig { params(id: Integer, message: String, type: Integer).void }
134
+ def fail_request_and_notify(id, message, type: Constant::MessageType::INFO)
135
+ send_message(Error.new(id: id, code: Constant::ErrorCodes::REQUEST_FAILED, message: message))
136
+ send_message(Notification.window_show_message(message, type: type))
137
+ end
138
+
133
139
  sig { returns(Thread) }
134
140
  def new_worker
135
141
  Thread.new do
@@ -27,8 +27,9 @@ module RubyLsp
27
27
  scanner = ERBScanner.new(@source)
28
28
  scanner.scan
29
29
  @host_language_source = scanner.host_language
30
- # assigning empty scopes to turn Prism into eval mode
31
- @parse_result = Prism.parse(scanner.ruby, scopes: [[]])
30
+ # Use partial script to avoid syntax errors in ERB files where keywords may be used without the full context in
31
+ # which they will be evaluated
32
+ @parse_result = Prism.parse(scanner.ruby, partial_script: true)
32
33
  true
33
34
  end
34
35
 
@@ -23,6 +23,9 @@ module RubyLsp
23
23
  sig { returns(T::Boolean) }
24
24
  attr_reader :supports_watching_files, :experimental_features, :supports_request_delegation
25
25
 
26
+ sig { returns(T::Array[String]) }
27
+ attr_reader :supported_resource_operations
28
+
26
29
  sig { returns(TypeInferrer) }
27
30
  attr_reader :type_inferrer
28
31
 
@@ -42,6 +45,7 @@ module RubyLsp
42
45
  @type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
43
46
  @addon_settings = T.let({}, T::Hash[String, T.untyped])
44
47
  @supports_request_delegation = T.let(false, T::Boolean)
48
+ @supported_resource_operations = T.let([], T::Array[String])
45
49
  end
46
50
 
47
51
  sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
@@ -117,6 +121,7 @@ module RubyLsp
117
121
  else
118
122
  Encoding::UTF_32
119
123
  end
124
+ @index.configuration.encoding = @encoding
120
125
 
121
126
  file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
122
127
  if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
@@ -132,6 +137,9 @@ module RubyLsp
132
137
  end
133
138
 
134
139
  @supports_request_delegation = options.dig(:capabilities, :experimental, :requestDelegation) || false
140
+ supported_resource_operations = options.dig(:capabilities, :workspace, :workspaceEdit, :resourceOperations)
141
+ @supported_resource_operations = supported_resource_operations if supported_resource_operations
142
+
135
143
  notifications
136
144
  end
137
145