ruby-lsp 0.5.1 → 0.6.1

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: eadf22b9ba9191df338c0da93b3ce6b98ddf61a4831cf204f79ad7c636695fcd
4
- data.tar.gz: c0f7fc6ec73638ffc2075e86e40925a14a2e629d9ee79e10a0ed5039f9fee3f2
3
+ metadata.gz: 11dd3401d408ed456da9b4dd0aca906b991a45a0103ca8786eafbf34d4659c85
4
+ data.tar.gz: 8469dd62a3660965daee589835e1481fe09e17b55cbd9d61c461d4ba94db833e
5
5
  SHA512:
6
- metadata.gz: b3ceb8e427dabbb4819d105b29a82fb719c61c4d2bd69b48c215b7010b6ad79f06bf71f57dc936f8ef74440fd0678b5e3640880008e8d76fe3be50091b8e0da1
7
- data.tar.gz: 855d9cff0ee7a3db7fe2699b08fe8d3cb7fc7edb39448fb9821ab58b4c234763f2489388a995de216e90f05ecbc4e4b22b7ef23b266bdff9654efbc0a1dc6bcd
6
+ metadata.gz: 6d2e0c3d9c5bf11068b1d5e3092d4b5f3b233066a33b7e74ea218f115fbb8b154798a8b8b0634569781245f1dedcb6da3e5ca3a23db04f78fc105c496d2a3280
7
+ data.tar.gz: 529ce841833605ac40c67536bbca1eb1a9568b742de854f4698ced147813ec63569fd926d4035d518f7bb0ab54d2bbd4665e7ad2564923b49036573a0d4625e7
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.1
1
+ 0.6.1
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
@@ -6,23 +6,26 @@ require "objspace"
6
6
 
7
7
  module RubyLsp
8
8
  # This rake task checks that all requests or extensions are fully documented. Add the rake task to your Rakefile and
9
- # specify the absolute path for all files that must be required in order to discover all listeners
9
+ # specify the absolute path for all files that must be required in order to discover all listeners and their
10
+ # related GIFs
10
11
  #
11
12
  # # Rakefile
12
13
  # request_files = FileList.new("#{__dir__}/lib/ruby_lsp/requests/*.rb") do |fl|
13
14
  # fl.exclude(/base_request\.rb/)
14
15
  # end
15
- # RubyLsp::CheckDocs.new(request_files)
16
+ # gif_files = FileList.new("#{__dir__}/**/*.gif")
17
+ # RubyLsp::CheckDocs.new(request_files, gif_files)
16
18
  # # Run with bundle exec rake ruby_lsp:check_docs
17
19
  class CheckDocs < Rake::TaskLib
18
20
  extend T::Sig
19
21
 
20
- sig { params(require_files: Rake::FileList).void }
21
- def initialize(require_files)
22
+ sig { params(require_files: Rake::FileList, gif_files: Rake::FileList).void }
23
+ def initialize(require_files, gif_files)
22
24
  super()
23
25
 
24
26
  @name = T.let("ruby_lsp:check_docs", String)
25
27
  @file_list = require_files
28
+ @gif_list = gif_files
26
29
  define_task
27
30
  end
28
31
 
@@ -34,6 +37,13 @@ module RubyLsp
34
37
  task(@name) { run_task }
35
38
  end
36
39
 
40
+ sig { params(request_path: String).returns(T::Boolean) }
41
+ def gif_exists?(request_path)
42
+ request_gif = request_path.gsub(".rb", ".gif").split("/").last
43
+
44
+ @gif_list.any? { |gif_path| gif_path.end_with?(request_gif) }
45
+ end
46
+
37
47
  sig { void }
38
48
  def run_task
39
49
  # Require all files configured to make sure all listeners are loaded
@@ -42,7 +52,7 @@ module RubyLsp
42
52
  # Find all classes that inherit from BaseRequest or Listener, which are the ones we want to make sure are
43
53
  # documented
44
54
  features = ObjectSpace.each_object(Class).filter_map do |k|
45
- klass = T.cast(k, Class)
55
+ klass = T.cast(k, T::Class[T.anything])
46
56
  klass if klass < RubyLsp::Requests::BaseRequest || klass < RubyLsp::Listener
47
57
  end
48
58
 
@@ -93,6 +103,14 @@ module RubyLsp
93
103
 
94
104
  # [Inlay hint demo](../../inlay_hint.gif)
95
105
  DOCS
106
+ elsif !gif_exists?(file_path)
107
+ T.must(missing_docs[class_name]) << <<~DOCS
108
+ The GIF for the request documentation does not exist. Make sure to add it,
109
+ with the same naming as the request. For example:
110
+
111
+ # lib/ruby_lsp/requests/code_lens.rb
112
+ # foo/bar/code_lens.gif
113
+ DOCS
96
114
  end
97
115
  end
98
116
 
@@ -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
@@ -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,65 @@ 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
+ class_name = @class_stack.last
84
+ return unless class_name
85
+
86
+ visibility, _ = @visibility_stack.last
87
+ if visibility == "public"
57
88
  method_name = node.name.value
58
89
  if method_name.start_with?("test_")
