ruby-lsp 0.12.0 → 0.12.2

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: dd4c9d22a582587f5b28e38863009ac0375ebb66a743da42e823ba3a0ff28987
4
- data.tar.gz: 90ff34eac484bc0af67529ce798d9b6a94def13a3989f1c94bb6bae67c190023
3
+ metadata.gz: 3f60569fbb67f1c78fcffed58dbf540cb4e68fe10200841e54cd8a68859cf0de
4
+ data.tar.gz: 07c4419c763e590e1fbed7fe14a2660c1bf6ddbd92ca791948a8c2235208f6e9
5
5
  SHA512:
6
- metadata.gz: a67a91649a58fef931970abd81a1def32f2a8c58b1cbce454aa22f44d7ca944e2b787ec55025e8051922fd6a9a94edcc38ed332f9c1d0086ad840fc99003536b
7
- data.tar.gz: 93b3584713f16f4655c2a075c008e66f263e0de449bebb6e1c440f4de8b3474366f69a69f511934883e38d5437c3b0c4d9817cc164bb943aabfced4134861aba
6
+ metadata.gz: 5284be197a27351a845cbcfc1735a0e9546472d60f9600ffd4ee55ba3ab0998438110c1ec59f684e0cf2fbaa7ff99f89878919ae3954022372e50116182c59dd
7
+ data.tar.gz: f706c17b0cd47c1001bffb42f6c9a1c43efbd390e548ffe7b51fb3c58bdb433c868a7f907a3aa32175d7742ccbb3c1c29606a0c7c979246bfe887500e6531676
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.12.0
1
+ 0.12.2
data/exe/ruby-lsp CHANGED
@@ -25,6 +25,13 @@ parser = OptionParser.new do |opts|
25
25
  options[:branch] = branch
26
26
  end
27
27
 
28
+ opts.on(
29
+ "--experimental",
30
+ "Run pre-release versions of the Ruby LSP",
31
+ ) do
32
+ options[:experimental] = true
33
+ end
34
+
28
35
  opts.on("-h", "--help", "Print this help") do
29
36
  puts opts.help
30
37
  puts
@@ -49,7 +56,7 @@ if ENV["BUNDLE_GEMFILE"].nil?
49
56
  require_relative "../lib/ruby_lsp/setup_bundler"
50
57
 
51
58
  begin
52
- bundle_gemfile, bundle_path, bundle_app_config = RubyLsp::SetupBundler.new(Dir.pwd, branch: options[:branch]).setup!
59
+ bundle_gemfile, bundle_path, bundle_app_config = RubyLsp::SetupBundler.new(Dir.pwd, **options).setup!
53
60
  rescue RubyLsp::SetupBundler::BundleNotLocked
54
61
  warn("Project contains a Gemfile, but no Gemfile.lock. Run `bundle install` to lock gems and restart the server")
55
62
  exit(78)
@@ -78,7 +85,8 @@ rescue
78
85
  nil
79
86
  end
80
87
 
81
- require_relative "../lib/ruby_lsp/internal"
88
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
89
+ require "ruby_lsp/internal"
82
90
 
83
91
  if options[:debug]
84
92
  if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
data/exe/ruby-lsp-check CHANGED
@@ -14,7 +14,8 @@ rescue
14
14
  nil
15
15
  end
16
16
 
17
- require_relative "../lib/ruby_lsp/internal"
17
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
18
+ require "ruby_lsp/internal"
18
19
 
19
20
  RubyLsp::Addon.load_addons
20
21
 
data/exe/ruby-lsp-doctor CHANGED
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "bundler/setup"
5
-
4
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
6
5
  require "ruby_lsp/internal"
7
6
 
8
7
  index = RubyIndexer::Index.new
