ruby-lsp 0.4.1 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +44 -52
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +12 -0
  5. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +62 -0
  6. data/lib/ruby_lsp/document.rb +13 -4
  7. data/lib/ruby_lsp/executor.rb +70 -26
  8. data/lib/ruby_lsp/internal.rb +1 -0
  9. data/lib/ruby_lsp/requests/base_request.rb +15 -6
  10. data/lib/ruby_lsp/requests/code_action_resolve.rb +40 -19
  11. data/lib/ruby_lsp/requests/code_actions.rb +5 -4
  12. data/lib/ruby_lsp/requests/diagnostics.rb +4 -4
  13. data/lib/ruby_lsp/requests/document_highlight.rb +3 -3
  14. data/lib/ruby_lsp/requests/document_link.rb +7 -7
  15. data/lib/ruby_lsp/requests/document_symbol.rb +14 -11
  16. data/lib/ruby_lsp/requests/folding_ranges.rb +38 -11
  17. data/lib/ruby_lsp/requests/formatting.rb +18 -5
  18. data/lib/ruby_lsp/requests/hover.rb +7 -6
  19. data/lib/ruby_lsp/requests/inlay_hints.rb +5 -4
  20. data/lib/ruby_lsp/requests/path_completion.rb +9 -3
  21. data/lib/ruby_lsp/requests/selection_ranges.rb +3 -3
  22. data/lib/ruby_lsp/requests/semantic_highlighting.rb +72 -8
  23. data/lib/ruby_lsp/requests/support/highlight_target.rb +5 -4
  24. data/lib/ruby_lsp/requests/support/rails_document_client.rb +7 -6
  25. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -1
  26. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
  27. data/lib/ruby_lsp/requests/support/sorbet.rb +5 -15
  28. data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +39 -0
  29. data/lib/ruby_lsp/server.rb +4 -1
  30. data/lib/ruby_lsp/store.rb +11 -7
  31. data/lib/ruby_lsp/utils.rb +3 -0
  32. metadata +7 -5
@@ -21,16 +21,15 @@ module RubyLsp
21
21
 
22
22
  sig do
23
23
  params(
24
- uri: String,
25
24
  document: Document,
26
25
  range: Document::RangeShape,
27
26
  context: T::Hash[Symbol, T.untyped],
28
27
  ).void
29
28
  end
30
- def initialize(uri, document, range, context)
29
+ def initialize(document, range, context)
31
30
  super(document)
32
31
 
33
- @uri = uri
32
+ @uri = T.let(document.uri, String)
34
33
  @range = range
35
34
  @context = context
36
35
  end
@@ -49,7 +48,9 @@ module RubyLsp
49
48
  code_action if diagnostic.dig(:data, :correctable) && cover?(range)
50
49
  end
51
50
 
52
- code_actions << refactor_code_action(@range, @uri)
51
+ # Only add refactor actions if there's a non empty selection in the editor
52
+ code_actions << refactor_code_action(@range, @uri) unless @range.dig(:start) == @range.dig(:end)
53
+ code_actions
53
54
  end
54
55
 
55
56
  private
@@ -21,11 +21,11 @@ module RubyLsp
21
21
  class Diagnostics < BaseRequest
22
22
  extend T::Sig
23
23
 
24
- sig { params(uri: String, document: Document).void }
25
- def initialize(uri, document)
24
+ sig { params(document: Document).void }
25
+ def initialize(document)
26
26
  super(document)
27
27
 
28
- @uri = uri
28
+ @uri = T.let(document.uri, String)
29
29
  end
30
30
 
31
31
  sig { override.returns(T.nilable(T.all(T::Array[Support::RuboCopDiagnostic], Object))) }
@@ -34,7 +34,7 @@ module RubyLsp
34
34
  return unless defined?(Support::RuboCopDiagnosticsRunner)
35
35
 
36
36
  # Don't try to run RuboCop diagnostics for files outside the current working directory
