ruby-lsp 0.17.17 → 0.18.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -110
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +5 -4
  5. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +14 -6
  6. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +157 -27
  7. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +5 -4
  8. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +2 -2
  9. data/lib/ruby_indexer/test/classes_and_modules_test.rb +10 -10
  10. data/lib/ruby_indexer/test/constant_test.rb +4 -4
  11. data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
  12. data/lib/ruby_indexer/test/method_test.rb +257 -2
  13. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  14. data/lib/ruby_lsp/base_server.rb +21 -1
  15. data/lib/ruby_lsp/document.rb +5 -3
  16. data/lib/ruby_lsp/erb_document.rb +29 -10
  17. data/lib/ruby_lsp/global_state.rb +3 -1
  18. data/lib/ruby_lsp/listeners/code_lens.rb +34 -5
  19. data/lib/ruby_lsp/listeners/signature_help.rb +55 -24
  20. data/lib/ruby_lsp/rbs_document.rb +5 -4
  21. data/lib/ruby_lsp/requests/code_action_resolve.rb +0 -15
  22. data/lib/ruby_lsp/requests/code_actions.rb +0 -10
  23. data/lib/ruby_lsp/requests/code_lens.rb +1 -11
  24. data/lib/ruby_lsp/requests/completion.rb +3 -20
  25. data/lib/ruby_lsp/requests/completion_resolve.rb +0 -8
  26. data/lib/ruby_lsp/requests/definition.rb +6 -20
  27. data/lib/ruby_lsp/requests/diagnostics.rb +0 -10
  28. data/lib/ruby_lsp/requests/document_highlight.rb +7 -14
  29. data/lib/ruby_lsp/requests/document_link.rb +0 -10
  30. data/lib/ruby_lsp/requests/document_symbol.rb +0 -17
  31. data/lib/ruby_lsp/requests/folding_ranges.rb +0 -10
  32. data/lib/ruby_lsp/requests/formatting.rb +0 -16
  33. data/lib/ruby_lsp/requests/hover.rb +9 -9
  34. data/lib/ruby_lsp/requests/inlay_hints.rb +0 -30
  35. data/lib/ruby_lsp/requests/on_type_formatting.rb +0 -10
  36. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +0 -11
  37. data/lib/ruby_lsp/requests/request.rb +17 -1
  38. data/lib/ruby_lsp/requests/selection_ranges.rb +0 -10
  39. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -23
  40. data/lib/ruby_lsp/requests/show_syntax_tree.rb +0 -11
  41. data/lib/ruby_lsp/requests/signature_help.rb +5 -20
  42. data/lib/ruby_lsp/requests/support/common.rb +1 -1
  43. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +2 -0
  44. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +0 -11
  45. data/lib/ruby_lsp/requests/workspace_symbol.rb +0 -12
  46. data/lib/ruby_lsp/ruby_document.rb +4 -3
  47. data/lib/ruby_lsp/server.rb +23 -8
  48. data/lib/ruby_lsp/setup_bundler.rb +31 -13
  49. data/lib/ruby_lsp/type_inferrer.rb +6 -2
  50. data/lib/ruby_lsp/utils.rb +11 -1
  51. metadata +3 -4
  52. data/lib/ruby_lsp/check_docs.rb +0 -130
@@ -3,20 +3,9 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
- # ![Type hierarchy supertypes demo](../../type_hierarchy_supertypes.gif)
7
- #
8
6
  # The [type hierarchy supertypes
9
7
  # request](https://microsoft.github.io/language-server-protocol/specification#typeHierarchy_supertypes)
10
8
  # displays the list of ancestors (supertypes) for the selected type.
11
- #
12
- # # Example
13
- #
14
- # ```ruby
15
- # class Foo; end
16
- # class Bar < Foo; end
17
- #
18
- # puts Bar # <-- right click on `Bar` and select "Show Type Hierarchy"
19
- # ```
20
9
  class TypeHierarchySupertypes < Request
