ruby-lsp 0.17.1 → 0.17.2

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: 61194ff211ddd14d6598752e0a60409c16c2c5460c4d007ac1e1bc7e56b50116
4
- data.tar.gz: 29ed28f4392929f09a4ba4ea6a19612f9dfc3e2c0b2641c551627ac69a32b3c0
3
+ metadata.gz: 595a062d747f467d33b204523cfc6173b450f1b570616888b292109d0390a980
4
+ data.tar.gz: 97d1ed5a7a69521c427207a971684054eb7d35ddb6cd9e315e5fdb470c01d4d8
5
5
  SHA512:
6
- metadata.gz: 56420391d81fe9e9c06a7fda2a04b1f99ac6fbb10211f064bfd65f36dd9148658d47743be437eb40240d5522786ec0566d0cea763b964630fb5ea67fe552687b
7
- data.tar.gz: f760fad5d7abf0d0071badf6dd04f82e2266a64b0122d3a08cd5c3c799be15e377c94da93a4e8164e1f6ae04350e5ca05b16afbb7a8e341a8e80590053728ed7
6
+ metadata.gz: 445852ced0242742544611c8ee39574eb09ca33f1a2b9b27ce53c8c33514256716313db4723ce7ce0a8e2ad6bd29ee47c75f68afc65150c243cbf1a0254dad2e
7
+ data.tar.gz: cb462f49c28c52ff92fa392790e030f3bdac3efeacfab865bf5892cb0b62494f7ee7c46c61a8e667867ed06f9abcd037786054e49567f982c974cd777da82551
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.1
1
+ 0.17.2
@@ -128,6 +128,7 @@ module RubyLsp
128
128
  closest = node
129
129
  parent = T.let(nil, T.nilable(Prism::Node))
130
130
  nesting = T.let([], T::Array[T.any(Prism::ClassNode, Prism::ModuleNode)])
131
+ call_node = T.let(nil, T.nilable(Prism::CallNode))
131
132
 
132
133
  until queue.empty?
133
134
  candidate = queue.shift
@@ -159,6 +160,15 @@ module RubyLsp
159
160
  nesting << candidate
160
161
  end
161
162
 
163
+ if candidate.is_a?(Prism::CallNode)
164
+ arg_loc = candidate.arguments&.location
165
+ blk_loc = candidate.block&.location
166
+ if (arg_loc && (arg_loc.start_offset...arg_loc.end_offset).cover?(char_position)) ||
167
+ (blk_loc && (blk_loc.start_offset...blk_loc.end_offset).cover?(char_position))
168
+ call_node = candidate
169
+ end
170
+ end
171
+
162
172
  # If there are node types to filter by, and the current node is not one of those types, then skip it
163
173
  next if node_types.any? && node_types.none? { |type| candidate.class == type }
164
174
 
@@ -170,7 +180,20 @@ module RubyLsp
170
180
  end
171
181
  end
172
182
 
173
- NodeContext.new(closest, parent, nesting.map { |n| n.constant_path.location.slice })
183
+ # When targeting the constant part of a class/module definition, we do not want the nesting to be duplicated. That
184
+ # is, when targeting Bar in the following example:
185
+ #
186
+ # ```ruby
187
+ # class Foo::Bar; end
188
+ # ```
189
+ # The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack, even
190
+ # though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
191
+ if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
192
+ last_level = nesting.last
193
+ nesting.pop if last_level && last_level.constant_path == closest
194
+ end
195
+
196
+ NodeContext.new(closest, parent, nesting.map { |n| n.constant_path.location.slice }, call_node)
174
197
  end
175
198
 
176
199
  sig { returns(T::Boolean) }
@@ -39,18 +39,27 @@ module RubyLsp
39
39
  :on_instance_variable_operator_write_node_enter,
40
40
  :on_instance_variable_or_write_node_enter,
41
41
  :on_instance_variable_target_node_enter,
42
+ :on_string_node_enter,
42
43
  )
43
44
  end
44
45
 
45
46
  sig { params(node: Prism::CallNode).void }
46
47
  def on_call_node_enter(node)
47
- message = node.name
48
+ message = node.message
49
+ return unless message
48
50
 
49
- if message == :require || message == :require_relative
50
- handle_require_definition(node)
51
- else
52
- handle_method_definition(message.to_s, self_receiver?(node))
53
- end
51
+ handle_method_definition(message, self_receiver?(node))
52
+ end
53
+
54
+ sig { params(node: Prism::StringNode).void }
55
+ def on_string_node_enter(node)
56
+ enclosing_call = @node_context.call_node
57
+ return unless enclosing_call
58
+
59
+ name = enclosing_call.name
60
+ return unless name == :require || name == :require_relative
61
+
62
+ handle_require_definition(node, name)
54
63
  end