37
- return unless @uri.sub("file://", "").start_with?(Dir.pwd)
37
+ return unless @uri.start_with?(WORKSPACE_URI)
38
38
 
39
39
  Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document)
40
40
  end
@@ -29,14 +29,14 @@ module RubyLsp
29
29
  def initialize(document, position)
30
30
  super(document)
31
31
 
32
- @highlights = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
32
+ @highlights = T.let([], T::Array[Interface::DocumentHighlight])
33
33
  return unless document.parsed?
34
34
 
35
35
  position = document.create_scanner.find_char_position(position)
36
36
  @target = T.let(find(T.must(document.tree), position), T.nilable(Support::HighlightTarget))
37
37
  end
38
38
 
39
- sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentHighlight], Object)) }
39
+ sig { override.returns(T.all(T::Array[Interface::DocumentHighlight], Object)) }
40
40
  def run
41
41
  # no @target means the target is not highlightable
42
42
  visit(@document.tree) if @document.parsed? && @target
@@ -90,7 +90,7 @@ module RubyLsp
90
90
  sig { params(match: Support::HighlightTarget::HighlightMatch).void }
91
91
  def add_highlight(match)
92
92
  range = range_from_syntax_tree_node(match.node)
93
- @highlights << LanguageServer::Protocol::Interface::DocumentHighlight.new(range: range, kind: match.type)
93
+ @highlights << Interface::DocumentHighlight.new(range: range, kind: match.type)
94
94
  end
95
95
  end
96
96
  end
@@ -69,18 +69,18 @@ module RubyLsp
69
69
  end
70
70
  end
71
71
 
72
- sig { params(uri: String, document: Document).void }
73
- def initialize(uri, document)
72
+ sig { params(document: Document).void }
73
+ def initialize(document)
74
74
  super(document)
75
75
 
76
76
  # Match the version based on the version in the RBI file name. Notice that the `@` symbol is sanitized to `%40`
77
77
  # in the URI
78
- version_match = /(?<=%40)[\d.]+(?=\.rbi$)/.match(uri)
78
+ version_match = /(?<=%40)[\d.]+(?=\.rbi$)/.match(document.uri)
79
79
  @gem_version = T.let(version_match && version_match[0], T.nilable(String))
80
- @links = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentLink])
80
+ @links = T.let([], T::Array[Interface::DocumentLink])
81
81
  end
82
82
 
83
- sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentLink], Object)) }
83
+ sig { override.returns(T.all(T::Array[Interface::DocumentLink], Object)) }
84
84
  def run
85
85
  visit(@document.tree) if @document.parsed?
86
86
  @links
@@ -91,12 +91,12 @@ module RubyLsp
91
91
  match = node.value.match(%r{source://.*#\d+$})
92
92
  return unless match
93
93
 
94
- uri = T.cast(URI(match[0]), URI::Source)
94
+ uri = T.cast(URI(T.must(match[0])), URI::Source)
95
95
  gem_version = T.must(resolve_version(uri))
96
96
  file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, uri.path)
97
97
  return if file_path.nil?
98
98
 
99
- @links << LanguageServer::Protocol::Interface::DocumentLink.new(
99
+ @links << Interface::DocumentLink.new(
100
100
  range: range_from_syntax_tree_node(node),
101
101
  target: "file://#{file_path}##{uri.line_number}",
102
102
  tooltip: "Jump to #{file_path}##{uri.line_number}",
@@ -66,12 +66,12 @@ module RubyLsp
66
66
  class SymbolHierarchyRoot
67
67
  extend T::Sig
68
68
 
69
- sig { returns(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol]) }
69
+ sig { returns(T::Array[Interface::DocumentSymbol]) }
70
70
  attr_reader :children
71
71
 
72
72
  sig { void }
73
73
  def initialize
74
- @children = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentSymbol])
74
+ @children = T.let([], T::Array[Interface::DocumentSymbol])
75
75
  end
