ruby-lsp 0.16.7 → 0.17.2

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/README.md +1 -1
  3. data/VERSION +1 -1
  4. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +139 -18
  5. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +101 -12
  6. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +183 -10
  7. data/lib/ruby_indexer/test/classes_and_modules_test.rb +55 -9
  8. data/lib/ruby_indexer/test/configuration_test.rb +4 -5
  9. data/lib/ruby_indexer/test/constant_test.rb +8 -8
  10. data/lib/ruby_indexer/test/index_test.rb +528 -0
  11. data/lib/ruby_indexer/test/instance_variables_test.rb +131 -0
  12. data/lib/ruby_indexer/test/method_test.rb +37 -0
  13. data/lib/ruby_indexer/test/test_case.rb +3 -1
  14. data/lib/ruby_lsp/addon.rb +8 -8
  15. data/lib/ruby_lsp/document.rb +26 -3
  16. data/lib/ruby_lsp/internal.rb +1 -0
  17. data/lib/ruby_lsp/listeners/completion.rb +74 -17
  18. data/lib/ruby_lsp/listeners/definition.rb +82 -24
  19. data/lib/ruby_lsp/listeners/hover.rb +62 -6
  20. data/lib/ruby_lsp/listeners/signature_help.rb +4 -4
  21. data/lib/ruby_lsp/node_context.rb +39 -0
  22. data/lib/ruby_lsp/requests/code_action_resolve.rb +73 -2
  23. data/lib/ruby_lsp/requests/code_actions.rb +16 -15
  24. data/lib/ruby_lsp/requests/completion.rb +21 -13
  25. data/lib/ruby_lsp/requests/completion_resolve.rb +9 -0
  26. data/lib/ruby_lsp/requests/definition.rb +25 -5
  27. data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
  28. data/lib/ruby_lsp/requests/hover.rb +5 -6
  29. data/lib/ruby_lsp/requests/on_type_formatting.rb +8 -4
  30. data/lib/ruby_lsp/requests/signature_help.rb +3 -3
  31. data/lib/ruby_lsp/requests/support/common.rb +4 -3
  32. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +23 -6
  33. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +5 -1
  34. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
  35. data/lib/ruby_lsp/requests/workspace_symbol.rb +7 -4
  36. data/lib/ruby_lsp/server.rb +11 -5
  37. data/lib/ruby_lsp/test_helper.rb +1 -0
  38. metadata +5 -3
@@ -0,0 +1,39 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
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
+ class NodeContext
8
+ extend T::Sig
9
+
10
+ sig { returns(T.nilable(Prism::Node)) }
11
+ attr_reader :node, :parent
12
+
13
+ sig { returns(T::Array[String]) }
14
+ attr_reader :nesting
15
+
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)
28
+ @node = node
29
+ @parent = parent
30
+ @nesting = nesting
31
+ @call_node = call_node
32
+ end
33
+
34
+ sig { returns(String) }
35
+ def fully_qualified_name
36
+ @fully_qualified_name ||= T.let(@nesting.join("::"), T.nilable(String))
37
+ end
38
+ end
39
+ end
@@ -24,6 +24,7 @@ module RubyLsp
24
24
  class CodeActionResolve < Request
25
25
  extend T::Sig
26
26
  NEW_VARIABLE_NAME = "new_variable"
27
+ NEW_METHOD_NAME = "new_method"
27
28
 
28
29
  class CodeActionError < StandardError; end
29
30
 
@@ -31,6 +32,7 @@ module RubyLsp
31
32
  enums do
32
33
  EmptySelection = new
33
34
  InvalidTargetRange = new
35
+ UnknownCodeAction = new
34
36
  end
35
37
  end
36
38
 
@@ -43,6 +45,18 @@ module RubyLsp
43
45
 
44
46
  sig { override.returns(T.any(Interface::CodeAction, Error)) }
45
47
  def perform
