ruby-lsp 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8f2f2aa1083dcc700e709ebd0bc3d3aed8f08d80943595ba3c9df332d806709
4
- data.tar.gz: a065d2f6be6539c81ad117bfba7c7b4f90ead0bbce37dd936733218e3135bc67
3
+ metadata.gz: 4d7205054d59bb5a292bc8e2d87a984663ce1f0dac527d6d69d3940bc6e86735
4
+ data.tar.gz: 9b760d79ca5406fe3e78e4bd1276cb507464d72d2fc2e68cf5abf1f9920df1a6
5
5
  SHA512:
6
- metadata.gz: de010f852e08fd72ac60a12cbb9a8666a92945c49b8a14214f0e347c9a6fbacb65770322627db22a30b573ee61f34ec18be56d625768a6c954cf447dbda63368
7
- data.tar.gz: 3f47e62896899091c4a9b166937720cc535f46004827a098bfb7eaf9c31fb39bb35261077b91a01a41baa18a50482d8ffe1b8ebddabaed1ea15bc0d023e76dae
6
+ metadata.gz: bf465a2d9af7e109b21ecd861cb88b556ee872e4d46e292e791f8f85ce131af7791254187bcc68216b9df0f1276296b5cf80e78be7193c11fdf16831b8da4f0f
7
+ data.tar.gz: 57fa298a96b7cd3eeb2be68d21261ae634f8e70d818a5c68b2e7cdcfa464d7d27b16e90aecbfb8192a4a3ff9fecc4fa8b94baa3b3b51d9c7c887f4fba2b07bd5
data/README.md CHANGED
@@ -39,10 +39,14 @@ end
39
39
  See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth details about the
