ruby-lsp 0.16.6 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +21 -4
  5. data/exe/ruby-lsp-check +1 -3
  6. data/exe/ruby-lsp-doctor +1 -4
  7. data/lib/core_ext/uri.rb +3 -0
  8. data/lib/ruby_indexer/lib/ruby_indexer/{collector.rb → declaration_listener.rb} +258 -140
  9. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +101 -12
  10. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +187 -12
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -1
  12. data/lib/ruby_indexer/test/classes_and_modules_test.rb +106 -10
  13. data/lib/ruby_indexer/test/configuration_test.rb +4 -5
  14. data/lib/ruby_indexer/test/constant_test.rb +11 -8
  15. data/lib/ruby_indexer/test/index_test.rb +528 -0
  16. data/lib/ruby_indexer/test/instance_variables_test.rb +131 -0
  17. data/lib/ruby_indexer/test/method_test.rb +93 -0
  18. data/lib/ruby_indexer/test/test_case.rb +3 -1
  19. data/lib/ruby_lsp/addon.rb +8 -8
  20. data/lib/ruby_lsp/document.rb +3 -3
  21. data/lib/ruby_lsp/internal.rb +1 -0
  22. data/lib/ruby_lsp/listeners/code_lens.rb +11 -0
  23. data/lib/ruby_lsp/listeners/completion.rb +144 -51
  24. data/lib/ruby_lsp/listeners/definition.rb +77 -12
  25. data/lib/ruby_lsp/listeners/document_highlight.rb +1 -1
  26. data/lib/ruby_lsp/listeners/document_link.rb +1 -1
  27. data/lib/ruby_lsp/listeners/hover.rb +60 -6
  28. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +59 -3
  29. data/lib/ruby_lsp/listeners/signature_help.rb +4 -4
  30. data/lib/ruby_lsp/node_context.rb +28 -0
  31. data/lib/ruby_lsp/requests/code_action_resolve.rb +73 -2
  32. data/lib/ruby_lsp/requests/code_actions.rb +16 -15
  33. data/lib/ruby_lsp/requests/completion.rb +22 -13
  34. data/lib/ruby_lsp/requests/completion_resolve.rb +26 -10
  35. data/lib/ruby_lsp/requests/definition.rb +21 -5
  36. data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
  37. data/lib/ruby_lsp/requests/hover.rb +5 -6
  38. data/lib/ruby_lsp/requests/on_type_formatting.rb +8 -4
  39. data/lib/ruby_lsp/requests/signature_help.rb +3 -3
  40. data/lib/ruby_lsp/requests/support/common.rb +20 -1
  41. data/lib/ruby_lsp/requests/workspace_symbol.rb +3 -1
  42. data/lib/ruby_lsp/server.rb +10 -4
  43. metadata +10 -8
@@ -36,20 +36,36 @@ module RubyLsp
36
36
  @item = item
37
37
  end
38
38
 
39
- sig { override.returns(Interface::CompletionItem) }
39
+ sig { override.returns(T::Hash[Symbol, T.untyped]) }
40
40
  def perform
41
+ # Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
42
+ # a completion resolve request must always return the original completion item without modifying ANY fields
43
+ # other than label details and documentation. If we modify anything, the completion behaviour might be broken.
44
+ #
45
+ # For example, forgetting to return the `insertText` included in the original item will make the editor use the
46
+ # `label` for the text edit instead
41
47
  label = @item[:label]
42
48
  entries = @index[label] || []
43
- Interface::CompletionItem.new(
44
- label: label,
45
- label_details: Interface::CompletionItemLabelDetails.new(
46
- description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
47
- ),
48
- documentation: Interface::MarkupContent.new(
49
- kind: "markdown",
50
- value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
51
- ),
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
+
59
+ @item[:labelDetails] = Interface::CompletionItemLabelDetails.new(
60
+ description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
52
61
  )
62
+
63
+ @item[:documentation] = Interface::MarkupContent.new(
64
+ kind: "markdown",
65
+ value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
66
+ )
67
+
68
+ @item
53
69
  end
54
70
  end
55
71
  end
@@ -12,11 +12,13 @@ module RubyLsp
12
12
  # definition of the symbol under the cursor.
13
13
  #
14
14
  # Currently supported targets:
15
+ #
15
16
  # - Classes
16
17
  # - Modules
17
18
  # - Constants
18
19
  # - Require paths
19
- # - Methods invoked on self only
20
+ # - Methods invoked on self only and on receivers where the type is unknown
21
+ # - Instance variables
20
22
  #
21
23
  # # Example
22
24
  #
@@ -45,11 +47,25 @@ module RubyLsp
45
47
  )
46
48
  @dispatcher = dispatcher
47
49
 
48
- target, parent, nesting = document.locate_node(
50
+ node_context = document.locate_node(
49
51
  position,
50
- node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode],
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
+ ],
51
64
  )
