ruby-lsp 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/VERSION +1 -1
- data/exe/ruby-lsp +33 -1
- data/lib/ruby_lsp/check_docs.rb +23 -5
- data/lib/ruby_lsp/event_emitter.rb +61 -0
- data/lib/ruby_lsp/executor.rb +31 -46
- data/lib/ruby_lsp/requests/base_request.rb +2 -6
- data/lib/ruby_lsp/requests/code_lens.rb +125 -33
- data/lib/ruby_lsp/requests/diagnostics.rb +2 -0
- data/lib/ruby_lsp/requests/folding_ranges.rb +2 -1
- data/lib/ruby_lsp/requests/formatting.rb +30 -11
- data/lib/ruby_lsp/requests/inlay_hints.rb +18 -16
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +85 -113
- data/lib/ruby_lsp/requests/support/common.rb +6 -17
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +41 -0
- data/lib/ruby_lsp/requests/support/formatter_runner.rb +18 -0
- data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +2 -1
- data/lib/ruby_lsp/requests/support/source_uri.rb +1 -1
- data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +2 -1
- data/lib/ruby_lsp/requests.rb +1 -0
- data/lib/ruby_lsp/server.rb +5 -2
- data/lib/ruby_lsp/utils.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11dd3401d408ed456da9b4dd0aca906b991a45a0103ca8786eafbf34d4659c85
|
4
|
+
data.tar.gz: 8469dd62a3660965daee589835e1481fe09e17b55cbd9d61c461d4ba94db833e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
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
|
data/lib/ruby_lsp/check_docs.rb
CHANGED
@@ -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
|
-
#
|
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
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
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
|
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
|
-
|
15
|
-
|
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
|
# 
|
@@ -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
|
-
|
41
|
-
@
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
90
|
+
add_test_code_lens(
|
60
91
|
node,
|
61
92
|
name: method_name,
|
62
|
-
command:
|
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
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
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
|
-
|
89
|
-
@visibility
|
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
|
-
|
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
|
-
@
|
105
|
-
@
|
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
|
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
|
-
|
124
|
-
|
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
|
-
|
134
|
-
|
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
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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,
|