21
10
  extend T::Sig
22
11
 
@@ -3,21 +3,9 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
- # ![Workspace symbol demo](../../workspace_symbol.gif)
7
- #
8
6
  # The [workspace symbol](https://microsoft.github.io/language-server-protocol/specification#workspace_symbol)
9
7
  # request allows fuzzy searching declarations in the entire project. On VS Code, use CTRL/CMD + T to search for
10
8
  # symbols.
11
- #
12
- # # Example
13
- #
14
- # ```ruby
15
- # # Searching for `Floo` will fuzzy match and return all declarations according to the query, including this `Foo`
16
- # class
17
- # class Foo
18
- # end
19
- # ```
20
- #
21
9
  class WorkspaceSymbol < Request
22
10
  extend T::Sig
23
11
  include Support::Common
@@ -121,12 +121,13 @@ module RubyLsp
121
121
  end
122
122
  end
123
123
 
124
- sig { override.returns(ParseResultType) }
125
- def parse
126
- return @parse_result unless @needs_parsing
124
+ sig { override.returns(T::Boolean) }
125
+ def parse!
126
+ return false unless @needs_parsing
127
127
 
128
128
  @needs_parsing = false
129
129
  @parse_result = Prism.parse(@source)
130
+ true
130
131
  end
131
132
 
132
133
  sig { override.returns(T::Boolean) }
@@ -9,12 +9,6 @@ module RubyLsp
9
9
  sig { returns(GlobalState) }
10
10
  attr_reader :global_state
11
11
 
12
- sig { params(test_mode: T::Boolean).void }
13
- def initialize(test_mode: false)
14
- super
15
- @global_state = T.let(GlobalState.new, GlobalState)
16
- end
17
-
18
12
  sig { override.params(message: T::Hash[Symbol, T.untyped]).void }
19
13
  def process_message(message)
20
14
  case message[:method]
@@ -98,6 +92,8 @@ module RubyLsp
98
92
  when "$/cancelRequest"
99
93
  @mutex.synchronize { @cancelled_requests << message[:params][:id] }
100
94
  end
95
+ rescue DelegateRequestError
96
+ send_message(Error.new(id: message[:id], code: DelegateRequestError::CODE, message: "DELEGATE_REQUEST"))
101
97
  rescue StandardError, LoadError => e
102
98
  # If an error occurred in a request, we have to return an error response or else the editor will hang
103
99
  if message[:id]
@@ -284,7 +280,15 @@ module RubyLsp
284
280
  RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
285
281
 
286
282
  if defined?(Requests::Support::RuboCopFormatter)
287
- @global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
283
+ begin
284
+ @global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
285
+ rescue RuboCop::Error => e
286
+ # The user may have provided unknown config switches in .rubocop or
287
+ # is trying to load a non-existant config file.
288
+ send_message(Notification.window_show_error(
289
+ "RuboCop configuration error: #{e.message}. Formatting will not be available.",
290
+ ))
291
+ end
288
292
  end
289
293
  if defined?(Requests::Support::SyntaxTreeFormatter)
290
294
  @global_state.register_formatter("syntax_tree", Requests::Support::SyntaxTreeFormatter.new)
@@ -543,7 +547,7 @@ module RubyLsp
543
547
  return
544
548
  end
545
549
 
546
- request = Requests::DocumentHighlight.new(document, params[:position], dispatcher)
550
+ request = Requests::DocumentHighlight.new(@global_state, document, params[:position], dispatcher)
547
551
  dispatcher.dispatch(document.parse_result.value)
548
552
  send_message(Result.new(id: message[:id], response: request.perform))
549
553
  end
@@ -740,6 +744,17 @@ module RubyLsp
740
744
 
741
745
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
742
746
  def text_document_completion_item_resolve(message)