76
76
  end
77
77
 
@@ -82,11 +82,11 @@ module RubyLsp
82
82
  @root = T.let(SymbolHierarchyRoot.new, SymbolHierarchyRoot)
83
83
  @stack = T.let(
84
84
  [@root],
85
- T::Array[T.any(SymbolHierarchyRoot, LanguageServer::Protocol::Interface::DocumentSymbol)],
85
+ T::Array[T.any(SymbolHierarchyRoot, Interface::DocumentSymbol)],
86
86
  )
87
87
  end
88
88
 
89
- sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol], Object)) }
89
+ sig { override.returns(T.all(T::Array[Interface::DocumentSymbol], Object)) }
90
90
  def run
91
91
  visit(@document.tree) if @document.parsed?
92
92
  @root.children
@@ -108,7 +108,7 @@ module RubyLsp
108
108
 
109
109
  sig { override.params(node: SyntaxTree::Command).void }
110
110
  def visit_command(node)
111
- return unless ATTR_ACCESSORS.include?(node.message.value)
111
+ return visit(node.arguments) unless ATTR_ACCESSORS.include?(node.message.value)
112
112
 
113
113
  node.arguments.parts.each do |argument|
114
114
  next unless argument.is_a?(SyntaxTree::SymbolLiteral)
@@ -134,7 +134,9 @@ module RubyLsp
134
134
 
135
135
  sig { override.params(node: SyntaxTree::DefNode).void }
136
136
  def visit_def(node)
137
- if node.target&.value&.value == "self"
137
+ target = node.target
138
+
139
+ if target.is_a?(SyntaxTree::VarRef) && target.value.is_a?(SyntaxTree::Kw) && target.value.value == "self"
138
140
  name = "self.#{node.name.value}"
139
141
  kind = :method
140
142
  else
@@ -180,7 +182,8 @@ module RubyLsp
180
182
 
181
183
  sig { override.params(node: SyntaxTree::VarField).void }
182
184
  def visit_var_field(node)
183
- kind = case node.value
185
+ value = node.value
186
+ kind = case value
184
187
  when SyntaxTree::Const
185
188
  :constant
186
189
  when SyntaxTree::CVar, SyntaxTree::IVar
@@ -190,10 +193,10 @@ module RubyLsp
190
193
  end
191
194
 
192
195
  create_document_symbol(
193
- name: node.value.value,
196
+ name: value.value,
194
197
  kind: kind,
195
198
  range_node: node,
196
- selection_range_node: node.value,
199
+ selection_range_node: value,
197
200
  )
198
201
  end
199
202
 
@@ -205,10 +208,10 @@ module RubyLsp
205
208
  kind: Symbol,
206
209
  range_node: SyntaxTree::Node,
207
210
  selection_range_node: SyntaxTree::Node,
208
- ).returns(LanguageServer::Protocol::Interface::DocumentSymbol)
211
+ ).returns(Interface::DocumentSymbol)
209
212
  end
210
213
  def create_document_symbol(name:, kind:, range_node:, selection_range_node:)
211
- symbol = LanguageServer::Protocol::Interface::DocumentSymbol.new(
214
+ symbol = Interface::DocumentSymbol.new(
212
215
  name: name,
213
216
  kind: SYMBOL_KIND[kind],
214
217
  range: range_from_syntax_tree_node(range_node),
@@ -24,7 +24,6 @@ module RubyLsp
24
24
  SyntaxTree::BlockNode,
25
25
  SyntaxTree::Case,
26
26
  SyntaxTree::ClassDeclaration,
27
- SyntaxTree::Command,
28
27
  SyntaxTree::For,
29
28
  SyntaxTree::HashLiteral,
30
29
  SyntaxTree::Heredoc,
@@ -64,11 +63,11 @@ module RubyLsp
64
63
  def initialize(document)
65
64
  super
66
65
 
67
- @ranges = T.let([], T::Array[LanguageServer::Protocol::Interface::FoldingRange])
66
+ @ranges = T.let([], T::Array[Interface::FoldingRange])
68
67
  @partial_range = T.let(nil, T.nilable(PartialRange))
69
68
  end
70
69
 
71
- sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::FoldingRange], Object)) }
70
+ sig { override.returns(T.all(T::Array[Interface::FoldingRange], Object)) }
72
71
  def run