@@ -0,0 +1,125 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "rubocop"
5
+ require "sorbet-runtime"
6
+
7
+ module RuboCop
8
+ module Cop
9
+ module RubyLsp
10
+ # Avoid using register without handler method, or handler without register.
11
+ #
12
+ # @example
13
+ # # Register without handler method.
14
+ #
15
+ # # bad
16
+ # class MyListener < Listener
17
+ # def initialize(dispatcher)
18
+ # super()
19
+ # dispatcher.register(
20
+ # self,
21
+ # :on_string_node_enter,
22
+ # )
23
+ # end
24
+ # end
25
+ #
26
+ # # good
27
+ # class MyListener < Listener
28
+ # def initialize(dispatcher)
29
+ # super()
30
+ # dispatcher.register(
31
+ # self,
32
+ # :on_string_node_enter,
33
+ # )
34
+ # end
35
+ #
36
+ # def on_string_node_enter(node)
37
+ # end
38
+ # end
39
+ #
40
+ # @example
41
+ # # Handler method without register.
42
+ #
43
+ # # bad
44
+ # class MyListener < Listener
45
+ # def initialize(dispatcher)
46
+ # super()
47
+ # dispatcher.register(
48
+ # self,
49
+ # )
50
+ # end
51
+ #
52
+ # def on_string_node_enter(node)
53
+ # end
54
+ # end
55
+ #
56
+ # # good
57
+ # class MyListener < Listener
58
+ # def initialize(dispatcher)
59
+ # super()
60
+ # dispatcher.register(
61
+ # self,
62
+ # :on_string_node_enter,
63
+ # )
64
+ # end
65
+ #
66
+ # def on_string_node_enter(node)
67
+ # end
68
+ # end
69
+ class UseRegisterWithHandlerMethod < RuboCop::Cop::Base
70
+ extend T::Sig
71
+
72
+ MSG_MISSING_HANDLER = "Registered to `%{listener}` without a handler defined."
73
+ MSG_MISSING_LISTENER = "Created a handler without registering the associated `%{listener}` event."
74
+
75
+ def_node_search(
76
+ :find_all_listeners,
77
+ "(send
78
+ (_ :dispatcher) :register
79
+ (self)
80
+ $(sym _)+)",
81
+ )
82
+
83
+ def_node_search(
84
+ :find_all_handlers,
85
+ "$(def [_ #valid_event_name?] (args (arg _)) ...)",
86
+ )
87
+
88
+ def on_new_investigation
89
+ return if processed_source.blank?
90
+
91
+ listeners = find_all_listeners(processed_source.ast).flat_map { |listener| listener }
92
+ handlers = find_all_handlers(processed_source.ast).flat_map { |handler| handler }
93
+
94
+ add_offense_to_listeners_without_handler(listeners, handlers)
95
+ add_offense_handlers_without_listener(listeners, handlers)
96
+ end
97
+
98
+ private
99
+
100
+ sig { params(event_name: Symbol).returns(T::Boolean) }
101
+ def valid_event_name?(event_name)
102
+ /^on_.*(node_enter|node_leave)$/.match?(event_name)
103
+ end
104
+
105
+ sig { params(listeners: T::Array[RuboCop::AST::SymbolNode], handlers: T::Array[RuboCop::AST::DefNode]).void }
106
+ def add_offense_to_listeners_without_handler(listeners, handlers)
107
+ return if listeners.none?
108
+
109
+ listeners
110
+ .filter { |node| handlers.map(&:method_name).none?(node.value) }
111
+ .each { |node| add_offense(node, message: format(MSG_MISSING_HANDLER, listener: node.value)) }
112
+ end
113
+
114
+ sig { params(listeners: T::Array[RuboCop::AST::SymbolNode], handlers: T::Array[RuboCop::AST::DefNode]).void }
115
+ def add_offense_handlers_without_listener(listeners, handlers)
116
+ return if handlers.none?
117
+
118
+ handlers
119
+ .filter { |node| listeners.map(&:value).none?(node.method_name) }
120
+ .each { |node| add_offense(node, message: format(MSG_MISSING_LISTENER, listener: node.method_name)) }
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -67,8 +67,12 @@ module RubyIndexer
67
67
 
68
68
  # Add user specified patterns
69
69
  indexables = @included_patterns.flat_map do |pattern|