747
+ # When responding to a delegated completion request, it means we're handling a completion item that isn't related
748
+ # to Ruby (probably related to an ERB host language like HTML). We need to return the original completion item
749
+ # back to the editor so that it's displayed correctly
750
+ if message.dig(:params, :data, :delegateCompletion)
751
+ send_message(Result.new(
752
+ id: message[:id],
753
+ response: message[:params],
754
+ ))
755
+ return
756
+ end
757
+
743
758
  send_message(Result.new(
744
759
  id: message[:id],
745
760
  response: Requests::CompletionResolve.new(@global_state, message[:params]).perform,
@@ -56,7 +56,7 @@ module RubyLsp
56
56
 
57
57
  # Sets up the custom bundle and returns the `BUNDLE_GEMFILE`, `BUNDLE_PATH` and `BUNDLE_APP_CONFIG` that should be
58
58
  # used for running the server
59
- sig { returns([String, T.nilable(String), T.nilable(String)]) }
59
+ sig { returns(T::Hash[String, String]) }
60
60
  def setup!
61
61
  raise BundleNotLocked if @gemfile&.exist? && !@lockfile&.exist?
62
62
 
@@ -176,22 +176,18 @@ module RubyLsp
176
176
  dependencies
177
177
  end
178
178
 
179
- sig { params(bundle_gemfile: T.nilable(Pathname)).returns([String, T.nilable(String), T.nilable(String)]) }
179
+ sig { params(bundle_gemfile: T.nilable(Pathname)).returns(T::Hash[String, String]) }
180
180
  def run_bundle_install(bundle_gemfile = @gemfile)
181
+ env = bundler_settings_as_env
182
+ env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
183
+
181
184
  # If the user has a custom bundle path configured, we need to ensure that we will use the absolute and not
182
185
  # relative version of it when running `bundle install`. This is necessary to avoid installing the gems under the
183
186
  # `.ruby-lsp` folder, which is not the user's intention. For example, if the path is configured as `vendor`, we
184
187
  # want to install it in the top level `vendor` and not `.ruby-lsp/vendor`
185
- path = Bundler.settings["path"]
186
- expanded_path = File.expand_path(path, @project_path) if path
187
-
188
- # Use the absolute `BUNDLE_PATH` to prevent accidentally creating unwanted folders under `.ruby-lsp`
189
- env = {}
190
- env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
191
- env["BUNDLE_PATH"] = expanded_path if expanded_path
192
-
193
- local_config_path = File.join(@project_path, ".bundle")
194
- env["BUNDLE_APP_CONFIG"] = local_config_path if Dir.exist?(local_config_path)
188
+ if env["BUNDLE_PATH"]
189
+ env["BUNDLE_PATH"] = File.expand_path(env["BUNDLE_PATH"], @project_path)
190
+ end
195
191
 
196
192
  # If `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't try
197
193
  # to upgrade them or else we'll produce undesired source control changes. If the custom bundle was just created
@@ -238,7 +234,29 @@ module RubyLsp
238
234
  return setup!
239
235
  end
240
236
 
241
- [bundle_gemfile.to_s, expanded_path, env["BUNDLE_APP_CONFIG"]]
237
+ env
238
+ end
239
+
240
+ # Gather all Bundler settings (global and local) and return them as a hash that can be used as the environment
241
+ sig { returns(T::Hash[String, String]) }
242
+ def bundler_settings_as_env
243
+ local_config_path = File.join(@project_path, ".bundle")
244
+
245
+ # If there's no Gemfile or if the local config path does not exist, we return an empty setting set (which has the
246
+ # global settings included). Otherwise, we also load the local settings
247
+ settings = begin
248
+ Dir.exist?(local_config_path) ? Bundler::Settings.new(local_config_path) : Bundler::Settings.new
249
+ rescue Bundler::GemfileNotFound
250
+ Bundler::Settings.new
251
+ end
252
+
253
+ # Map all settings to their environment variable names with `key_for` and their values. For example, the if the
254
+ # setting name `e` is `path` with a value of `vendor/bundle`, then it will return `"BUNDLE_PATH" =>
255
+ # "vendor/bundle"`
256
+ settings.all.to_h do |e|
257
+ key = Bundler::Settings.key_for(e)
258
+ [key, settings[e].to_s]
259
+ end
242
260
  end
243
261
 
244
262
  sig { returns(T::Boolean) }
@@ -121,8 +121,12 @@ module RubyLsp
121
121
  return Type.new(node_context.fully_qualified_name) if node_context.surrounding_method
122
122
 
123
123
  # If we're not inside a method, then we're inside the body of a class or module, which is a singleton
124
- # context
125
- Type.new("#{nesting.join("::")}::<Class:#{nesting.last}>")
124
+ # context.
125
+ #
126
+ # If the class/module definition is using compact style (e.g.: `class Foo::Bar`), then we need to split the name
127
+ # into its individual parts to build the correct singleton name
128
+ parts = nesting.flat_map { |part| part.split("::") }
129
+ Type.new("#{parts.join("::")}::<Class:#{parts.last}>")
126
130
  end
127
131
 
128
132
  sig do
@@ -25,7 +25,17 @@ module RubyLsp
25
25
  end,
26
26
  String,
27
27
  )
28
- GUESSED_TYPES_URL = "https://github.com/Shopify/ruby-lsp/blob/main/DESIGN_AND_ROADMAP.md#guessed-types"
28
+ GUESSED_TYPES_URL = "https://shopify.github.io/ruby-lsp/design-and-roadmap.html#guessed-types"
29
+
30
+ # Request delegation for embedded languages is not yet standardized into the language server specification. Here we
31
+ # use this custom error class as a way to return a signal to the client that the request should be delegated to the
32
+ # language server for the host language. The support for delegation is custom built on the client side, so each editor
33
+ # needs to implement their own until this becomes a part of the spec
34
+ class DelegateRequestError < StandardError
35
+ # A custom error code that clients can use to handle delegate requests. This is past the range of error codes listed
36
+ # by the specification to avoid conflicting with other error types
37
+ CODE = -32900
38
+ end
29
39
 
30
40
  # A notification to be sent to the client
31
41
  class Message
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.17
4
+ version: 0.18.0
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-29 00:00:00.000000000 Z
11
+ date: 2024-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -112,7 +112,6 @@ files:
112
112
  - lib/ruby_indexer/test/test_case.rb
113
113
  - lib/ruby_lsp/addon.rb
114
114
  - lib/ruby_lsp/base_server.rb
115
- - lib/ruby_lsp/check_docs.rb
116
115
  - lib/ruby_lsp/document.rb
117
116
  - lib/ruby_lsp/erb_document.rb
118
117
  - lib/ruby_lsp/global_state.rb
@@ -201,7 +200,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
201
200
  - !ruby/object:Gem::Version
202
201
  version: '0'
203
202
  requirements: []
204
- rubygems_version: 3.5.17
203
+ rubygems_version: 3.5.18
205
204
  signing_key:
206
205
  specification_version: 4
207
206
  summary: An opinionated language server for Ruby
@@ -1,130 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "ruby_lsp/internal"
5
- require "objspace"
6
-
7
- module RubyLsp
8
- # This rake task checks that all requests or addons are fully documented. Add the rake task to your Rakefile and
9
- # specify the absolute path for all files that must be required in order to discover all requests and their related
10
- # GIFs
11
- #
12
- # # Rakefile
13
- # request_files = FileList.new("#{__dir__}/lib/ruby_lsp/requests/*.rb") do |fl|
14
- # fl.exclude(/base_request\.rb/)
15
- # end
16
- # gif_files = FileList.new("#{__dir__}/**/*.gif")
17
- # RubyLsp::CheckDocs.new(request_files, gif_files)
18
- # # Run with bundle exec rake ruby_lsp:check_docs
19
- class CheckDocs < Rake::TaskLib
20
- extend T::Sig
21
-
22
- sig { params(require_files: Rake::FileList, gif_files: Rake::FileList).void }
23
- def initialize(require_files, gif_files)
24
- super()
25
-
26
- @name = T.let("ruby_lsp:check_docs", String)
27
- @file_list = require_files
28
- @gif_list = gif_files
29
- define_task
30
- end
31
-
32
- private
33
-
34
- sig { void }
35
- def define_task
36
- desc("Checks if all Ruby LSP requests are documented")
37
- task(@name) { run_task }
38
- end
39
-
40
- sig { params(request_path: String).returns(T::Boolean) }
41
- def gif_exists?(request_path)
42
- request_gif = request_path.gsub(".rb", ".gif").split("/").last
43
-
44
- @gif_list.any? { |gif_path| gif_path.end_with?(request_gif) }
45
- end
46
-
47
- sig { void }
48
- def run_task
49
- # Require all files configured to make sure all requests are loaded
50
- @file_list.each { |f| require(f.delete_suffix(".rb")) }
51
-
52
- # Find all classes that inherit from BaseRequest, which are the ones we want to make sure are
53
- # documented
54
- features = ObjectSpace.each_object(Class).select do |k|
55
- klass = T.unsafe(k)
56
- klass < Requests::Request
57
- end
58
-
59
- missing_docs = T.let(Hash.new { |h, k| h[k] = [] }, T::Hash[String, T::Array[String]])
60
-
61
- features.each do |klass|
62
- class_name = T.unsafe(klass).name
63
- file_path, line_number = Module.const_source_location(class_name)
64
- next unless file_path && line_number
65
-
66
- # Adjust the line number to start searching right above the class definition
67
- line_number -= 2
68
-
69
- lines = File.readlines(file_path)
70
- docs = []
71
-
72
- # Extract the documentation on top of the request constant
73
- while (line = lines[line_number]&.strip) && line.start_with?("#")
74
- docs.unshift(line)
75
- line_number -= 1
76
- end
77
-
78
- documentation = docs.join("\n")
79
-
80
- if docs.empty?
81
- T.must(missing_docs[class_name]) << "No documentation found"
82
- elsif !%r{\(https://microsoft.github.io/language-server-protocol/specification#.*\)}.match?(documentation)
83
- T.must(missing_docs[class_name]) << <<~DOCS
84
- Missing specification link. Requests and addons should include a link to the LSP specification for the
85
- related feature. For example:
86
-
87
- [Inlay hint](https://microsoft.github.io/language-server-protocol/specification#textDocument_inlayHint)
88
- DOCS
89
- elsif !documentation.include?("# Example")
90
- T.must(missing_docs[class_name]) << <<~DOCS
91
- Missing example. Requests and addons should include a code example that explains what the feature does.
92
-
93
- # # Example
94
- # ```ruby
95
- # class Foo # <- information is shown here
96
- # end
97
- # ```
98
- DOCS
99
- elsif !/\[.* demo\]\(.*\.gif\)/.match?(documentation)
100
- T.must(missing_docs[class_name]) << <<~DOCS
101
- Missing demonstration GIF. Each request and addon must be documented with a GIF that shows the feature
102
- working. For example:
103
-
104
- # [Inlay hint demo](../../inlay_hint.gif)
105
- DOCS
106
- elsif !gif_exists?(file_path)
107
- T.must(missing_docs[class_name]) << <<~DOCS
108
- The GIF for the request documentation does not exist. Make sure to add it,
109
- with the same naming as the request. For example:
110
-
111
- # lib/ruby_lsp/requests/code_lens.rb
112
- # foo/bar/code_lens.gif
113
- DOCS
114
- end
115
- end
116
-
117
- if missing_docs.any?
118
- $stderr.puts(<<~WARN)
119
- The following requests are missing documentation:
120
-
121
- #{missing_docs.map { |k, v| "#{k}\n\n#{v.join("\n")}" }.join("\n\n")}
122
- WARN
123
-
124
- abort
125
- end
126
-
127
- puts "All requests are documented!"
128
- end
129
- end
130
- end