73
72
  if @document.parsed?
74
73
  visit(@document.tree)
@@ -100,6 +99,11 @@ module RubyLsp
100
99
  add_call_range(node)
101
100
  return
102
101
  end
102
+ when SyntaxTree::Command
103
+ unless same_lines_for_command_and_block?(node)
104
+ location = node.location
105
+ add_lines_range(location.start_line, location.end_line - 1)
106
+ end
103
107
  when SyntaxTree::DefNode
104
108
  add_def_range(node)
105
109
  when SyntaxTree::StringConcat
@@ -110,6 +114,17 @@ module RubyLsp
110
114
  super
111
115
  end
112
116
 
117
+ # This is to prevent duplicate ranges
118
+ sig { params(node: T.any(SyntaxTree::Command, SyntaxTree::CommandCall)).returns(T::Boolean) }
119
+ def same_lines_for_command_and_block?(node)
120
+ node_block = node.block
121
+ return false unless node_block
122
+
123
+ location = node.location
124
+ block_location = node_block.location
125
+ block_location.start_line == location.start_line && block_location.end_line == location.end_line
126
+ end
127
+
113
128
  class PartialRange
114
129
  extend T::Sig
115
130
 
@@ -146,9 +161,9 @@ module RubyLsp
146
161
  node.is_a?(SyntaxTree::Comment) && @end_line + 1 != node.location.start_line - 1
147
162
  end
148
163
 
149
- sig { returns(LanguageServer::Protocol::Interface::FoldingRange) }
164
+ sig { returns(Interface::FoldingRange) }
150
165
  def to_range
