ruby-lsp 0.7.6 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +41 -33
  4. data/exe/ruby-lsp-check +2 -2
  5. data/lib/core_ext/uri.rb +40 -0
  6. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +91 -0
  7. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +122 -0
  8. data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +121 -0
  9. data/lib/ruby_indexer/ruby_indexer.rb +19 -0
  10. data/lib/ruby_indexer/test/classes_and_modules_test.rb +204 -0
  11. data/lib/ruby_indexer/test/configuration_test.rb +35 -0
  12. data/lib/ruby_indexer/test/constant_test.rb +108 -0
  13. data/lib/ruby_indexer/test/index_test.rb +94 -0
  14. data/lib/ruby_indexer/test/test_case.rb +42 -0
  15. data/lib/ruby_lsp/document.rb +3 -3
  16. data/lib/ruby_lsp/executor.rb +131 -24
  17. data/lib/ruby_lsp/extension.rb +24 -0
  18. data/lib/ruby_lsp/internal.rb +4 -0
  19. data/lib/ruby_lsp/listener.rb +15 -14
  20. data/lib/ruby_lsp/requests/code_actions.rb +3 -3
  21. data/lib/ruby_lsp/requests/code_lens.rb +10 -24
  22. data/lib/ruby_lsp/requests/definition.rb +55 -8
  23. data/lib/ruby_lsp/requests/diagnostics.rb +3 -2
  24. data/lib/ruby_lsp/requests/document_link.rb +4 -3
  25. data/lib/ruby_lsp/requests/formatting.rb +3 -2
  26. data/lib/ruby_lsp/requests/hover.rb +4 -18
  27. data/lib/ruby_lsp/requests/on_type_formatting.rb +4 -6
  28. data/lib/ruby_lsp/requests/support/dependency_detector.rb +5 -0
  29. data/lib/ruby_lsp/requests/support/formatter_runner.rb +1 -1
  30. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +2 -2
  31. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +2 -3
  32. data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +2 -3
  33. data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +3 -2
  34. data/lib/ruby_lsp/server.rb +10 -2
  35. data/lib/ruby_lsp/setup_bundler.rb +28 -14
  36. data/lib/ruby_lsp/store.rb +20 -13
  37. data/lib/ruby_lsp/utils.rb +1 -1
  38. metadata +27 -3
@@ -58,7 +58,7 @@ module RubyLsp
58
58
  def initialize(document, formatter: "auto")
59
59
  super(document)
60
60
 
61
- @uri = T.let(document.uri, String)
61
+ @uri = T.let(document.uri, URI::Generic)
62
62
  @formatter = formatter
63
63
  end
64
64
 
@@ -67,7 +67,8 @@ module RubyLsp
67
67
  return if @formatter == "none"
68
68
 
69
69
  # Don't try to format files outside the current working directory
70
- return unless @uri.sub("file://", "").start_with?(Dir.pwd)
70
+ path = @uri.to_standardized_path
71
+ return unless path.nil? || path.start_with?(T.must(WORKSPACE_URI.to_standardized_path))
71
72
 
72
73
  return if @document.syntax_error?
73
74
 
@@ -39,29 +39,15 @@ module RubyLsp
39
39
  def initialize(emitter, message_queue)
40
40
  super
41
41
 
42
- @external_listeners = T.let([], T::Array[RubyLsp::Listener[ResponseType]])
42
+ @external_listeners.concat(
43
+ Extension.extensions.filter_map { |ext| ext.create_hover_listener(emitter, message_queue) },
44
+ )
43
45
  @response = T.let(nil, ResponseType)
44
46
  emitter.register(self, :on_command, :on_const_path_ref, :on_call)
45
-
46
- register_external_listeners!
47
- end
48
-
49
- sig { void }
50
- def register_external_listeners!
51
- self.class.listeners.each do |l|
52
- @external_listeners << T.unsafe(l).new(@emitter, @message_queue)
53
- end
54
- end
55
-
56
- sig { void }
57
- def merge_external_listeners_responses!
58
- @external_listeners.each do |l|
59
- merge_response!(l)
60
- end
61
47
  end
62
48
 
63
49
  # Merges responses from other hover listeners
64
- sig { params(other: Listener[ResponseType]).returns(T.self_type) }
50
+ sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
65
51
  def merge_response!(other)
66
52
  other_response = other.response
67
53
  return self unless other_response
@@ -67,12 +67,10 @@ module RubyLsp
67
67
 
68
68
  line = T.must(current_line)
69
69
 