55
64
 
56
65
  sig { params(node: Prism::BlockArgumentNode).void }
@@ -159,19 +168,12 @@ module RubyLsp
159
168
  end
160
169
  end
161
170
 
162
- sig { params(node: Prism::CallNode).void }
163
- def handle_require_definition(node)
164
- message = node.name
165
- arguments = node.arguments
166
- return unless arguments
167
-
168
- argument = arguments.arguments.first
169
- return unless argument.is_a?(Prism::StringNode)
170
-
171
+ sig { params(node: Prism::StringNode, message: Symbol).void }
172
+ def handle_require_definition(node, message)
171
173
  case message
172
174
  when :require
173
- entry = @index.search_require_paths(argument.content).find do |indexable_path|
174
- indexable_path.require_path == argument.content
175
+ entry = @index.search_require_paths(node.content).find do |indexable_path|
176
+ indexable_path.require_path == node.content
175
177
  end
176
178
 
177
179
  if entry
@@ -186,7 +188,7 @@ module RubyLsp
186
188
  )
187
189
  end
188
190
  when :require_relative
189
- required_file = "#{argument.content}.rb"
191
+ required_file = "#{node.content}.rb"
190
192
  path = @uri.to_standardized_path
191
193
  current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
192
194
  candidate = File.expand_path(File.join(current_folder, required_file))
@@ -19,6 +19,8 @@ module RubyLsp
19
19
  Prism::InstanceVariableOrWriteNode,
20
20
  Prism::InstanceVariableTargetNode,
21
21
  Prism::InstanceVariableWriteNode,
22
+ Prism::SymbolNode,
23
+ Prism::StringNode,
22
24
  ],
23
25
  T::Array[T.class_of(Prism::Node)],
24
26
  )
@@ -2,8 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
- # This class allows listeners to access contextual information about a node in the AST, such as its parent
6
- # and its namespace nesting.
5
+ # This class allows listeners to access contextual information about a node in the AST, such as its parent,
6
+ # its namespace nesting, and the surrounding CallNode (e.g. a method call).
7
7
  class NodeContext
8
8
  extend T::Sig
9
9
 
@@ -13,11 +13,22 @@ module RubyLsp
13
13
  sig { returns(T::Array[String]) }
14
14
  attr_reader :nesting
15
15
 
16
- sig { params(node: T.nilable(Prism::Node), parent: T.nilable(Prism::Node), nesting: T::Array[String]).void }
17
- def initialize(node, parent, nesting)
16
+ sig { returns(T.nilable(Prism::CallNode)) }
17
+ attr_reader :call_node
18
+
19
+ sig do
20
+ params(
21
+ node: T.nilable(Prism::Node),
22
+ parent: T.nilable(Prism::Node),
23
+ nesting: T::Array[String],
24
+ call_node: T.nilable(Prism::CallNode),
25
+ ).void
26
+ end
27
+ def initialize(node, parent, nesting, call_node)
18
28
  @node = node
19
29
  @parent = parent
20
30
  @nesting = nesting
31
+ @call_node = call_node
21
32
  end
22
33
 
23
34
  sig { returns(String) }
@@ -60,6 +60,8 @@ module RubyLsp
60
60
  Prism::InstanceVariableOrWriteNode,
61
61
  Prism::InstanceVariableTargetNode,
62
62
  Prism::InstanceVariableWriteNode,
63
+ Prism::SymbolNode,
64
+ Prism::StringNode,
63
65
  ],
64
66
  )
65
67
 
@@ -79,6 +81,9 @@ module RubyLsp
79
81
  # If the target is a method call, we need to ensure that the requested position is exactly on top of the
80
82
  # method identifier. Otherwise, we risk showing definitions for unrelated things
81
83
  target = nil
84
+ # For methods with block arguments using symbol-to-proc
85
+ elsif target.is_a?(Prism::SymbolNode) && parent.is_a?(Prism::BlockArgumentNode)
86
+ target = parent
82
87
  end
83
88
 
84
89
  if target
@@ -19,6 +19,16 @@ module RubyLsp
19
19
  T::Hash[Symbol, Integer],
20
20
  )
21
21
 
22
+ ENHANCED_DOC_URL = T.let(
23
+ begin
24
+ gem("rubocop", ">= 1.64.0")
25
+ true
26
+ rescue LoadError
27
+ false
28
+ end,
29
+ T::Boolean,
30
+ )
31
+
22
32
  # TODO: avoid passing document once we have alternative ways to get at
23
33
  # encoding and file source