151
- LanguageServer::Protocol::Interface::FoldingRange.new(
166
+ Interface::FoldingRange.new(
152
167
  start_line: @start_line,
153
168
  end_line: @end_line,
154
169
  kind: @kind,
@@ -205,7 +220,8 @@ module RubyLsp
205
220
 
206
221
  sig { params(node: T.any(SyntaxTree::CallNode, SyntaxTree::CommandCall)).void }
207
222
  def add_call_range(node)
208
- receiver = T.let(node.receiver, SyntaxTree::Node)
223
+ receiver = T.let(node.receiver, T.nilable(SyntaxTree::Node))
224
+
209
225
  loop do
210
226
  case receiver
211
227
  when SyntaxTree::CallNode
@@ -223,18 +239,27 @@ module RubyLsp
223
239
  end
224
240
  end
225
241
 
226
- add_lines_range(receiver.location.start_line, node.location.end_line - 1) if receiver
242
+ if receiver
243
+ unless node.is_a?(SyntaxTree::CommandCall) && same_lines_for_command_and_block?(node)
244
+ add_lines_range(
245
+ receiver.location.start_line,
246
+ node.location.end_line - 1,
247
+ )
248
+ end
249
+ end
227
250
 
228
251
  visit(node.arguments)
252
+ visit(node.block) if node.is_a?(SyntaxTree::CommandCall)
229
253
  end
230
254
 
231
255
  sig { params(node: SyntaxTree::DefNode).void }
232
256
  def add_def_range(node)
233
257
  # For an endless method with no arguments, `node.params` returns `nil` for Ruby 3.0, but a `Syntax::Params`
234
258
  # for Ruby 3.1
235
- return unless node.params
259
+ params = node.params
260
+ return unless params
236
261
 
237
- params_location = node.params.location
262
+ params_location = params.location
238
263
 
239
264
  if params_location.start_line < params_location.end_line
240
265
  add_lines_range(params_location.end_line, node.location.end_line - 1)
@@ -253,7 +278,9 @@ module RubyLsp
253
278
 
254
279
  sig { params(node: SyntaxTree::Node, statements: SyntaxTree::Statements).void }
255
280
  def add_statements_range(node, statements)
256
- add_lines_range(node.location.start_line, statements.body.last.location.end_line) unless statements.empty?
281
+ return if statements.empty?
282
+
283
+ add_lines_range(node.location.start_line, T.must(statements.body.last).location.end_line)
257
284
  end
258
285
 
259
286
  sig { params(node: SyntaxTree::StringConcat).void }
@@ -268,7 +295,7 @@ module RubyLsp
268
295
  def add_lines_range(start_line, end_line)
269
296
  return if start_line >= end_line
270
297
 
271
- @ranges << LanguageServer::Protocol::Interface::FoldingRange.new(
298
+ @ranges << Interface::FoldingRange.new(
272
299
  start_line: start_line - 1,
273
300
  end_line: end_line - 1,
274
301
  kind: "region",
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "ruby_lsp/requests/support/rubocop_formatting_runner"
5
+ require "ruby_lsp/requests/support/syntax_tree_formatting_runner"
5
6
 
6
7
  module RubyLsp
7
8
  module Requests
@@ -22,14 +23,23 @@ module RubyLsp
22
23
  # ```
23
24
  class Formatting < BaseRequest
24
25
  class Error < StandardError; end
26
+ class InvalidFormatter < StandardError; end
25
27
 
26
28
  extend T::Sig
27
29
 
28
- sig { params(uri: String, document: Document).void }
29
- def initialize(uri, document)
30
+ sig { params(document: Document, formatter: String).void }
31
+ def initialize(document, formatter: "auto")
30
32
  super(document)
31
33
 
32
- @uri = uri
34
+ @uri = T.let(document.uri, String)
35
+ @formatter = T.let(
36
+ if formatter == "auto"
37
+ defined?(Support::RuboCopFormattingRunner) ? "rubocop" : "syntax_tree"
38
+ else
39
+ formatter
40
+ end,
41
+ String,
42
+ )
33
43
  end
34
44
 
35
45
  sig { override.returns(T.nilable(T.all(T::Array[Interface::TextEdit], Object))) }
@@ -60,10 +70,13 @@ module RubyLsp
60
70
 
61
71
  sig { returns(T.nilable(String)) }
62
72
  def formatted_file
63
- if defined?(Support::RuboCopFormattingRunner)
73
+ case @formatter
74
+ when "rubocop"
64
75
  Support::RuboCopFormattingRunner.instance.run(@uri, @document)
76
+ when "syntax_tree"
77
+ Support::SyntaxTreeFormattingRunner.instance.run(@uri, @document)
65
78
  else
66
- SyntaxTree.format(@document.source)
79
+ raise InvalidFormatter, "Unknown formatter: #{@formatter}"
67
80
  end
68
81
  end
69
82
  end
@@ -36,7 +36,7 @@ module RubyLsp
36
36
  @position = T.let(document.create_scanner.find_char_position(position), Integer)
37
37
  end
38
38
 
39
- sig { override.returns(T.nilable(LanguageServer::Protocol::Interface::Hover)) }
39
+ sig { override.returns(T.nilable(Interface::Hover)) }
40
40
  def run
41
41
  return unless @document.parsed?
42
42
 
@@ -48,9 +48,10 @@ module RubyLsp
48
48
  message = target.message
49
49
  generate_rails_document_link_hover(message.value, message)
50
50
  when SyntaxTree::CallNode
51
- return if target.message == :call
51
+ message = target.message
52
+ return if message.is_a?(Symbol)
52
53
 
53
- generate_rails_document_link_hover(target.message.value, target.message)
54
+ generate_rails_document_link_hover(message.value, message)
54
55
  when SyntaxTree::ConstPathRef
55
56
  constant_name = full_constant_name(target)
56
57
  generate_rails_document_link_hover(constant_name, target)
@@ -60,18 +61,18 @@ module RubyLsp
60
61
  private
61
62
 
62
63
  sig do
63
- params(name: String, node: SyntaxTree::Node).returns(T.nilable(LanguageServer::Protocol::Interface::Hover))
64
+ params(name: String, node: SyntaxTree::Node).returns(T.nilable(Interface::Hover))
64
65
  end
65
66
  def generate_rails_document_link_hover(name, node)
66
67
  urls = Support::RailsDocumentClient.generate_rails_document_urls(name)
67
68
 
68
69
  return if urls.empty?
69
70
 
70
- contents = LanguageServer::Protocol::Interface::MarkupContent.new(
71
+ contents = Interface::MarkupContent.new(
71
72
  kind: "markdown",
72
73
  value: urls.join("\n\n"),
73
74
  )
74
- LanguageServer::Protocol::Interface::Hover.new(
75
+ Interface::Hover.new(
75
76
  range: range_from_syntax_tree_node(node),
76
77
  contents: contents,
77
78
  )
@@ -25,11 +25,11 @@ module RubyLsp
25
25
  def initialize(document, range)
26
26
  super(document)
27
27
 
28
- @hints = T.let([], T::Array[LanguageServer::Protocol::Interface::InlayHint])
28
+ @hints = T.let([], T::Array[Interface::InlayHint])
29
29
  @range = range
30
30
  end
31
31
 
32
- sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::InlayHint], Object)) }
32
+ sig { override.returns(T.all(T::Array[Interface::InlayHint], Object)) }
33
33
  def run
34
34
  visit(@document.tree) if @document.parsed?
35
35
  @hints
@@ -37,12 +37,13 @@ module RubyLsp
37
37
 
38
38
  sig { override.params(node: SyntaxTree::Rescue).void }
39
39
  def visit_rescue(node)
40
- return unless node.exception.nil?
40
+ exception = node.exception
41
+ return unless exception.nil? || exception.exceptions.nil?
41
42
 
42
43
  loc = node.location
43
44
  return unless visible?(node, @range)
44
45
 
45
- @hints << LanguageServer::Protocol::Interface::InlayHint.new(
46
+ @hints << Interface::InlayHint.new(
46
47
  position: { line: loc.start_line - 1, character: loc.start_column + RESCUE_STRING_LENGTH },
47
48
  label: "StandardError",
48
49
  padding_left: true,
@@ -63,13 +63,19 @@ module RubyLsp
63
63
 
64
64
  case matched
65
65
  when SyntaxTree::Command, SyntaxTree::CallNode, SyntaxTree::CommandCall
66
- return unless matched.message.value == "require"
66
+ message = matched.message
67
+ return if message.is_a?(Symbol)
68
+ return unless message.value == "require"
67
69
 
68
70
  args = matched.arguments
69
71
  args = args.arguments if args.is_a?(SyntaxTree::ArgParen)
72
+ return if args.nil? || args.is_a?(SyntaxTree::ArgsForward)
70
73
 
71
- path_node = args.parts.first.parts.first
72
- return unless path_node
74
+ argument = args.parts.first
75
+ return unless argument.is_a?(SyntaxTree::StringLiteral)
76
+
77
+ path_node = argument.parts.first
78
+ return unless path_node.is_a?(SyntaxTree::TStringContent)
73
79
  return unless (path_node.location.start_char..path_node.location.end_char).cover?(position)
74
80
 
75
81
  path_node
@@ -100,12 +100,12 @@ module RubyLsp
100
100
  end
101
101
  def create_selection_range(location, parent = nil)
102
102
  RubyLsp::Requests::Support::SelectionRange.new(
103
- range: LanguageServer::Protocol::Interface::Range.new(
104
- start: LanguageServer::Protocol::Interface::Position.new(
103
+ range: Interface::Range.new(
104
+ start: Interface::Position.new(
105
105
  line: location.start_line - 1,
106
106
  character: location.start_column,
107
107
  ),
108
- end: LanguageServer::Protocol::Interface::Position.new(
108
+ end: Interface::Position.new(
109
109
  line: location.end_line - 1,
110
110
  character: location.end_column,
111
111
  ),
@@ -122,7 +122,7 @@ module RubyLsp
122
122
  sig do
123
123
  override.returns(
124
124
  T.any(
125
- LanguageServer::Protocol::Interface::SemanticTokens,
125
+ Interface::SemanticTokens,
126
126
  T.all(T::Array[SemanticToken], Object),
127
127
  ),
128
128
  )
@@ -143,7 +143,7 @@ module RubyLsp
143
143
  visit(node.receiver)
144
144
 
145
145
  message = node.message
146
- if message != :call && !special_method?(message.value)
146
+ if !message.is_a?(Symbol) && !special_method?(message.value)
147
147
  type = Support::Sorbet.annotation?(node) ? :type : :method
148
148
 
149
149
  add_token(message.location, type)
@@ -160,6 +160,7 @@ module RubyLsp
160
160
  add_token(node.message.location, :method)
161
161
  end
162
162
  visit(node.arguments)
163
+ visit(node.block)
163
164
  end
164
165
 
165
166
  sig { override.params(node: SyntaxTree::CommandCall).void }
@@ -167,8 +168,10 @@ module RubyLsp
167
168
  return super unless visible?(node, @range)
168
169
 
169
170
  visit(node.receiver)
170
- add_token(node.message.location, :method)
171
+ message = node.message
172
+ add_token(message.location, :method) unless message.is_a?(Symbol)
171
173
  visit(node.arguments)
174
+ visit(node.block)
172
175
  end
173
176
 
174
177
  sig { override.params(node: SyntaxTree::Const).void }
@@ -203,7 +206,7 @@ module RubyLsp
203
206
  def visit_params(node)
204
207
  return super unless visible?(node, @range)
205
208
 
206
- node.keywords.each do |keyword,|
209
+ node.keywords.each do |keyword, *|
207
210
  location = keyword.location
208
211
  add_token(location_without_colon(location), :parameter)
209
212
  end
@@ -213,7 +216,7 @@ module RubyLsp
213
216
  end
214
217
 
215
218
  rest = node.keyword_rest
216
- if rest && !rest.is_a?(SyntaxTree::ArgsForward)
219
+ if rest && !rest.is_a?(SyntaxTree::ArgsForward) && !rest.is_a?(Symbol)
217
220
  name = rest.name
218
221
  add_token(name.location, :parameter) if name
219
222
  end
@@ -240,9 +243,13 @@ module RubyLsp
240
243
  when SyntaxTree::Ident
241
244
  type = type_for_local(value)
242
245
  add_token(value.location, type)
246
+ when Symbol
247
+ # do nothing
243
248
  else
244
249
  visit(value)
245
250
  end
251
+
252
+ super
246
253
  end
247
254
 
248
255
  sig { override.params(node: SyntaxTree::VarRef).void }
@@ -255,27 +262,84 @@ module RubyLsp
255
262
  when SyntaxTree::Ident
256
263
  type = type_for_local(value)
257
264
  add_token(value.location, type)
265
+ when Symbol
266
+ # do nothing
258
267
  else
259
268
  visit(value)
260
269
  end
261
270
  end
262
271
 
272
+ # All block locals are variables. E.g.: [].each do |x; block_local|
273
+ sig { override.params(node: SyntaxTree::BlockVar).void }
274
+ def visit_block_var(node)
275
+ node.locals.each { |local| add_token(local.location, :variable) }
276
+ super
277
+ end
278
+
279
+ # All lambda locals are variables. E.g.: ->(x; lambda_local) {}
280
+ sig { override.params(node: SyntaxTree::LambdaVar).void }
281
+ def visit_lambda_var(node)
282
+ node.locals.each { |local| add_token(local.location, :variable) }
283
+ super
284
+ end
285
+
263
286
  sig { override.params(node: SyntaxTree::VCall).void }
264
287
  def visit_vcall(node)
265
288
  return super unless visible?(node, @range)
266
289
 
267
- return if special_method?(node.value.value)
290
+ # A VCall may exist as a local in the current_scope. This happens when used named capture groups in a regexp
291
+ ident = node.value
292
+ value = ident.value
293
+ local = current_scope.find_local(value)
294
+ return if local.nil? && special_method?(value)
295
+
296
+ type = if local
297
+ :variable
298
+ elsif Support::Sorbet.annotation?(node)
299
+ :type
300
+ else
301
+ :method
302
+ end
268
303
 
269
- type = Support::Sorbet.annotation?(node) ? :type : :method
270
304
  add_token(node.value.location, type)
271
305
  end
272
306
 
307
+ sig { override.params(node: SyntaxTree::Binary).void }
308
+ def visit_binary(node)
309
+ # It's important to visit the regexp first in the WithScope module
310
+ super
311
+
312
+ # You can only capture local variables with regexp by using the =~ operator
313
+ return unless node.operator == :=~
314
+
315
+ left = node.left
316
+ # The regexp needs to be on the left hand side of the =~ for local variable capture
317
+ return unless left.is_a?(SyntaxTree::RegexpLiteral)
318
+
319
+ parts = left.parts
320
+ return unless parts.one?
321
+
322
+ content = parts.first
323
+ return unless content.is_a?(SyntaxTree::TStringContent)
324
+
325
+ # For each capture name we find in the regexp, look for a local in the current_scope
326
+ Regexp.new(content.value, Regexp::FIXEDENCODING).names.each do |name|
327
+ local = current_scope.find_local(name)
328
+ next unless local
329
+
330
+ local.definitions.each { |definition| add_token(definition, :variable) }
331
+ end
332
+ end
333
+
273
334
  sig { override.params(node: SyntaxTree::ClassDeclaration).void }
274
335
  def visit_class(node)
275
336
  return super unless visible?(node, @range)
276
337
 
277
338
  add_token(node.constant.location, :class, [:declaration])
278
- add_token(node.superclass.location, :class) if node.superclass
339
+
340
+ superclass = node.superclass
341
+ add_token(superclass.location, :class) if superclass
342
+
279
343
  visit(node.bodystmt)
280
344
  end
281
345
 
@@ -7,8 +7,8 @@ module RubyLsp
7
7
  class HighlightTarget
8
8
  extend T::Sig
9
9
 
10
- READ = LanguageServer::Protocol::Constant::DocumentHighlightKind::READ
11
- WRITE = LanguageServer::Protocol::Constant::DocumentHighlightKind::WRITE
10
+ READ = Constant::DocumentHighlightKind::READ
11
+ WRITE = Constant::DocumentHighlightKind::WRITE
12
12
 
13
13
  class HighlightMatch
14
14
  extend T::Sig
@@ -84,10 +84,11 @@ module RubyLsp
84
84
  SyntaxTree::KwRestParam, SyntaxTree::BlockArg
85
85
  node.name&.value
86
86
  when SyntaxTree::VarField, SyntaxTree::VarRef, SyntaxTree::VCall
87
- node.value&.value
87
+ value = node.value
88
+ value.value unless value.nil? || value.is_a?(Symbol)
88
89
  when SyntaxTree::CallNode, SyntaxTree::Command, SyntaxTree::CommandCall
89
90
  message = node.message
90
- message != :call ? message.value : nil
91
+ message.value unless message.is_a?(Symbol)
91
92
  when SyntaxTree::ClassDeclaration, SyntaxTree::ModuleDeclaration
92
93
  node.constant.constant.value
93
94
  end