40
40
  [supported features](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
41
41
 
42
+ For creating rich themes for Ruby using the semantic highlighting information, see the [semantic highlighting
43
+ documentation](SEMANTIC_HIGHLIGHTING.md).
44
+
42
45
  ## Learn More
43
46
 
44
47
  * [RubyConf 2022: Improving the development experience with language servers](https://www.youtube.com/watch?v=kEfXPTm1aCI) ([Vinicius Stock](https://github.com/vinistock))
45
48
  * [Remote Ruby: Ruby Language Server with Vinicius Stock](https://remoteruby.com/221)
49
+ * [RubyKaigi 2023: Code indexing - How language servers understand our code](https://www.youtube.com/watch?v=ks3tQojSJLU) ([Vinicius Stock](https://github.com/vinistock))
46
50
 
47
51
  ## Contributing
48
52
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.6.0
data/exe/ruby-lsp CHANGED
@@ -20,7 +20,39 @@ end
20
20
 
21
21
  require_relative "../lib/ruby_lsp/internal"
22
22
 
23
- if ARGV.include?("--debug")
23
+ require "optparse"
24
+
25
+ options = {}
26
+ parser = OptionParser.new do |opts|
27
+ opts.banner = "Usage: ruby-lsp [options]"
28
+
29
+ opts.on("--version", "Print ruby-lsp version") do
30
+ puts RubyLsp::VERSION
31
+ exit(0)
32
+ end
33
+
34
+ opts.on("--debug", "Launch the Ruby LSP with a debugger attached") do
35
+ options[:debug] = true
36
+ end
37
+
38
+ opts.on("-h", "--help", "Print this help") do
39
+ puts opts.help
40
+ puts
41
+ puts "See https://shopify.github.io/ruby-lsp/ for more information"
42
+ exit(0)
43
+ end
44
+ end
45
+
46
+ begin
47
+ parser.parse!
48
+ rescue OptionParser::InvalidOption => e
49
+ warn(e)
50
+ warn("")
51
+ warn(parser.help)
52
+ exit(1)
53
+ end
54
+
55
+ if options[:debug]
24
56
  if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
25
57
  puts "Debugging is not supported on Windows"
26
58
  exit 1
@@ -42,7 +42,7 @@ module RubyLsp
42
42
  # Find all classes that inherit from BaseRequest or Listener, which are the ones we want to make sure are
43
43
  # documented
44
44
  features = ObjectSpace.each_object(Class).filter_map do |k|
45
- klass = T.cast(k, Class)
45
+ klass = T.cast(k, T::Class[T.anything])
46
46
  klass if klass < RubyLsp::Requests::BaseRequest || klass < RubyLsp::Listener
47
47
  end
48
48
 
@@ -20,6 +20,7 @@ module RubyLsp
20
20
  # ```
21
21
  class EventEmitter < SyntaxTree::Visitor
22
22
  extend T::Sig
23
+ include SyntaxTree::WithScope
23
24
 
24
25
  sig { void }
25
26
  def initialize
@@ -73,6 +74,12 @@ module RubyLsp
73
74
  @listeners[:after_command]&.each { |l| T.unsafe(l).after_command(node) }
74
75
  end
75
76
 
77
+ sig { override.params(node: SyntaxTree::CommandCall).void }
78
+ def visit_command_call(node)
79
+ @listeners[:on_command_call]&.each { |l| T.unsafe(l).on_command_call(node) }
80
+ super
81
+ end
82
+
76
83
  sig { override.params(node: SyntaxTree::CallNode).void }
77
84
  def visit_call(node)
78
85
  @listeners[:on_call]&.each { |l| T.unsafe(l).on_call(node) }
@@ -116,5 +123,59 @@ module RubyLsp
116
123
  @listeners[:on_comment]&.each { |l| T.unsafe(l).on_comment(node) }
117
124
  super
118
125
  end
126
+
127
+ sig { override.params(node: SyntaxTree::Rescue).void }
128
+ def visit_rescue(node)
129
+ @listeners[:on_rescue]&.each { |l| T.unsafe(l).on_rescue(node) }
130
+ super
131
+ end
132
+
133
+ sig { override.params(node: SyntaxTree::Kw).void }
134
+ def visit_kw(node)
135
+ @listeners[:on_kw]&.each { |l| T.unsafe(l).on_kw(node) }
136
+ super
137
+ end
138
+
139
+ sig { override.params(node: SyntaxTree::Params).void }
140
+ def visit_params(node)
141
+ @listeners[:on_params]&.each { |l| T.unsafe(l).on_params(node) }
142
+ super
143
+ end
144
+
145
+ sig { override.params(node: SyntaxTree::Field).void }
146
+ def visit_field(node)
147
+ @listeners[:on_field]&.each { |l| T.unsafe(l).on_field(node) }
148
+ super
149
+ end
150
+
151
+ sig { override.params(node: SyntaxTree::VarRef).void }
152
+ def visit_var_ref(node)
153
+ @listeners[:on_var_ref]&.each { |l| T.unsafe(l).on_var_ref(node) }
154
+ super
155
+ end
156
+
157
+ sig { override.params(node: SyntaxTree::BlockVar).void }
158
+ def visit_block_var(node)
159
+ @listeners[:on_block_var]&.each { |l| T.unsafe(l).on_block_var(node) }
160
+ super
161
+ end
162
+
163
+ sig { override.params(node: SyntaxTree::LambdaVar).void }
164
+ def visit_lambda_var(node)
165
+ @listeners[:on_lambda_var]&.each { |l| T.unsafe(l).on_lambda_var(node) }
166
+ super
167
+ end
168
+
169
+ sig { override.params(node: SyntaxTree::Binary).void }
170
+ def visit_binary(node)
171
+ super
172
+ @listeners[:after_binary]&.each { |l| T.unsafe(l).after_binary(node) }
173
+ end
174
+
175
+ sig { override.params(node: SyntaxTree::Const).void }
176
+ def visit_const(node)
177
+ @listeners[:on_const]&.each { |l| T.unsafe(l).on_const(node) }
178
+ super
179
+ end
119
180
  end
120
181
  end
@@ -1,6 +1,8 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "ruby_lsp/requests/support/dependency_detector"
5
+
4
6
  module RubyLsp
5
7
  # This class dispatches a request execution to the right request class. No IO should happen anywhere here!
6
8
  class Executor
@@ -11,6 +13,7 @@ module RubyLsp
11
13
  # Requests that mutate the store must be run sequentially! Parallel requests only receive a temporary copy of the
12
14
  # store
13
15
  @store = store
16
+ @test_library = T.let(DependencyDetector.detected_test_library, String)
14
17
  @message_queue = message_queue
15
18
  end
16
19
 
@@ -81,7 +84,8 @@ module RubyLsp
81
84
  folding_range(uri)
82
85
  when "textDocument/selectionRange"
83
86
  selection_range(uri, request.dig(:params, :positions))
84
- when "textDocument/documentSymbol", "textDocument/documentLink", "textDocument/codeLens"
87
+ when "textDocument/documentSymbol", "textDocument/documentLink", "textDocument/codeLens",
88
+ "textDocument/semanticTokens/full"
85
89
  document = @store.get(uri)
86
90
 
87
91
  # If the response has already been cached by another request, return it
@@ -92,10 +96,11 @@ module RubyLsp
92
96
  emitter = EventEmitter.new
93
97
  document_symbol = Requests::DocumentSymbol.new(emitter, @message_queue)
94
98
  document_link = Requests::DocumentLink.new(uri, emitter, @message_queue)
95
- code_lens = Requests::CodeLens.new(uri, emitter, @message_queue)
99
+ code_lens = Requests::CodeLens.new(uri, emitter, @message_queue, @test_library)
96
100
  code_lens_extensions_listeners = Requests::CodeLens.listeners.map do |l|
97
101
  T.unsafe(l).new(document.uri, emitter, @message_queue)
98
102
  end
103
+ semantic_highlighting = Requests::SemanticHighlighting.new(emitter, @message_queue)
99
104
  emitter.visit(document.tree) if document.parsed?
100
105
 
101
106
  code_lens_extensions_listeners.each { |ext| code_lens.merge_response!(ext) }
@@ -105,9 +110,11 @@ module RubyLsp
105
110
  document.cache_set("textDocument/documentSymbol", document_symbol.response)
106
111
  document.cache_set("textDocument/documentLink", document_link.response)
107
112
  document.cache_set("textDocument/codeLens", code_lens.response)
113
+ document.cache_set(
114
+ "textDocument/semanticTokens/full",
115
+ Requests::Support::SemanticTokenEncoder.new.encode(semantic_highlighting.response),
116
+ )
108
117
  document.cache_get(request[:method])
109
- when "textDocument/semanticTokens/full"
110
- semantic_tokens_full(uri)
111
118
  when "textDocument/semanticTokens/range"
112
119
  semantic_tokens_range(uri, request.dig(:params, :range))
113
120
  when "textDocument/formatting"
@@ -245,19 +252,6 @@ module RubyLsp
245
252
  end
246
253
  end
247
254
 
248
- sig { params(uri: String).returns(Interface::SemanticTokens) }
249
- def semantic_tokens_full(uri)
250
- @store.cache_fetch(uri, "textDocument/semanticTokens/full") do |document|
251
- T.cast(
252
- Requests::SemanticHighlighting.new(
253
- document,
254
- encoder: Requests::Support::SemanticTokenEncoder.new,
255
- ).run,
256
- Interface::SemanticTokens,
257
- )
258
- end
259
- end
260
-
261
255
  sig { params(uri: String).returns(T.nilable(T::Array[Interface::TextEdit])) }
262
256
  def formatting(uri)
263
257
  # If formatter is set to `auto` but no supported formatting gem is found, don't attempt to format
@@ -287,13 +281,18 @@ module RubyLsp
287
281
  Requests::DocumentHighlight.new(@store.get(uri), position).run
288
282
  end
289
283
 
290
- sig { params(uri: String, range: Document::RangeShape).returns(T::Array[Interface::InlayHint]) }
284
+ sig { params(uri: String, range: Document::RangeShape).returns(T.nilable(T::Array[Interface::InlayHint])) }
291
285
  def inlay_hint(uri, range)
292
286
  document = @store.get(uri)
287
+ return if document.syntax_error?
288
+
293
289
  start_line = range.dig(:start, :line)
294
290
  end_line = range.dig(:end, :line)
295
291
 
296
- Requests::InlayHints.new(document, start_line..end_line).run
292
+ emitter = EventEmitter.new
293
+ listener = Requests::InlayHints.new(start_line..end_line, emitter, @message_queue)
294
+ emitter.visit(document.tree)
295
+ listener.response
297
296
  end
298
297
 
299
298
  sig do
@@ -354,14 +353,15 @@ module RubyLsp
354
353
  start_line = range.dig(:start, :line)
355
354
  end_line = range.dig(:end, :line)
356
355
 
357
- T.cast(
358
- Requests::SemanticHighlighting.new(
359
- document,
360
- range: start_line..end_line,
361
- encoder: Requests::Support::SemanticTokenEncoder.new,
362
- ).run,
363
- Interface::SemanticTokens,
356
+ emitter = EventEmitter.new
357
+ listener = Requests::SemanticHighlighting.new(
358
+ emitter,
359
+ @message_queue,
360
+ range: start_line..end_line,
364
361
  )
362
+ emitter.visit(document.tree) if document.parsed?
363
+
364
+ Requests::Support::SemanticTokenEncoder.new.encode(listener.response)
365
365
  end
366
366
 
367
367
  sig do
@@ -421,9 +421,9 @@ module RubyLsp
421
421
  encodings.first
422
422
  end
423
423
 
424
- formatter = options.dig(:initializationOptions, :formatter)
424
+ formatter = options.dig(:initializationOptions, :formatter) || "auto"
425
425
  @store.formatter = if formatter == "auto"
426
- detected_formatter
426
+ DependencyDetector.detected_formatter
427
427
  else
428
428
  formatter
429
429
  end
@@ -536,23 +536,6 @@ module RubyLsp
536
536
  )
537
537
  end
538
538
 
539
- sig { returns(String) }
540
- def detected_formatter
541
- # NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
542
- if direct_dependency?(/^rubocop/)
543
- "rubocop"
544
- elsif direct_dependency?(/^syntax_tree$/)
545
- "syntax_tree"
546
- else
547
- "none"
548
- end
549
- end
550
-
551
- sig { params(gem_pattern: Regexp).returns(T::Boolean) }
552
- def direct_dependency?(gem_pattern)
553
- Bundler.locked_gems.dependencies.keys.grep(gem_pattern).any?
554
- end
555
-
556
539
  sig { void }
557
540
  def check_formatter_is_available
558
541
  # Warn of an unavailable `formatter` setting, e.g. `rubocop` on a project which doesn't have RuboCop.
@@ -560,11 +543,13 @@ module RubyLsp
560
543
  return unless @store.formatter == "rubocop"
561
544
 
562
545
  unless defined?(RubyLsp::Requests::Support::RuboCopRunner)
546
+ @store.formatter = "none"
547
+
563
548
  @message_queue << Notification.new(
564
549
  message: "window/showMessage",
565
550
  params: Interface::ShowMessageParams.new(
566
551
  type: Constant::MessageType::ERROR,
567
- message: "Ruby LSP formatter is set to `rubocop` but RuboCop was not found in the bundle.",
552
+ message: "Ruby LSP formatter is set to `rubocop` but RuboCop was not found in the Gemfile or gemspec.",
568
553
  ),
569
554
  )
570
555
  end
@@ -6,6 +6,7 @@ require "syntax_tree"
6
6
  require "language_server-protocol"
7
7
  require "benchmark"
8
8
  require "bundler"
9
+ require "uri"
9
10
 
10
11
  require "ruby-lsp"
11
12
  require "ruby_lsp/utils"
@@ -11,12 +11,8 @@ module RubyLsp
11
11
 
12
12
  abstract!
13
13
 
14
- # We must accept rest keyword arguments here, so that the argument count matches when
15
- # SyntaxTree::WithScope#initialize invokes `super` for Sorbet. We don't actually use these parameters for
16
- # anything. We can remove these arguments once we drop support for Ruby 2.7
17
- # https://github.com/ruby-syntax-tree/syntax_tree/blob/4dac90b53df388f726dce50ce638a1ba71cc59f8/lib/syntax_tree/with_scope.rb#L122
18
- sig { params(document: Document, _kwargs: T.untyped).void }
19
- def initialize(document, **_kwargs)
14
+ sig { params(document: Document).void }
15
+ def initialize(document)
20
16
  @document = document
21
17
  super()
22
18
  end
@@ -1,6 +1,8 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "shellwords"
5
+
4
6
  module RubyLsp
5
7
  module Requests
6
8
  # ![Code lens demo](../../code_lens.gif)
@@ -31,35 +33,63 @@ module RubyLsp
31
33
  sig { override.returns(ResponseType) }
32
34
  attr_reader :response
33
35
 
34
- sig { params(uri: String, emitter: EventEmitter, message_queue: Thread::Queue).void }
35
- def initialize(uri, emitter, message_queue)
36
+ sig { params(uri: String, emitter: EventEmitter, message_queue: Thread::Queue, test_library: String).void }
37
+ def initialize(uri, emitter, message_queue, test_library)
36
38
  super(emitter, message_queue)
37
39
 
40
+ @test_library = T.let(test_library, String)
38
41
  @response = T.let([], ResponseType)
39
42
  @path = T.let(T.must(URI(uri).path), String)
40
- @visibility = T.let("public", String)
41
- @prev_visibility = T.let("public", String)
42
-
43
- emitter.register(self, :on_class, :on_def, :on_command, :after_command, :on_call, :after_call, :on_vcall)
43
+ # visibility_stack is a stack of [current_visibility, previous_visibility]
44
+ @visibility_stack = T.let([["public", "public"]], T::Array[T::Array[T.nilable(String)]])
45
+ @class_stack = T.let([], T::Array[String])
46
+
47
+ emitter.register(
48
+ self,
49
+ :on_class,
50
+ :after_class,
51
+ :on_def,
52
+ :on_command,
53
+ :after_command,
54
+ :on_call,
55
+ :after_call,
56
+ :on_vcall,
57
+ )
44
58
  end
45
59
 
46
60
  sig { params(node: SyntaxTree::ClassDeclaration).void }
47
61
  def on_class(node)
62
+ @visibility_stack.push(["public", "public"])
48
63
  class_name = node.constant.constant.value
49
64
  if class_name.end_with?("Test")
50
- add_code_lens(node, name: class_name, command: BASE_COMMAND + @path)
65
+ @class_stack.push(class_name)
66
+ add_test_code_lens(
67
+ node,
68
+ name: class_name,
69
+ command: generate_test_command(class_name: class_name),
70
+ kind: :group,
71
+ )
51
72
  end
52
73
  end
53
74
 
75
+ sig { params(node: SyntaxTree::ClassDeclaration).void }
76
+ def after_class(node)
77
+ @visibility_stack.pop
78
+ @class_stack.pop
79
+ end
80
+
54
81
  sig { params(node: SyntaxTree::DefNode).void }
55
82
  def on_def(node)
56
- if @visibility == "public"
83
+ visibility, _ = @visibility_stack.last
84
+ if visibility == "public"
57
85
  method_name = node.name.value
58
86
  if method_name.start_with?("test_")
59
- add_code_lens(
87
+ class_name = T.must(@class_stack.last)
88
+ add_test_code_lens(
60
89
  node,
61
90
  name: method_name,
62
- command: BASE_COMMAND + @path + " --name " + method_name,
91
+ command: generate_test_command(method_name: method_name, class_name: class_name),
92
+ kind: :example,
63
93
  )
64
94
  end
65
95
  end
@@ -67,15 +97,22 @@ module RubyLsp
67
97
 
68
98
  sig { params(node: SyntaxTree::Command).void }
69
99
  def on_command(node)
70
- if ACCESS_MODIFIERS.include?(node.message.value) && node.arguments.parts.any?
71
- @prev_visibility = @visibility
72
- @visibility = node.message.value
100
+ node_message = node.message.value
101
+ if ACCESS_MODIFIERS.include?(node_message) && node.arguments.parts.any?
102
+ visibility, _ = @visibility_stack.pop
103
+ @visibility_stack.push([node_message, visibility])
104
+ elsif @path.include?("Gemfile") && node_message.include?("gem") && node.arguments.parts.any?
105
+ remote = resolve_gem_remote(node)
106
+ return unless remote
107
+
108
+ add_open_gem_remote_code_lens(node, remote)
73
109
  end
74
110
  end
75
111
 
76
112
  sig { params(node: SyntaxTree::Command).void }
77
113
  def after_command(node)
78
- @visibility = @prev_visibility
114
+ _, prev_visibility = @visibility_stack.pop
115
+ @visibility_stack.push([prev_visibility, prev_visibility])
79
116
  end
80
117
 
81
118
  sig { params(node: SyntaxTree::CallNode).void }
@@ -85,15 +122,16 @@ module RubyLsp
85
122
  if ident
86
123
  ident_value = T.cast(ident, SyntaxTree::Ident).value
87
124
  if ACCESS_MODIFIERS.include?(ident_value)
88
- @prev_visibility = @visibility
89
- @visibility = ident_value
125
+ visibility, _ = @visibility_stack.pop
126
+ @visibility_stack.push([ident_value, visibility])
90
127
  end
91
128
  end
92
129
  end
93
130
 
94
131
  sig { params(node: SyntaxTree::CallNode).void }
95
132
  def after_call(node)
96
- @visibility = @prev_visibility
133
+ _, prev_visibility = @visibility_stack.pop
134
+ @visibility_stack.push([prev_visibility, prev_visibility])
97
135
  end
98
136
 
99
137
  sig { params(node: SyntaxTree::VCall).void }
@@ -101,8 +139,8 @@ module RubyLsp
101
139
  vcall_value = node.value.value
102
140
 
103
141
  if ACCESS_MODIFIERS.include?(vcall_value)
104
- @prev_visibility = vcall_value
105
- @visibility = vcall_value
142
+ @visibility_stack.pop
143
+ @visibility_stack.push([vcall_value, vcall_value])
106
144
  end
107
145
  end
108
146
 
@@ -114,36 +152,86 @@ module RubyLsp
114
152
 
115
153
  private
116
154
 
117
- sig { params(node: SyntaxTree::Node, name: String, command: String).void }
118
- def add_code_lens(node, name:, command:)
155
+ sig { params(node: SyntaxTree::Node, name: String, command: String, kind: Symbol).void }
156
+ def add_test_code_lens(node, name:, command:, kind:)
157
+ arguments = [
158
+ @path,
159
+ name,
160
+ command,
161
+ {
162
+ start_line: node.location.start_line - 1,
163
+ start_column: node.location.start_column,
164
+ end_line: node.location.end_line - 1,
165
+ end_column: node.location.end_column,
166
+ },
167
+ ]
168
+
119
169
  @response << create_code_lens(
120
170
  node,
121
171
  title: "Run",
122
172
  command_name: "rubyLsp.runTest",
123
- path: @path,
124
- name: name,
125
- test_command: command,
126
- type: "test",
173
+ arguments: arguments,
174
+ data: { type: "test", kind: kind },
127
175
  )
128
176
 
129
177
  @response << create_code_lens(
130
178
  node,
131
179
  title: "Run In Terminal",
132
180
  command_name: "rubyLsp.runTestInTerminal",
133
- path: @path,
134
- name: name,
135
- test_command: command,
136
- type: "test_in_terminal",
181
+ arguments: arguments,
182
+ data: { type: "test_in_terminal", kind: kind },
137
183
  )
138
184
 
139
185
  @response << create_code_lens(
140
186
  node,
141
187
  title: "Debug",
142
188
  command_name: "rubyLsp.debugTest",
143
- path: @path,
144
- name: name,
145
- test_command: command,
146
- type: "debug",
189
+ arguments: arguments,
190
+ data: { type: "debug", kind: kind },
191
+ )
192
+ end
193
+
194
+ sig { params(node: SyntaxTree::Command).returns(T.nilable(String)) }
195
+ def resolve_gem_remote(node)
196
+ gem_name = node.arguments.parts.flat_map(&:child_nodes).first.value
197
+ spec = Gem::Specification.stubs.find { |gem| gem.name == gem_name }&.to_spec
198
+ return if spec.nil?
199
+
200
+ [spec.homepage, spec.metadata["source_code_uri"]].compact.find do |page|
201
+ page.start_with?("https://github.com", "https://gitlab.com")
202
+ end
203
+ end
204
+
205
+ sig { params(class_name: String, method_name: T.nilable(String)).returns(String) }
206
+ def generate_test_command(class_name:, method_name: nil)
207
+ command = BASE_COMMAND + @path
208
+
209
+ case @test_library
210
+ when "minitest"
211
+ command += if method_name
212
+ " --name " + "/#{Shellwords.escape(class_name + "#" + method_name)}/"
213
+ else
214
+ " --name " + "/#{Shellwords.escape(class_name)}/"
215
+ end
216
+ when "test-unit"
217
+ command += " --testcase " + "/#{Shellwords.escape(class_name)}/"
218
+
219
+ if method_name
220
+ command += " --name " + Shellwords.escape(method_name)
221
+ end
222
+ end
223
+
224
+ command
225
+ end
226
+
227
+ sig { params(node: SyntaxTree::Command, remote: String).void }
228
+ def add_open_gem_remote_code_lens(node, remote)
229
+ @response << create_code_lens(
230
+ node,
231
+ title: "Open remote",
232
+ command_name: "rubyLsp.openLink",
233
+ arguments: [remote],
234
+ data: { type: "link" },
147
235
  )
148
236
  end
149
237
  end
@@ -30,7 +30,9 @@ module RubyLsp
30
30
 
31
31
  sig { override.returns(T.nilable(T.all(T::Array[Support::RuboCopDiagnostic], Object))) }
32
32
  def run
33
+ # Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid
33
34
  return if @document.syntax_error?
35
+
34
36
  return unless defined?(Support::RuboCopDiagnosticsRunner)
35
37
 
36
38
  # Don't try to run RuboCop diagnostics for files outside the current working directory
@@ -27,7 +27,6 @@ module RubyLsp
27
27
  SyntaxTree::For,
28
28
  SyntaxTree::HashLiteral,
29
29
  SyntaxTree::Heredoc,
30
- SyntaxTree::IfNode,
31
30
  SyntaxTree::ModuleDeclaration,
32
31
  SyntaxTree::SClass,
33
32
  SyntaxTree::UnlessNode,
@@ -42,6 +41,7 @@ module RubyLsp
42
41
 
43
42
  NODES_WITH_STATEMENTS = T.let(
44
43
  [
44
+ SyntaxTree::IfNode,
45
45
  SyntaxTree::Elsif,
46
46
  SyntaxTree::In,
47
47
  SyntaxTree::Rescue,
@@ -52,6 +52,7 @@ module RubyLsp
52
52
 
53
53
  StatementNode = T.type_alias do
54
54
  T.any(
55
+ SyntaxTree::IfNode,
55
56
  SyntaxTree::Elsif,
56
57
  SyntaxTree::In,
57
58
  SyntaxTree::Rescue,