70
+ load_path_entry = T.let(nil, T.nilable(String))
71
+
70
72
  Dir.glob(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
71
- load_path_entry = $LOAD_PATH.find { |load_path| path.start_with?(load_path) }
73
+ # All entries for the same pattern match the same $LOAD_PATH entry. Since searching the $LOAD_PATH for every
74
+ # entry is expensive, we memoize it for the entire pattern
75
+ load_path_entry ||= $LOAD_PATH.find { |load_path| path.start_with?(load_path) }
72
76
  IndexablePath.new(load_path_entry, path)
73
77
  end
74
78
  end
@@ -194,7 +198,11 @@ module RubyIndexer
194
198
  excluded.each do |dependency|
195
199
  next unless dependency.runtime?
196
200
 
197
- dependency.to_spec.dependencies.each do |transitive_dependency|
201
+ # If the dependency is prerelease, to_spec may return `nil`
202
+ spec = dependency.to_spec
203
+ next unless spec
204
+
205
+ spec.dependencies.each do |transitive_dependency|
198
206
  # If the transitive dependency is included in other groups, skip it
199
207
  next if others.any? { |d| d.name == transitive_dependency.name }
200
208
 
@@ -21,7 +21,7 @@ module RubyIndexer
21
21
  def initialize(load_path_entry, full_path)
22
22
  @full_path = full_path
23
23
  @require_path = T.let(
24
- load_path_entry ? Pathname.new(full_path).relative_path_from(load_path_entry).to_s.delete_suffix(".rb") : nil,
24
+ load_path_entry ? full_path.delete_prefix("#{load_path_entry}/").delete_suffix(".rb") : nil,
25
25
  T.nilable(String),
26
26
  )
27
27
  end
@@ -12,12 +12,12 @@ require "ruby_indexer/lib/ruby_indexer/configuration"
12
12
  require "ruby_indexer/lib/ruby_indexer/prefix_tree"
13
13
 
14
14
  module RubyIndexer
15
+ @configuration = T.let(Configuration.new, Configuration)
16
+
15
17
  class << self
16
18
  extend T::Sig
17
19
 
18
20
  sig { returns(Configuration) }
19
- def configuration
20
- @configuration ||= T.let(Configuration.new, T.nilable(Configuration))
21
- end
21
+ attr_reader :configuration
22
22
  end
23
23
  end
@@ -24,7 +24,23 @@ module RubyLsp
24
24
 
25
25
  ResponseType = type_member { { fixed: T::Array[Interface::CodeLens] } }
26
26
 
27
- BASE_COMMAND = T.let((File.exist?("Gemfile.lock") ? "bundle exec ruby" : "ruby") + " -Itest ", String)
27
+ BASE_COMMAND = T.let(
28
+ begin
29
+ Bundler.with_original_env { Bundler.default_lockfile }
30
+ "bundle exec ruby"
31
+ rescue Bundler::GemfileNotFound
32
+ "ruby"
33
+ end + " -Itest ",
34
+ String,
35
+ )
36
+ GEMFILE_NAME = T.let(
37
+ begin
38
+ Bundler.with_original_env { Bundler.default_gemfile.basename.to_s }
39
+ rescue Bundler::GemfileNotFound
40
+ "Gemfile"
41
+ end,
42
+ String,
43
+ )
28
44
  ACCESS_MODIFIERS = T.let([:public, :private, :protected], T::Array[Symbol])
29
45
  SUPPORTED_TEST_LIBRARIES = T.let(["minitest", "test-unit"], T::Array[String])
30
46
 
@@ -111,7 +127,7 @@ module RubyLsp
111
127
  return
112
128
  end
113
129
 
114
- if @path&.include?("Gemfile") && name == :gem && arguments
130
+ if @path&.include?(GEMFILE_NAME) && name == :gem && arguments
115
131
  first_argument = arguments.arguments.first
116
132
  return unless first_argument.is_a?(Prism::StringNode)
117
133
 
@@ -73,6 +73,8 @@ module RubyLsp
73
73
  :on_module_node_leave,
74
74
  :on_instance_variable_write_node_enter,
75
75
  :on_class_variable_write_node_enter,
76
+ :on_singleton_class_node_enter,
77
+ :on_singleton_class_node_leave,
76
78
  )
77
79
  end
78
80
 
@@ -103,6 +105,23 @@ module RubyLsp
103
105
  @stack.pop
104
106
  end
105
107
 
108
+ sig { params(node: Prism::SingletonClassNode).void }
109
+ def on_singleton_class_node_enter(node)
110
+ expression = node.expression
111
+
112
+ @stack << create_document_symbol(
113
+ name: "<< #{expression.slice}",
114
+ kind: Constant::SymbolKind::NAMESPACE,
115
+ range_location: node.location,
116
+ selection_range_location: expression.location,
117
+ )
118
+ end
119
+
120
+ sig { params(node: Prism::SingletonClassNode).void }
121
+ def on_singleton_class_node_leave(node)
122
+ @stack.pop
123
+ end
124
+
106
125
  sig { params(node: Prism::CallNode).void }
107
126
  def on_call_node_enter(node)
108
127
  return unless ATTR_ACCESSORS.include?(node.name) && node.receiver.nil?
@@ -163,10 +182,14 @@ module RubyLsp
163
182
  sig { params(node: Prism::DefNode).void }
164
183
  def on_def_node_enter(node)
165
184
  receiver = node.receiver