52
65
 
66
+ target = node_context.node
67
+ parent = node_context.parent
68
+
53
69
  if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
54
70
  # If the target is part of a constant path node, we need to find the exact portion of the constant that the
55
71
  # user is requesting to go to definition for
@@ -70,13 +86,13 @@ module RubyLsp
70
86
  @response_builder,
71
87
  global_state,
72
88
  document.uri,
73
- nesting,
89
+ node_context,
74
90
  dispatcher,
75
91
  typechecker_enabled,
76
92
  )
77
93
 
78
94
  Addon.addons.each do |addon|
79
- addon.create_definition_listener(@response_builder, document.uri, nesting, dispatcher)
95
+ addon.create_definition_listener(@response_builder, document.uri, node_context, dispatcher)
80
96
  end
81
97
  end
82
98
 
@@ -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)) }
@@ -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
 
@@ -167,6 +168,24 @@ module RubyLsp
167
168
  constant_name(path)
168
169
  end
169
170
  end
171
+
172
+ # Iterates over each part of a constant path, so that we can easily push response items for each section of the
173
+ # name. For example, for `Foo::Bar::Baz`, this method will invoke the block with `Foo`, then `Bar` and finally
174
+ # `Baz`.
175
+ sig do
176
+ params(
177
+ node: Prism::Node,
178
+ block: T.proc.params(part: Prism::Node).void,
179
+ ).void
180
+ end
181
+ def each_constant_path_part(node, &block)
182
+ current = T.let(node, T.nilable(Prism::Node))
183
+
184
+ while current.is_a?(Prism::ConstantPathNode)
185
+ block.call(current)
186
+ current = current.parent
187
+ end
188
+ end
170
189
  end
171
190
  end
172
191
  end
@@ -39,7 +39,7 @@ module RubyLsp
39
39
  next if @global_state.typechecker && not_in_dependencies?(file_path)
40
40
 
41
41
  # We should never show private symbols when searching the entire workspace
42
- next if entry.visibility == :private
42
+ next if entry.private?
43
43
 
44
44
  kind = kind_for_entry(entry)
45
45
  loc = entry.location
@@ -79,6 +79,8 @@ module RubyLsp
79
79
  entry.name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
80
80
  when RubyIndexer::Entry::Accessor
81
81
  Constant::SymbolKind::PROPERTY
82
+ when RubyIndexer::Entry::InstanceVariable
83
+ Constant::SymbolKind::FIELD
82
84
  end
83
85
  end
84
86
  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)
@@ -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
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.16.6
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-26 00:00:00.000000000 Z
11
+ date: 2024-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -30,20 +30,20 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 0.23.0
33
+ version: 0.29.0
34
34
  - - "<"
35
35
  - !ruby/object:Gem::Version
36
- version: '0.28'
36
+ version: '0.30'
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 0.23.0
43
+ version: 0.29.0
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.28'
46
+ version: '0.30'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sorbet-runtime
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -78,8 +78,8 @@ files:
78
78
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
79
79
  - lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
80
80
  - lib/ruby-lsp.rb
81
- - lib/ruby_indexer/lib/ruby_indexer/collector.rb
82
81
  - lib/ruby_indexer/lib/ruby_indexer/configuration.rb
82
+ - lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb
83
83
  - lib/ruby_indexer/lib/ruby_indexer/entry.rb
84
84
  - lib/ruby_indexer/lib/ruby_indexer/index.rb
85
85
  - lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb
@@ -90,6 +90,7 @@ files:
90
90
  - lib/ruby_indexer/test/configuration_test.rb
91
91
  - lib/ruby_indexer/test/constant_test.rb
92
92
  - lib/ruby_indexer/test/index_test.rb
93
+ - lib/ruby_indexer/test/instance_variables_test.rb
93
94
  - lib/ruby_indexer/test/method_test.rb
94
95
  - lib/ruby_indexer/test/prefix_tree_test.rb
95
96
  - lib/ruby_indexer/test/test_case.rb
@@ -111,6 +112,7 @@ files:
111
112
  - lib/ruby_lsp/listeners/semantic_highlighting.rb
112
113
  - lib/ruby_lsp/listeners/signature_help.rb
113
114
  - lib/ruby_lsp/load_sorbet.rb
115
+ - lib/ruby_lsp/node_context.rb
114
116
  - lib/ruby_lsp/parameter_scope.rb
115
117
  - lib/ruby_lsp/requests.rb
116
118
  - lib/ruby_lsp/requests/code_action_resolve.rb
@@ -178,7 +180,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
180
  - !ruby/object:Gem::Version
179
181
  version: '0'
180
182
  requirements: []
181
- rubygems_version: 3.5.9
183
+ rubygems_version: 3.5.10
182
184
  signing_key:
183
185
  specification_version: 4
184
186
  summary: An opinionated language server for Ruby