ruby-lsp 0.12.0 → 0.12.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd4c9d22a582587f5b28e38863009ac0375ebb66a743da42e823ba3a0ff28987
4
- data.tar.gz: 90ff34eac484bc0af67529ce798d9b6a94def13a3989f1c94bb6bae67c190023
3
+ metadata.gz: 2774623b2161a416bfb07769e8eb5ccf3d40177f98edf9e4d4ed4c7e3f7fd47b
4
+ data.tar.gz: 23626aa84fdaf85571c7d039d670f9131409ccb7a8cd2c61d4889ffd08ecb8de
5
5
  SHA512:
6
- metadata.gz: a67a91649a58fef931970abd81a1def32f2a8c58b1cbce454aa22f44d7ca944e2b787ec55025e8051922fd6a9a94edcc38ed332f9c1d0086ad840fc99003536b
7
- data.tar.gz: 93b3584713f16f4655c2a075c008e66f263e0de449bebb6e1c440f4de8b3474366f69a69f511934883e38d5437c3b0c4d9817cc164bb943aabfced4134861aba
6
+ metadata.gz: ffd099ba6d95ce10cd3fd2c3fb1e431d5481e0e8bad77c062fdd9b2f9fce534f9c58319672e508d17ca089e10ba9fdf398479dd9dfac3adbc347912815800cdd
7
+ data.tar.gz: fb0f4c3111718c5f792266459e2694787ea7939b63c819707bba764395dc6cc3bf1d925517a1fa412b362fbd0b3222e8460723df4f2c3a4272ac73dc62eb872a
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.12.0
1
+ 0.12.1
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
@@ -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
 
@@ -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 }
@@ -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.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-10-18 00:00:00.000000000 Z
11
+ date: 2023-10-26 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