185
+ previous_symbol = @stack.last
166
186
 
167
187
  if receiver.is_a?(Prism::SelfNode)
168
188
  name = "self.#{node.name}"
169
- kind = Constant::SymbolKind::METHOD
189
+ kind = Constant::SymbolKind::FUNCTION
190
+ elsif previous_symbol.is_a?(Interface::DocumentSymbol) && previous_symbol.name.start_with?("<<")
191
+ name = node.name.to_s
192
+ kind = Constant::SymbolKind::FUNCTION
170
193
  else
171
194
  name = node.name.to_s
172
195
  kind = name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
@@ -9,9 +9,9 @@ module RubyLsp
9
9
 
10
10
  RUBOCOP_TO_LSP_SEVERITY = T.let(
11
11
  {
12
- convention: Constant::DiagnosticSeverity::INFORMATION,
13
- info: Constant::DiagnosticSeverity::INFORMATION,
12
+ info: Constant::DiagnosticSeverity::HINT,
14
13
  refactor: Constant::DiagnosticSeverity::INFORMATION,
14
+ convention: Constant::DiagnosticSeverity::INFORMATION,
15
15
  warning: Constant::DiagnosticSeverity::WARNING,
16
16
  error: Constant::DiagnosticSeverity::ERROR,
17
17
  fatal: Constant::DiagnosticSeverity::ERROR,
@@ -19,12 +19,6 @@ module RubyLsp
19
19
  T::Hash[Symbol, Integer],
20
20
  )
21
21
 
22
- # Cache cops to attach URLs to diagnostics. Only built-in cops for now.
23
- COP_TO_DOC_URL = T.let(
24
- RuboCop::Cop::Registry.global.to_h,
25
- T::Hash[String, [T.class_of(RuboCop::Cop::Base)]],
26
- )
27
-
28
22
  sig { params(offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
29
23
  def initialize(offense, uri)
30
24
  @offense = offense
@@ -53,16 +47,6 @@ module RubyLsp
53
47
 
54
48
  sig { returns(Interface::Diagnostic) }
55
49
  def to_lsp_diagnostic
56
- severity = RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name]
57
- message = @offense.message
58
-
59
- message += "\n\nThis offense is not auto-correctable.\n" unless @offense.correctable?
60
-
61
- cop = COP_TO_DOC_URL[@offense.cop_name]&.first
62
- if cop&.documentation_url
63
- code_description = { href: cop.documentation_url }
64
- end
65
-
66
50
  Interface::Diagnostic.new(
67
51
  message: message,
68
52
  source: "RuboCop",
@@ -88,6 +72,24 @@ module RubyLsp
88
72
 
89
73
  private
90
74
 
75
+ sig { returns(String) }
76
+ def message
77
+ message = @offense.message
78
+ message += "\n\nThis offense is not auto-correctable.\n" unless @offense.correctable?
79
+ message
80
+ end
81
+
82
+ sig { returns(T.nilable(Integer)) }
83
+ def severity
84
+ RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name]
85
+ end
86
+
87
+ sig { returns(T.nilable(Interface::CodeDescription)) }
88
+ def code_description
89
+ doc_url = RuboCopRunner.find_cop_by_name(@offense.cop_name)&.documentation_url
90
+ Interface::CodeDescription.new(href: doc_url) if doc_url
91
+ end
92
+
91
93
  sig { returns(T::Array[Interface::TextEdit]) }
92
94
  def offense_replacements
93
95
  @offense.corrector.as_replacements.map do |range, replacement|
@@ -101,6 +101,25 @@ module RubyLsp
101
101
  @options[:stdin]
102
102
  end
103
103
 
104
+ class << self
105
+ extend T::Sig
106
+
107
+ sig { params(cop_name: String).returns(T.nilable(T.class_of(RuboCop::Cop::Base))) }
108
+ def find_cop_by_name(cop_name)
109
+ cop_registry[cop_name]&.first
110
+ end
111
+
112
+ private
113
+
114
+ sig { returns(T::Hash[String, [T.class_of(RuboCop::Cop::Base)]]) }
115
+ def cop_registry
116
+ @cop_registry ||= T.let(
117
+ RuboCop::Cop::Registry.global.to_h,
118
+ T.nilable(T::Hash[String, [T.class_of(RuboCop::Cop::Base)]]),
119
+ )
120
+ end
121
+ end
122
+
104
123
  private
105
124
 
106
125
  sig { params(_file: String, offenses: T::Array[RuboCop::Cop::Offense]).void }
@@ -58,7 +58,7 @@ module RubyLsp
58
58
 
59
59
  sig { void }
60
60
  def start
61
- warn("Starting Ruby LSP...")
61
+ warn("Starting Ruby LSP v#{VERSION}...")
62
62
 
63
63
  # Requests that have to be executed sequentially or in the main process are implemented here. All other requests
64
64
  # fall under the else branch which just pushes requests to the queue
@@ -20,17 +20,11 @@ module RubyLsp
20
20
 
21
21
  FOUR_HOURS = T.let(4 * 60 * 60, Integer)
22
22
 
23
- sig { params(project_path: String, branch: T.nilable(String)).void }
24
- def initialize(project_path, branch: nil)
23
+ sig { params(project_path: String, options: T.untyped).void }
24
+ def initialize(project_path, **options)
25
25
  @project_path = project_path
26
- @branch = branch
27
-
28
- # Custom bundle paths
29
- @custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(Dir.pwd), Pathname)
30
- @custom_gemfile = T.let(@custom_dir + "Gemfile", Pathname)
31
- @custom_lockfile = T.let(@custom_dir + "Gemfile.lock", Pathname)
32
- @lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
33
- @last_updated_path = T.let(@custom_dir + "last_updated", Pathname)
26
+ @branch = T.let(options[:branch], T.nilable(String))
27
+ @experimental = T.let(options[:experimental], T.nilable(T::Boolean))
34
28
 
35
29
  # Regular bundle paths
36
30
  @gemfile = T.let(
@@ -43,6 +37,15 @@ module RubyLsp
43
37
  )
44
38
  @lockfile = T.let(@gemfile ? Bundler.default_lockfile : nil, T.nilable(Pathname))
45
39
 
40
+ @gemfile_name = T.let(@gemfile&.basename&.to_s || "Gemfile", String)
41
+
42
+ # Custom bundle paths
43
+ @custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(Dir.pwd), Pathname)
44
+ @custom_gemfile = T.let(@custom_dir + @gemfile_name, Pathname)
45
+ @custom_lockfile = T.let(@custom_dir + (@lockfile&.basename || "Gemfile.lock"), Pathname)
46
+ @lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
47
+ @last_updated_path = T.let(@custom_dir + "last_updated", Pathname)
48
+
46
49
  @dependencies = T.let(load_dependencies, T::Hash[String, T.untyped])