24
34
  sig { params(document: Document, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
@@ -38,8 +48,8 @@ module RubyLsp
38
48
  code_actions
39
49
  end
40
50
 
41
- sig { returns(Interface::Diagnostic) }
42
- def to_lsp_diagnostic
51
+ sig { params(config: RuboCop::Config).returns(Interface::Diagnostic) }
52
+ def to_lsp_diagnostic(config)
43
53
  # highlighted_area contains the begin and end position of the first line
44
54
  # This ensures that multiline offenses don't clutter the editor
45
55
  highlighted = @offense.highlighted_area
@@ -47,7 +57,7 @@ module RubyLsp
47
57
  message: message,
48
58
  source: "RuboCop",
49
59
  code: @offense.cop_name,
50
- code_description: code_description,
60
+ code_description: code_description(config),
51
61
  severity: severity,
52
62
  range: Interface::Range.new(
53
63
  start: Interface::Position.new(
@@ -80,9 +90,16 @@ module RubyLsp
80
90
  RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name]
81
91
  end
82
92
 
83
- sig { returns(T.nilable(Interface::CodeDescription)) }
84
- def code_description
85
- doc_url = RuboCopRunner.find_cop_by_name(@offense.cop_name)&.documentation_url
93
+ sig { params(config: RuboCop::Config).returns(T.nilable(Interface::CodeDescription)) }
94
+ def code_description(config)
95
+ cop = RuboCopRunner.find_cop_by_name(@offense.cop_name)
96
+ return unless cop
97
+
98
+ doc_url = if ENHANCED_DOC_URL
99
+ cop.documentation_url(config)
100
+ else
101
+ cop.documentation_url
102
+ end
86
103
  Interface::CodeDescription.new(href: doc_url) if doc_url
87
104
  end
88
105
 
@@ -38,7 +38,11 @@ module RubyLsp
38
38
  @diagnostic_runner.run(filename, document.source)
39
39
 
40
40
  @diagnostic_runner.offenses.map do |offense|
41
- Support::RuboCopDiagnostic.new(document, offense, uri).to_lsp_diagnostic
41
+ Support::RuboCopDiagnostic.new(
42
+ document,
43
+ offense,
44
+ uri,
45
+ ).to_lsp_diagnostic(@diagnostic_runner.config_for_working_directory)
42
46
  end
43
47
  end
44
48
  end
@@ -50,6 +50,9 @@ module RubyLsp
50
50
  sig { returns(T::Array[RuboCop::Cop::Offense]) }
51
51
  attr_reader :offenses
52
52
 
53
+ sig { returns(::RuboCop::Config) }
54
+ attr_reader :config_for_working_directory
55
+
53
56
  DEFAULT_ARGS = T.let(
54
57
  [
55
58
  "--stderr", # Print any output to stderr so that our stdout does not get polluted
@@ -78,6 +81,7 @@ module RubyLsp
78
81
  args += DEFAULT_ARGS
79
82
  rubocop_options = ::RuboCop::Options.new.parse(args).first
80
83
  config_store = ::RuboCop::ConfigStore.new
84
+ @config_for_working_directory = T.let(config_store.for_pwd, ::RuboCop::Config)
81
85
 
82
86
  super(rubocop_options, config_store)
83
87
  end
@@ -33,10 +33,11 @@ module RubyLsp
33
33
  sig { override.returns(T::Array[Interface::WorkspaceSymbol]) }
34
34
  def perform
35
35
  @index.fuzzy_search(@query).filter_map do |entry|
36
- # If the project is using Sorbet, we let Sorbet handle symbols defined inside the project itself and RBIs, but
37
- # we still return entries defined in gems to allow developers to jump directly to the source
38
36
  file_path = entry.file_path
39
- next if @global_state.typechecker && not_in_dependencies?(file_path)
37
+
38
+ # We only show symbols declared in the workspace
39
+ in_dependencies = !not_in_dependencies?(file_path)
40
+ next if in_dependencies
40
41
 
41
42
  # We should never show private symbols when searching the entire workspace
42
43
  next if entry.private?
@@ -175,7 +175,7 @@ module RubyLsp
175
175
  completion_provider: completion_provider,
176
176
  code_lens_provider: code_lens_provider,
177
177
  definition_provider: enabled_features["definition"],
178
- workspace_symbol_provider: enabled_features["workspaceSymbol"],
178
+ workspace_symbol_provider: enabled_features["workspaceSymbol"] && !@global_state.typechecker,
179
179
  signature_help_provider: signature_help_provider,
180
180
  ),
181
181
  serverInfo: {
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.1
4
+ version: 0.17.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-31 00:00:00.000000000 Z
11
+ date: 2024-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -180,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
180
  - !ruby/object:Gem::Version
181
181
  version: '0'
182
182
  requirements: []
183
- rubygems_version: 3.5.10
183
+ rubygems_version: 3.5.11
184
184
  signing_key:
185
185
  specification_version: 4
186
186
  summary: An opinionated language server for Ruby