70
- # If the current character is a pipe and both previous ones are pipes too, then we autocompleted a pipe and the
71
- # user inserted a third one. In this case, we need to avoid adding a fourth and remove the previous one
72
- if line[@position[:character] - 2] == "|" &&
73
- line[@position[:character] - 1] == "|" &&
74
- line[@position[:character]] == "|"
75
-
70
+ # If the user inserts the closing pipe manually to the end of the block argument, we need to avoid adding
71
+ # an additional one and remove the previous one. This also helps to remove the user who accidentally
72
+ # inserts another pipe after the autocompleted one.
73
+ if line[..@position[:character]] =~ /(do|{)\s+\|[^|]*\|\|$/
76
74
  @edits << Interface::TextEdit.new(
77
75
  range: Interface::Range.new(
78
76
  start: Interface::Position.new(
@@ -36,6 +36,11 @@ module RubyLsp
36
36
  end
37
37
  end
38
38
 
39
+ sig { returns(T::Boolean) }
40
+ def typechecker?
41
+ direct_dependency?(/^sorbet/) || direct_dependency?(/^sorbet-static-and-runtime/)
42
+ end
43
+
39
44
  sig { params(gem_pattern: Regexp).returns(T::Boolean) }
40
45
  def direct_dependency?(gem_pattern)
41
46
  Bundler.with_original_env { Bundler.default_gemfile } &&
@@ -10,7 +10,7 @@ module RubyLsp
10
10
 
11
11
  interface!
12
12
 
13
- sig { abstract.params(uri: String, document: Document).returns(T.nilable(String)) }
13
+ sig { abstract.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
14
14
  def run(uri, document); end
15
15
  end
16
16
  end
@@ -19,7 +19,7 @@ module RubyLsp
19
19
  T::Hash[Symbol, Integer],
20
20
  )
21
21
 
22
- sig { params(offense: RuboCop::Cop::Offense, uri: String).void }
22
+ sig { params(offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
23
23
  def initialize(offense, uri)
24
24
  @offense = offense
25
25
  @uri = uri
@@ -34,7 +34,7 @@ module RubyLsp
34
34
  document_changes: [
35
35
  Interface::TextDocumentEdit.new(
36
36
  text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
37
- uri: @uri,
37
+ uri: @uri.to_s,
38
38
  version: nil,
39
39
  ),
40
40
  edits: @offense.correctable? ? offense_replacements : [],
@@ -3,7 +3,6 @@
3
3
 
4
4
  return unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
5
5
 
6
- require "cgi"
7
6
  require "singleton"
8
7
 
9
8
  module RubyLsp
@@ -19,9 +18,9 @@ module RubyLsp
19
18
  @runner = T.let(RuboCopRunner.new, RuboCopRunner)
20
19
  end
21
20
 
22
- sig { params(uri: String, document: Document).returns(T::Array[Support::RuboCopDiagnostic]) }
21
+ sig { params(uri: URI::Generic, document: Document).returns(T::Array[Support::RuboCopDiagnostic]) }
23
22
  def run(uri, document)
24
- filename = CGI.unescape(URI.parse(uri).path)
23
+ filename = T.must(uri.to_standardized_path || uri.opaque)
25
24
  # Invoke RuboCop with just this file in `paths`
26
25
  @runner.run(filename, document.source)
27
26
 
@@ -3,7 +3,6 @@
3
3
 
4
4
  return unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
5
5
 
6
- require "cgi"
7
6
  require "singleton"
8
7
 
9
8
  module RubyLsp
@@ -21,9 +20,9 @@ module RubyLsp
21
20
  @runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
22
21
  end
23
22
 
24
- sig { override.params(uri: String, document: Document).returns(String) }
23
+ sig { override.params(uri: URI::Generic, document: Document).returns(String) }
25
24
  def run(uri, document)
26
- filename = CGI.unescape(URI.parse(uri).path)
25
+ filename = T.must(uri.to_standardized_path || uri.opaque)
27
26
 
28
27
  # Invoke RuboCop with just this file in `paths`
29
28
  @runner.run(filename, document.source)
@@ -26,9 +26,10 @@ module RubyLsp
26
26
  )
27
27
  end
28
28
 
29
- sig { override.params(uri: String, document: Document).returns(T.nilable(String)) }
29
+ sig { override.params(uri: URI::Generic, document: Document).returns(T.nilable(String)) }
30
30
  def run(uri, document)
31
- relative_path = Pathname.new(URI(uri).path).relative_path_from(T.must(WORKSPACE_URI.path))
31
+ relative_path = Pathname.new(T.must(uri.to_standardized_path || uri.opaque))
32
+ .relative_path_from(T.must(WORKSPACE_URI.to_standardized_path))
32
33
  return if @options.ignore_files.any? { |pattern| File.fnmatch(pattern, relative_path) }
33
34
 
34
35
  SyntaxTree.format(
@@ -86,6 +86,7 @@ module RubyLsp
86
86
  @message_dispatcher.join
87
87
  @store.clear
88
88
 
89
+ Extension.extensions.each(&:deactivate)
89
90
  finalize_request(Result.new(response: nil), request)
90
91
  when "exit"
91
92
  # We return zero if shutdown has already been received or one otherwise as per the recommendation in the spec
@@ -105,7 +106,7 @@ module RubyLsp
105
106
  # source. Altering the source reference during parsing will put the parser in an invalid internal state,
106
107
  # since it started parsing with one source but then it changed in the middle
107
108
  uri = request.dig(:params, :textDocument, :uri)
108
- @store.get(uri).parse if uri
109
+ @store.get(URI(uri)).parse if uri
109
110
  end
110
111
 
111
112
  @job_queue << job
@@ -192,7 +193,14 @@ module RubyLsp
192
193
  params[:backtrace] = backtrace.map { |bt| bt.sub(/^#{Dir.home}/, "~") }.join("\n") if backtrace
193
194
  end
194
195
 
195
- params[:uri] = uri.sub(%r{.*://#{Dir.home}}, "~") if uri
196
+ if uri
197
+ home = URI::Generic.from_path(path: Dir.home)
198
+
199
+ parsed_uri = URI(uri)
200
+ path = parsed_uri.path
201
+ params[:uri] = path ? path.sub(T.must(home.path), "~") : parsed_uri.opaque
202
+ end
203
+
196
204
  params
197
205
  end
198
206
  end
@@ -5,6 +5,7 @@ require "sorbet-runtime"
5
5
  require "bundler"
6
6
  require "fileutils"
7
7
  require "pathname"
8
+ require "digest"
8
9
 
9
10
  # This file is a script that will configure a custom bundle for the Ruby LSP. The custom bundle allows developers to use
10
11
  # the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to the
@@ -16,14 +17,16 @@ module RubyLsp
16
17
 
17
18
  class BundleNotLocked < StandardError; end
18
19
 
19
- sig { params(project_path: String).void }
20
- def initialize(project_path)
20
+ sig { params(project_path: String, branch: T.nilable(String)).void }
21
+ def initialize(project_path, branch: nil)
21
22
  @project_path = project_path
23
+ @branch = branch
22
24
 
23
25
  # Custom bundle paths
24
26
  @custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(Dir.pwd), Pathname)
25
27
  @custom_gemfile = T.let(@custom_dir + "Gemfile", Pathname)
26
28
  @custom_lockfile = T.let(@custom_dir + "Gemfile.lock", Pathname)
29
+ @lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
27
30
 
28
31
  # Regular bundle paths
29
32
  @gemfile = T.let(
@@ -37,7 +40,6 @@ module RubyLsp
37
40
  @lockfile = T.let(@gemfile ? Bundler.default_lockfile : nil, T.nilable(Pathname))
38
41
 
39
42
  @dependencies = T.let(load_dependencies, T::Hash[String, T.untyped])
40
- @custom_bundle_dependencies = T.let(custom_bundle_dependencies, T::Hash[String, T.untyped])
41
43
  end
42
44
 
43
45
  # Setups up the custom bundle and returns the `BUNDLE_GEMFILE` and `BUNDLE_PATH` that should be used for running the
@@ -69,14 +71,16 @@ module RubyLsp
69
71
  return run_bundle_install(@custom_gemfile)
70
72
  end
71
73
 
72
- # If .ruby-lsp/Gemfile.lock already exists and the top level Gemfile.lock hasn't been modified since it was last
73
- # updated, then we're ready to boot the server
74
- if @custom_lockfile.exist? && @custom_lockfile.stat.mtime > @lockfile.stat.mtime
74
+ lockfile_contents = @lockfile.read
75
+ current_lockfile_hash = Digest::SHA256.hexdigest(lockfile_contents)
76
+
77
+ if @custom_lockfile.exist? && @lockfile_hash_path.exist? && @lockfile_hash_path.read == current_lockfile_hash
75
78
  warn("Ruby LSP> Skipping custom bundle setup since #{@custom_lockfile} already exists and is up to date")
76
79
  return run_bundle_install(@custom_gemfile)
77
80
  end
78
81
 
79
82
  FileUtils.cp(@lockfile.to_s, @custom_lockfile.to_s)
83
+ @lockfile_hash_path.write(current_lockfile_hash)
80
84
  run_bundle_install(@custom_gemfile)
81
85
  end
82
86
 
@@ -84,10 +88,17 @@ module RubyLsp
84
88
 
85
89
  sig { returns(T::Hash[String, T.untyped]) }
86
90
  def custom_bundle_dependencies
87
- return {} unless @custom_lockfile.exist?
88
-
89
- ENV["BUNDLE_GEMFILE"] = @custom_gemfile.to_s
90
- Bundler::LockfileParser.new(@custom_lockfile.read).dependencies
91
+ @custom_bundle_dependencies ||= T.let(
92
+ begin
93
+ if @custom_lockfile.exist?
94
+ ENV["BUNDLE_GEMFILE"] = @custom_gemfile.to_s
95
+ Bundler::LockfileParser.new(@custom_lockfile.read).dependencies
96
+ else
97
+ {}
98
+ end
99
+ end,
100
+ T.nilable(T::Hash[String, T.untyped]),
101
+ )
91
102
  ensure
92
103
  ENV.delete("BUNDLE_GEMFILE")
93
104
  end
@@ -109,7 +120,9 @@ module RubyLsp
109
120
  end
110
121
 
111
122
  unless @dependencies["ruby-lsp"]
112
- parts << 'gem "ruby-lsp", require: false, group: :development'
123
+ ruby_lsp_entry = +'gem "ruby-lsp", require: false, group: :development'
124
+ ruby_lsp_entry << ", github: \"Shopify/ruby-lsp\", branch: \"#{@branch}\"" if @branch
125
+ parts << ruby_lsp_entry
113
126
  end
114
127
 
115
128
  unless @dependencies["debug"]
@@ -146,11 +159,12 @@ module RubyLsp
146
159
  # `.ruby-lsp` folder, which is not the user's intention. For example, if the path is configured as `vendor`, we
147
160
  # want to install it in the top level `vendor` and not `.ruby-lsp/vendor`
148
161
  path = Bundler.settings["path"]
162
+ expanded_path = File.expand_path(path, Dir.pwd) if path
149
163
 
150
164
  # Use the absolute `BUNDLE_PATH` to prevent accidentally creating unwanted folders under `.ruby-lsp`
151
165
  env = {}
152
166
  env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
153
- env["BUNDLE_PATH"] = File.expand_path(path, Dir.pwd) if path
167
+ env["BUNDLE_PATH"] = expanded_path if expanded_path
154
168
 
155
169
  # If both `ruby-lsp` and `debug` are already in the Gemfile, then we shouldn't try to upgrade them or else we'll
156
170
  # produce undesired source control changes. If the custom bundle was just created and either `ruby-lsp` or `debug`
@@ -160,7 +174,7 @@ module RubyLsp
160
174
  command = +""
161
175
 
162
176
  if (@dependencies["ruby-lsp"] && @dependencies["debug"]) ||
163
- @custom_bundle_dependencies["ruby-lsp"].nil? || @custom_bundle_dependencies["debug"].nil?
177
+ custom_bundle_dependencies["ruby-lsp"].nil? || custom_bundle_dependencies["debug"].nil?
164
178
  # Install gems using the custom bundle
165
179
  command << "bundle install "
166
180
  else
@@ -177,7 +191,7 @@ module RubyLsp
177
191
  # Add bundle update
178
192
  warn("Ruby LSP> Running bundle install for the custom bundle. This may take a while...")
179
193
  system(env, command)
180
- [bundle_gemfile.to_s, path]
194
+ [bundle_gemfile.to_s, expanded_path]
181
195
  end
182
196
  end
183
197
  end
@@ -1,8 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "cgi"
5
- require "uri"
6
4
  require "ruby_lsp/document"
7
5
 
8
6
  module RubyLsp
@@ -15,31 +13,40 @@ module RubyLsp
15
13
  sig { returns(String) }
16
14
  attr_accessor :formatter
17
15
 
16
+ sig { returns(T::Boolean) }
17
+ attr_accessor :supports_progress
18
+
19
+ sig { returns(T::Boolean) }
20
+ attr_accessor :experimental_features
21
+
18
22
  sig { void }
19
23
  def initialize
20
24
  @state = T.let({}, T::Hash[String, Document])
21
25
  @encoding = T.let(Constant::PositionEncodingKind::UTF8, String)
22
26
  @formatter = T.let("auto", String)
27
+ @supports_progress = T.let(true, T::Boolean)
28
+ @experimental_features = T.let(false, T::Boolean)
23
29
  end
24
30
 
25
- sig { params(uri: String).returns(Document) }
31
+ sig { params(uri: URI::Generic).returns(Document) }
26
32
  def get(uri)
27
- document = @state[uri]
33
+ document = @state[uri.to_s]
28
34
  return document unless document.nil?
29
35
 
30
- set(uri: uri, source: File.binread(CGI.unescape(URI.parse(uri).path)), version: 0)
31
- T.must(@state[uri])
36
+ path = T.must(uri.to_standardized_path)
37
+ set(uri: uri, source: File.binread(path), version: 0)
38
+ T.must(@state[uri.to_s])
32
39
  end
33
40
 
34
- sig { params(uri: String, source: String, version: Integer).void }
41
+ sig { params(uri: URI::Generic, source: String, version: Integer).void }
35
42
  def set(uri:, source:, version:)
36
43
  document = Document.new(source: source, version: version, uri: uri, encoding: @encoding)
37
- @state[uri] = document
44
+ @state[uri.to_s] = document
38
45
  end
39
46
 
40
- sig { params(uri: String, edits: T::Array[Document::EditShape], version: Integer).void }
47
+ sig { params(uri: URI::Generic, edits: T::Array[Document::EditShape], version: Integer).void }
41
48
  def push_edits(uri:, edits:, version:)
42
- T.must(@state[uri]).push_edits(edits, version: version)
49
+ T.must(@state[uri.to_s]).push_edits(edits, version: version)
43
50
  end
44
51
 
45
52
  sig { void }
@@ -52,15 +59,15 @@ module RubyLsp
52
59
  @state.empty?
53
60
  end
54
61
 
55
- sig { params(uri: String).void }
62
+ sig { params(uri: URI::Generic).void }
56
63
  def delete(uri)
57
- @state.delete(uri)
64
+ @state.delete(uri.to_s)
58
65
  end
59
66
 
60
67
  sig do
61
68
  type_parameters(:T)
62
69
  .params(
63
- uri: String,
70
+ uri: URI::Generic,
64
71
  request_name: String,
65
72
  block: T.proc.params(document: Document).returns(T.type_parameter(:T)),
66
73
  ).returns(T.type_parameter(:T))
@@ -6,7 +6,7 @@ module RubyLsp
6
6
  VOID = T.let(Object.new.freeze, Object)
7
7
 
8
8
  # This freeze is not redundant since the interpolated string is mutable
9
- WORKSPACE_URI = T.let(URI("file://#{Dir.pwd}".freeze), URI::Generic)
9
+ WORKSPACE_URI = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
10
10
 
11
11
  # A notification to be sent to the client
12
12
  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.7.6
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-02 00:00:00.000000000 Z
11
+ date: 2023-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -58,6 +58,20 @@ dependencies:
58
58
  - - "<"
59
59
  - !ruby/object:Gem::Version
60
60
  version: '7'
61
+ - !ruby/object:Gem::Dependency
62
+ name: yarp
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.6.0
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.6.0
61
75
  description: An opinionated language server for Ruby
62
76
  email:
63
77
  - ruby@shopify.com
@@ -72,8 +86,18 @@ files:
72
86
  - VERSION
73
87
  - exe/ruby-lsp
74
88
  - exe/ruby-lsp-check
89
+ - lib/core_ext/uri.rb
75
90
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
76
91
  - lib/ruby-lsp.rb
92
+ - lib/ruby_indexer/lib/ruby_indexer/configuration.rb
93
+ - lib/ruby_indexer/lib/ruby_indexer/index.rb
94
+ - lib/ruby_indexer/lib/ruby_indexer/visitor.rb
95
+ - lib/ruby_indexer/ruby_indexer.rb
96
+ - lib/ruby_indexer/test/classes_and_modules_test.rb
97
+ - lib/ruby_indexer/test/configuration_test.rb
98
+ - lib/ruby_indexer/test/constant_test.rb
99
+ - lib/ruby_indexer/test/index_test.rb
100
+ - lib/ruby_indexer/test/test_case.rb
77
101
  - lib/ruby_lsp/check_docs.rb
78
102
  - lib/ruby_lsp/document.rb
79
103
  - lib/ruby_lsp/event_emitter.rb
@@ -140,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
164
  - !ruby/object:Gem::Version
141
165
  version: '0'
142
166
  requirements: []
143
- rubygems_version: 3.4.17
167
+ rubygems_version: 3.4.18
144
168
  signing_key:
145
169
  specification_version: 4
146
170
  summary: An opinionated language server for Ruby