48
+ case @code_action[:title]
49
+ when CodeActions::EXTRACT_TO_VARIABLE_TITLE
50
+ refactor_variable
51
+ when CodeActions::EXTRACT_TO_METHOD_TITLE
52
+ refactor_method
53
+ else
54
+ Error::UnknownCodeAction
55
+ end
56
+ end
57
+
58
+ sig { returns(T.any(Interface::CodeAction, Error)) }
59
+ def refactor_variable
46
60
  return Error::EmptySelection if @document.source.empty?
47
61
 
48
62
  source_range = @code_action.dig(:data, :range)
@@ -54,9 +68,11 @@ module RubyLsp
54
68
  extracted_source = T.must(@document.source[start_index...end_index])
55
69
 
56
70
  # Find the closest statements node, so that we place the refactor in a valid position
57
- closest_statements, parent_statements = @document
71
+ node_context = @document
58
72
  .locate(@document.tree, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
59
73
 
74
+ closest_statements = node_context.node
75
+ parent_statements = node_context.parent
60
76
  return Error::InvalidTargetRange if closest_statements.nil? || closest_statements.child_nodes.compact.empty?
61
77
 
62
78
  # Find the node with the end line closest to the requested position, so that we can place the refactor
@@ -117,7 +133,7 @@ module RubyLsp
117
133
  end
118
134
 
119
135
  Interface::CodeAction.new(
120
- title: "Refactor: Extract Variable",
136
+ title: CodeActions::EXTRACT_TO_VARIABLE_TITLE,
121
137
  edit: Interface::WorkspaceEdit.new(
122
138
  document_changes: [
123
139
  Interface::TextDocumentEdit.new(
@@ -135,6 +151,61 @@ module RubyLsp
135
151
  )
136
152
  end
137
153
 
154
+ sig { returns(T.any(Interface::CodeAction, Error)) }
155
+ def refactor_method
156
+ return Error::EmptySelection if @document.source.empty?
157
+
158
+ source_range = @code_action.dig(:data, :range)
159
+ return Error::EmptySelection if source_range[:start] == source_range[:end]
160
+
161
+ scanner = @document.create_scanner
162
+ start_index = scanner.find_char_position(source_range[:start])
163
+ end_index = scanner.find_char_position(source_range[:end])
164
+ extracted_source = T.must(@document.source[start_index...end_index])
165
+
166
+ # Find the closest method declaration node, so that we place the refactor in a valid position
167
+ node_context = @document.locate(@document.tree, start_index, node_types: [Prism::DefNode])
168
+ closest_def = T.cast(node_context.node, Prism::DefNode)
169
+ return Error::InvalidTargetRange if closest_def.nil?
170
+
171
+ end_keyword_loc = closest_def.end_keyword_loc
172
+ return Error::InvalidTargetRange if end_keyword_loc.nil?
173
+
174
+ end_line = end_keyword_loc.end_line - 1
175
+ character = end_keyword_loc.end_column
176
+ indentation = " " * end_keyword_loc.start_column
177
+ target_range = {
178
+ start: { line: end_line, character: character },
179
+ end: { line: end_line, character: character },
180
+ }
181
+
182
+ new_method_source = <<~RUBY.chomp
183
+
184
+
185
+ #{indentation}def #{NEW_METHOD_NAME}
186
+ #{indentation} #{extracted_source}
187
+ #{indentation}end
188
+ RUBY
189
+
190
+ Interface::CodeAction.new(
191
+ title: CodeActions::EXTRACT_TO_METHOD_TITLE,
192
+ edit: Interface::WorkspaceEdit.new(
193
+ document_changes: [
194
+ Interface::TextDocumentEdit.new(
195
+ text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
196
+ uri: @code_action.dig(:data, :uri),
197
+ version: nil,
198
+ ),
199
+ edits: [
200
+ create_text_edit(target_range, new_method_source),
201
+ create_text_edit(source_range, NEW_METHOD_NAME),
202
+ ],
203
+ ),
204
+ ],
205
+ ),
206
+ )
207
+ end
208
+
138
209
  private
139
210
 
140
211
  sig { params(range: T::Hash[Symbol, T.untyped], new_text: String).returns(Interface::TextEdit) }
@@ -19,6 +19,9 @@ module RubyLsp
19
19
  class CodeActions < Request
20
20
  extend T::Sig
21
21
 
22
+ EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable"
23
+ EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method"
24
+
22
25
  class << self
23
26
  extend T::Sig
24
27
 
@@ -52,22 +55,20 @@ module RubyLsp
52
55
  end
53
56
 
54
57
  # Only add refactor actions if there's a non empty selection in the editor
55
- code_actions << refactor_code_action(@range, @uri) unless @range.dig(:start) == @range.dig(:end)
56
- code_actions
57
- end
58
-
59
- private
58
+ unless @range.dig(:start) == @range.dig(:end)
59
+ code_actions << Interface::CodeAction.new(
60
+ title: EXTRACT_TO_VARIABLE_TITLE,
61
+ kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
62
+ data: { range: @range, uri: @uri.to_s },
63
+ )
64
+ code_actions << Interface::CodeAction.new(
65
+ title: EXTRACT_TO_METHOD_TITLE,
66
+ kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
67
+ data: { range: @range, uri: @uri.to_s },
68
+ )
69
+ end
60
70
 
61
- sig { params(range: T::Hash[Symbol, T.untyped], uri: URI::Generic).returns(Interface::CodeAction) }
62
- def refactor_code_action(range, uri)
63
- Interface::CodeAction.new(
64
- title: "Refactor: Extract Variable",
65
- kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
66
- data: {
67
- range: range,
68
- uri: uri.to_s,
69
- },
70
- )
71
+ code_actions
71
72
  end
72
73
  end
73
74
  end
@@ -17,6 +17,7 @@ module RubyLsp
17
17
  # - Constants
18
18
  # - Require paths
19
19
  # - Methods invoked on self only
20
+ # - Instance variables
20
21
  #
21
22
  # # Example
22
23
  #
@@ -35,7 +36,7 @@ module RubyLsp
35
36
  def provider
36
37
  Interface::CompletionOptions.new(
37
38
  resolve_provider: true,
38
- trigger_characters: ["/", "\"", "'", ":"],
39
+ trigger_characters: ["/", "\"", "'", ":", "@"],
39
40
  completion_item: {
40
41
  labelDetailsSupport: true,
41
42
  },
@@ -59,10 +60,20 @@ module RubyLsp
59
60
  # Completion always receives the position immediately after the character that was just typed. Here we adjust it
60
61
  # back by 1, so that we find the right node
61
62
  char_position = document.create_scanner.find_char_position(position) - 1
62
- matched, parent, nesting = document.locate(
63
+ node_context = document.locate(
63
64
  document.tree,
64
65
  char_position,
65
- node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode],
66
+ node_types: [
67
+ Prism::CallNode,
68
+ Prism::ConstantReadNode,
69
+ Prism::ConstantPathNode,
70
+ Prism::InstanceVariableReadNode,
71
+ Prism::InstanceVariableAndWriteNode,
72
+ Prism::InstanceVariableOperatorWriteNode,
73
+ Prism::InstanceVariableOrWriteNode,
74
+ Prism::InstanceVariableTargetNode,
75
+ Prism::InstanceVariableWriteNode,
76
+ ],
66
77
  )
67
78
  @response_builder = T.let(
68
79
  ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem].new,
@@ -72,27 +83,24 @@ module RubyLsp
72
83
  Listeners::Completion.new(
73
84
  @response_builder,
74
85
  global_state,
75
- nesting,
86
+ node_context,
76
87
  typechecker_enabled,
77
88
  dispatcher,
78
89
  document.uri,
79
90
  )
80
91
 
81
92
  Addon.addons.each do |addon|
82
- addon.create_completion_listener(@response_builder, nesting, dispatcher, document.uri)
93
+ addon.create_completion_listener(@response_builder, node_context, dispatcher, document.uri)
83
94
  end
84
95
 
96
+ matched = node_context.node
97
+ parent = node_context.parent
85
98
  return unless matched && parent
86
99
 
87
- @target = case matched
88
- when Prism::CallNode
100
+ @target = if parent.is_a?(Prism::ConstantPathNode) && matched.is_a?(Prism::ConstantReadNode)
101
+ parent
102
+ else
89
103
  matched
90
- when Prism::ConstantReadNode, Prism::ConstantPathNode
91
- if parent.is_a?(Prism::ConstantPathNode) && matched.is_a?(Prism::ConstantReadNode)
92
- parent
93
- else
94
- matched
95
- end
96
104
  end
97
105
  end
98
106
 
@@ -47,6 +47,15 @@ module RubyLsp
47
47
  label = @item[:label]
48
48
  entries = @index[label] || []
49
49
 
50
+ owner_name = @item.dig(:data, :owner_name)
51
+
52
+ if owner_name
53
+ entries = entries.select do |entry|
54
+ (entry.is_a?(RubyIndexer::Entry::Member) || entry.is_a?(RubyIndexer::Entry::InstanceVariable)) &&
55
+ entry.owner&.name == owner_name
56
+ end
57
+ end
58
+
50
59
  @item[:labelDetails] = Interface::CompletionItemLabelDetails.new(
51
60
  description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
52
61
  )
@@ -17,7 +17,8 @@ module RubyLsp
17
17
  # - Modules
18
18
  # - Constants
19
19
  # - Require paths
20
- # - Methods invoked on self only
20
+ # - Methods invoked on self only and on receivers where the type is unknown
21
+ # - Instance variables
21
22
  #
22
23
  # # Example
23
24
  #
@@ -46,11 +47,27 @@ module RubyLsp
46
47
  )
47
48
  @dispatcher = dispatcher
48
49
 
49
- target, parent, nesting = document.locate_node(
50
+ node_context = document.locate_node(
50
51
  position,
51
- node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::BlockArgumentNode],
52
+ node_types: [
53
+ Prism::CallNode,
54
+ Prism::ConstantReadNode,
55
+ Prism::ConstantPathNode,
56
+ Prism::BlockArgumentNode,
57
+ Prism::InstanceVariableReadNode,
58
+ Prism::InstanceVariableAndWriteNode,
59
+ Prism::InstanceVariableOperatorWriteNode,
60
+ Prism::InstanceVariableOrWriteNode,
61
+ Prism::InstanceVariableTargetNode,
62
+ Prism::InstanceVariableWriteNode,
63
+ Prism::SymbolNode,
64
+ Prism::StringNode,
65
+ ],
52
66
  )
53
67
 
68
+ target = node_context.node
69
+ parent = node_context.parent
70
+
54
71
  if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
55
72
  # If the target is part of a constant path node, we need to find the exact portion of the constant that the
56
73
  # user is requesting to go to definition for
@@ -64,6 +81,9 @@ module RubyLsp
64
81
  # If the target is a method call, we need to ensure that the requested position is exactly on top of the
65
82
  # method identifier. Otherwise, we risk showing definitions for unrelated things
66
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
67
87
  end
68
88
 
69
89
  if target
@@ -71,13 +91,13 @@ module RubyLsp
71
91
  @response_builder,
72
92
  global_state,
73
93
  document.uri,
74
- nesting,
94
+ node_context,
75
95
  dispatcher,
76
96
  typechecker_enabled,
77
97
  )
78
98
 
79
99
  Addon.addons.each do |addon|
80
- addon.create_definition_listener(@response_builder, document.uri, nesting, dispatcher)
100
+ addon.create_definition_listener(@response_builder, document.uri, node_context, dispatcher)
81
101
  end
82
102
  end
83
103
 
@@ -36,12 +36,12 @@ module RubyLsp
36
36
  end
37
37
  def initialize(document, position, dispatcher)
38
38
  super()
39
- target, parent = document.locate_node(position)
39
+ node_context = document.locate_node(position)
40
40
  @response_builder = T.let(
41
41
  ResponseBuilders::CollectionResponseBuilder[Interface::DocumentHighlight].new,
42
42
  ResponseBuilders::CollectionResponseBuilder[Interface::DocumentHighlight],
43
43
  )
44
- Listeners::DocumentHighlight.new(@response_builder, target, parent, dispatcher)
44
+ Listeners::DocumentHighlight.new(@response_builder, node_context.node, node_context.parent, dispatcher)
45
45
  end
46
46
 
47
47
  sig { override.returns(T::Array[Interface::DocumentHighlight]) }
@@ -41,10 +41,9 @@ module RubyLsp
41
41
  end
42
42
  def initialize(document, global_state, position, dispatcher, typechecker_enabled)
43
43
  super()
44
- target, parent, nesting = document.locate_node(
45
- position,
46
- node_types: Listeners::Hover::ALLOWED_TARGETS,
47
- )
44
+ node_context = document.locate_node(position, node_types: Listeners::Hover::ALLOWED_TARGETS)
45
+ target = node_context.node
46
+ parent = node_context.parent
48
47
 
49
48
  if (Listeners::Hover::ALLOWED_TARGETS.include?(parent.class) &&
50
49
  !Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
@@ -66,9 +65,9 @@ module RubyLsp
66
65
  @target = T.let(target, T.nilable(Prism::Node))
67
66
  uri = document.uri
68
67
  @response_builder = T.let(ResponseBuilders::Hover.new, ResponseBuilders::Hover)
69
- Listeners::Hover.new(@response_builder, global_state, uri, nesting, dispatcher, typechecker_enabled)
68
+ Listeners::Hover.new(@response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled)
70
69
  Addon.addons.each do |addon|
71
- addon.create_hover_listener(@response_builder, nesting, dispatcher)
70
+ addon.create_hover_listener(@response_builder, node_context, dispatcher)
72
71
  end
73
72
 
74
73
  @dispatcher = dispatcher
@@ -213,12 +213,13 @@ module RubyLsp
213
213
  sig { void }
214
214
  def auto_indent_after_end_keyword
215
215
  current_line = @lines[@position[:line]]
216
- return unless current_line&.strip == "end"
216
+ return unless current_line && current_line.strip == "end"
217
217
 
218
- target, _parent, _nesting = @document.locate_node({
218
+ node_context = @document.locate_node({
219
219
  line: @position[:line],
220
220
  character: @position[:character] - 1,
221
221
  })
222
+ target = node_context.node
222
223
 
223
224
  statements = case target
224
225
  when Prism::IfNode, Prism::UnlessNode, Prism::ForNode, Prism::WhileNode, Prism::UntilNode
@@ -226,11 +227,14 @@ module RubyLsp
226
227
  end
227
228
  return unless statements
228
229
 
230
+ current_indentation = find_indentation(current_line)
229
231
  statements.body.each do |node|
230
232
  loc = node.location
231
- next unless loc.start_column == @indentation
233
+ next unless loc.start_column == current_indentation
232
234
 
233
- add_edit_with_text(" ", { line: loc.start_line - 1, character: 0 })
235
+ (loc.start_line..loc.end_line).each do |line|
236
+ add_edit_with_text(" ", { line: line - 1, character: 0 })
237
+ end
234
238
  end
235
239
 
236
240
  move_cursor_to(@position[:line], @position[:character])
@@ -51,17 +51,17 @@ module RubyLsp
51
51
  end
52
52
  def initialize(document, global_state, position, context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
53
53
  super()
54
- target, parent, nesting = document.locate_node(
54
+ node_context = document.locate_node(
55
55
  { line: position[:line], character: position[:character] },
56
56
  node_types: [Prism::CallNode],
57
57
  )
58
58
 
59
- target = adjust_for_nested_target(target, parent, position)
59
+ target = adjust_for_nested_target(node_context.node, node_context.parent, position)
60
60
 
61
61
  @target = T.let(target, T.nilable(Prism::Node))
62
62
  @dispatcher = dispatcher
63
63
  @response_builder = T.let(ResponseBuilders::SignatureHelp.new, ResponseBuilders::SignatureHelp)
64
- Listeners::SignatureHelp.new(@response_builder, global_state, nesting, dispatcher, typechecker_enabled)
64
+ Listeners::SignatureHelp.new(@response_builder, global_state, node_context, dispatcher, typechecker_enabled)
65
65
  end
66
66
 
67
67
  sig { override.returns(T.nilable(Interface::SignatureHelp)) }
@@ -50,12 +50,12 @@ module RubyLsp
50
50
  params(
51
51
  node: Prism::Node,
52
52
  title: String,
53
- command_name: T.nilable(String),
53
+ command_name: String,
54
54
  arguments: T.nilable(T::Array[T.untyped]),
55
55
  data: T.nilable(T::Hash[T.untyped, T.untyped]),
56
56
  ).returns(Interface::CodeLens)
57
57
  end
58
- def create_code_lens(node, title:, command_name: nil, arguments: nil, data: nil)
58
+ def create_code_lens(node, title:, command_name:, arguments:, data:)
59
59
  range = range_from_node(node)
60
60
 
61
61
  Interface::CodeLens.new(
@@ -155,7 +155,8 @@ module RubyLsp
155
155
  end
156
156
  def constant_name(node)
157
157
  node.full_name
158
- rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError
158
+ rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
159
+ Prism::ConstantPathNode::MissingNodesInConstantPathError
159
160
  nil
160
161
  end
161
162
 
@@ -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,13 +33,14 @@ 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
- next if entry.visibility == :private
43
+ next if entry.private?
43
44
 
44
45
  kind = kind_for_entry(entry)
45
46
  loc = entry.location
@@ -79,6 +80,8 @@ module RubyLsp
79
80
  entry.name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
80
81
  when RubyIndexer::Entry::Accessor
81
82
  Constant::SymbolKind::PROPERTY
83
+ when RubyIndexer::Entry::InstanceVariable
84
+ Constant::SymbolKind::FIELD
82
85
  end
83
86
  end
84
87
  end
@@ -104,7 +104,7 @@ module RubyLsp
104
104
  ),
105
105
  )
106
106
 
107
- $stderr.puts(errored_addons.map(&:backtraces).join("\n\n"))
107
+ $stderr.puts(errored_addons.map(&:errors_details).join("\n\n"))
108
108
  end
109
109
  end
110
110
 
@@ -134,7 +134,7 @@ module RubyLsp
134
134
  when Hash
135
135
  # If the configuration is already a hash, merge it with a default value of `true`. That way clients don't have
136
136
  # to opt-in to every single feature
137
- Hash.new(true).merge!(configured_features)
137
+ Hash.new(true).merge!(configured_features.transform_keys(&:to_s))
138
138
  else
139
139
  # If no configuration was passed by the client, just enable every feature
140
140
  Hash.new(true)
@@ -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: {
@@ -498,6 +498,13 @@ module RubyLsp
498
498
  ),
499
499
  )
500
500
  raise Requests::CodeActionResolve::CodeActionError
501
+ when Requests::CodeActionResolve::Error::UnknownCodeAction
502
+ send_message(
503
+ Notification.window_show_error(
504
+ "Unknown code action",
505
+ ),
506
+ )
507
+ raise Requests::CodeActionResolve::CodeActionError
501
508
  else
502
509
  send_message(Result.new(id: message[:id], response: result))
503
510
  end
@@ -619,8 +626,7 @@ module RubyLsp
619
626
  when Constant::FileChangeType::CREATED
620
627
  index.index_single(indexable)
621
628
  when Constant::FileChangeType::CHANGED
622
- index.delete(indexable)
623
- index.index_single(indexable)
629
+ index.handle_change(indexable)
624
630
  when Constant::FileChangeType::DELETED
625
631
  index.delete(indexable)
626
632
  end
@@ -21,6 +21,7 @@ module RubyLsp
21
21
  &block)
22
22
  server = RubyLsp::Server.new(test_mode: true)
23
23
  server.global_state.stubs(:typechecker).returns(false) if stub_no_typechecker
24
+ server.global_state.apply_options({})
24
25
 
25
26
  if source
26
27
  server.process_message({