59
- add_code_lens(
90
+ add_test_code_lens(
60
91
  node,
61
92
  name: method_name,
62
- command: BASE_COMMAND + @path + " --name " + method_name,
93
+ command: generate_test_command(method_name: method_name, class_name: class_name),
94
+ kind: :example,
63
95
  )
64
96
  end
65
97
  end
@@ -67,15 +99,22 @@ module RubyLsp
67
99
 
68
100
  sig { params(node: SyntaxTree::Command).void }
69
101
  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
102
+ node_message = node.message.value
103
+ if ACCESS_MODIFIERS.include?(node_message) && node.arguments.parts.any?
104
+ visibility, _ = @visibility_stack.pop
105
+ @visibility_stack.push([node_message, visibility])
106
+ elsif @path.include?("Gemfile") && node_message.include?("gem") && node.arguments.parts.any?
107
+ remote = resolve_gem_remote(node)
108
+ return unless remote
109
+
110
+ add_open_gem_remote_code_lens(node, remote)
73
111
  end
74
112
  end
75
113
 
76
114
  sig { params(node: SyntaxTree::Command).void }
77
115
  def after_command(node)
78
- @visibility = @prev_visibility
116
+ _, prev_visibility = @visibility_stack.pop
117
+ @visibility_stack.push([prev_visibility, prev_visibility])
79
118
  end
80
119
 
81
120
  sig { params(node: SyntaxTree::CallNode).void }
@@ -85,15 +124,16 @@ module RubyLsp
85
124
  if ident
86
125
  ident_value = T.cast(ident, SyntaxTree::Ident).value
87
126
  if ACCESS_MODIFIERS.include?(ident_value)
88
- @prev_visibility = @visibility
89
- @visibility = ident_value
127
+ visibility, _ = @visibility_stack.pop
128
+ @visibility_stack.push([ident_value, visibility])
90
129
  end
91
130
  end
92
131
  end
93
132
 
94
133
  sig { params(node: SyntaxTree::CallNode).void }
95
134
  def after_call(node)
96
- @visibility = @prev_visibility
135
+ _, prev_visibility = @visibility_stack.pop
136
+ @visibility_stack.push([prev_visibility, prev_visibility])
97
137
  end
98
138
 
99
139
  sig { params(node: SyntaxTree::VCall).void }
@@ -101,8 +141,8 @@ module RubyLsp
101
141
  vcall_value = node.value.value
102
142
 
103
143
  if ACCESS_MODIFIERS.include?(vcall_value)
104
- @prev_visibility = vcall_value
105
- @visibility = vcall_value
144
+ @visibility_stack.pop
145
+ @visibility_stack.push([vcall_value, vcall_value])
106
146
  end
107
147
  end
108
148
 
@@ -114,36 +154,88 @@ module RubyLsp
114
154
 
115
155
  private
116
156
 
117
- sig { params(node: SyntaxTree::Node, name: String, command: String).void }
118
- def add_code_lens(node, name:, command:)
157
+ sig { params(node: SyntaxTree::Node, name: String, command: String, kind: Symbol).void }
158
+ def add_test_code_lens(node, name:, command:, kind:)
159
+ arguments = [
160
+ @path,
161
+ name,
162
+ command,
163
+ {
164
+ start_line: node.location.start_line - 1,
165
+ start_column: node.location.start_column,
166
+ end_line: node.location.end_line - 1,
167
+ end_column: node.location.end_column,
168
+ },
169
+ ]
170
+
119
171
  @response << create_code_lens(
120
172
  node,
121
173
  title: "Run",
122
174
  command_name: "rubyLsp.runTest",
123
- path: @path,
124
- name: name,
125
- test_command: command,
126
- type: "test",
175
+ arguments: arguments,
176
+ data: { type: "test", kind: kind },
127
177
  )
128
178
 
129
179
  @response << create_code_lens(
130
180
  node,
131
181
  title: "Run In Terminal",
132
182
  command_name: "rubyLsp.runTestInTerminal",
133
- path: @path,
134
- name: name,
135
- test_command: command,
136
- type: "test_in_terminal",
183
+ arguments: arguments,
184
+ data: { type: "test_in_terminal", kind: kind },
137
185
  )
138
186
 
139
187
  @response << create_code_lens(
140
188
  node,
141
189
  title: "Debug",
142
190
  command_name: "rubyLsp.debugTest",
143
- path: @path,
144
- name: name,
145
- test_command: command,
146
- type: "debug",
191
+ arguments: arguments,
192
+ data: { type: "debug", kind: kind },
193
+ )
194
+ end
195
+
196
+ sig { params(node: SyntaxTree::Command).returns(T.nilable(String)) }
197
+ def resolve_gem_remote(node)
198
+ gem_statement = node.arguments.parts.flat_map(&:child_nodes).first
199
+ return unless gem_statement
200
+
201
+ spec = Gem::Specification.stubs.find { |gem| gem.name == gem_statement.value }&.to_spec
202
+ return if spec.nil?
203
+
204
+ [spec.homepage, spec.metadata["source_code_uri"]].compact.find do |page|
205
+ page.start_with?("https://github.com", "https://gitlab.com")
206
+ end
207
+ end
208
+
209
+ sig { params(class_name: String, method_name: T.nilable(String)).returns(String) }
210
+ def generate_test_command(class_name:, method_name: nil)
211
+ command = BASE_COMMAND + @path
212
+
213
+ case @test_library
214
+ when "minitest"
215
+ command += if method_name
216
+ " --name " + "/#{Shellwords.escape(class_name + "#" + method_name)}/"
217
+ else
218
+ " --name " + "/#{Shellwords.escape(class_name)}/"
219
+ end
220
+ when "test-unit"
221
+ command += " --testcase " + "/#{Shellwords.escape(class_name)}/"
222
+
223
+ if method_name
224
+ command += " --name " + Shellwords.escape(method_name)
225
+ end
226
+ end
227
+
228
+ command
229
+ end
230
+
231
+ sig { params(node: SyntaxTree::Command, remote: String).void }
232
+ def add_open_gem_remote_code_lens(node, remote)
233
+ @response << create_code_lens(
234
+ node,
235
+ title: "Open remote",
236
+ command_name: "rubyLsp.openLink",
237
+ arguments: [remote],
238
+ data: { type: "link" },
147
239
  )
148
240
  end
149
241
  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,