47
50
  end
48
51
 
@@ -118,7 +121,7 @@ module RubyLsp
118
121
  # If there's a top level Gemfile, we want to evaluate from the custom bundle. We get the source from the top level
119
122
  # Gemfile, so if there isn't one we need to add a default source
120
123
  if @gemfile&.exist?
121
- parts << "eval_gemfile(File.expand_path(\"../Gemfile\", __dir__))"
124
+ parts << "eval_gemfile(File.expand_path(\"../#{@gemfile_name}\", __dir__))"
122
125
  else
123
126
  parts.unshift('source "https://rubygems.org"')
124
127
  end
@@ -188,7 +191,9 @@ module RubyLsp
188
191
  command.prepend("(")
189
192
  command << " && bundle update "
190
193
  command << "ruby-lsp " unless @dependencies["ruby-lsp"]
191
- command << "debug" unless @dependencies["debug"]
194
+ command << "debug " unless @dependencies["debug"]
195
+ command << "--pre" if @experimental
196
+ command.delete_suffix!(" ")
192
197
  command << ")"
193
198
 
194
199
  @last_updated_path.write(Time.now.iso8601)
@@ -202,6 +207,7 @@ module RubyLsp
202
207
 
203
208
  # Add bundle update
204
209
  warn("Ruby LSP> Running bundle install for the custom bundle. This may take a while...")
210
+ warn("Ruby LSP> Command: #{command}")
205
211
  system(env, command)
206
212
  [bundle_gemfile.to_s, expanded_path, env["BUNDLE_APP_CONFIG"]]
207
213
  end
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.12.0
4
+ version: 0.12.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-18 00:00:00.000000000 Z
11
+ date: 2023-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -76,6 +76,7 @@ files:
76
76
  - exe/ruby-lsp-doctor
77
77
  - lib/core_ext/uri.rb
78
78
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
79
+ - lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
79
80
  - lib/ruby-lsp.rb
80
81
  - lib/ruby_indexer/lib/ruby_indexer/configuration.rb
81
82
  - lib/ruby_indexer/lib/ruby_indexer/entry.rb