ruby-lsp 0.23.14 → 0.23.15
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/VERSION +1 -1
- data/exe/ruby-lsp-launcher +9 -1
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +6 -3
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +4 -2
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +59 -29
- data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +5 -4
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +5 -1
- data/lib/ruby_indexer/test/class_variables_test.rb +14 -14
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +65 -40
- data/lib/ruby_indexer/test/configuration_test.rb +6 -4
- data/lib/ruby_indexer/test/constant_test.rb +34 -34
- data/lib/ruby_indexer/test/enhancements_test.rb +1 -1
- data/lib/ruby_indexer/test/index_test.rb +139 -135
- data/lib/ruby_indexer/test/instance_variables_test.rb +37 -37
- data/lib/ruby_indexer/test/method_test.rb +118 -118
- data/lib/ruby_indexer/test/prefix_tree_test.rb +13 -13
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +64 -70
- data/lib/ruby_indexer/test/test_case.rb +2 -2
- data/lib/ruby_lsp/erb_document.rb +12 -4
- data/lib/ruby_lsp/global_state.rb +1 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +3 -3
- data/lib/ruby_lsp/listeners/completion.rb +24 -11
- data/lib/ruby_lsp/listeners/definition.rb +1 -1
- data/lib/ruby_lsp/listeners/document_link.rb +3 -1
- data/lib/ruby_lsp/listeners/document_symbol.rb +3 -3
- data/lib/ruby_lsp/listeners/folding_ranges.rb +8 -4
- data/lib/ruby_lsp/listeners/hover.rb +2 -2
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +12 -5
- data/lib/ruby_lsp/listeners/signature_help.rb +5 -1
- data/lib/ruby_lsp/listeners/spec_style.rb +1 -1
- data/lib/ruby_lsp/listeners/test_style.rb +3 -3
- data/lib/ruby_lsp/requests/code_action_resolve.rb +4 -3
- data/lib/ruby_lsp/requests/completion_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +2 -2
- data/lib/ruby_lsp/requests/on_type_formatting.rb +4 -2
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -2
- data/lib/ruby_lsp/requests/references.rb +2 -1
- data/lib/ruby_lsp/requests/rename.rb +8 -5
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +4 -4
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +3 -1
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
- data/lib/ruby_lsp/requests/support/source_uri.rb +1 -1
- data/lib/ruby_lsp/response_builders/document_symbol.rb +3 -2
- data/lib/ruby_lsp/response_builders/hover.rb +1 -1
- data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +1 -1
- data/lib/ruby_lsp/scripts/compose_bundle.rb +6 -4
- data/lib/ruby_lsp/server.rb +12 -4
- data/lib/ruby_lsp/setup_bundler.rb +5 -2
- data/lib/ruby_lsp/static_docs.rb +8 -1
- data/lib/ruby_lsp/store.rb +3 -2
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +152 -0
- data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +105 -0
- data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +94 -0
- data/lib/ruby_lsp/type_inferrer.rb +4 -1
- metadata +6 -6
- data/lib/ruby_lsp/ruby_lsp_reporter_plugin.rb +0 -109
- data/lib/ruby_lsp/test_reporter.rb +0 -207
- data/lib/ruby_lsp/test_unit_test_runner.rb +0 -98
@@ -299,7 +299,7 @@ module RubyLsp
|
|
299
299
|
methods = @index.resolve_method(message, type.name, inherited_only: inherited_only)
|
300
300
|
return unless methods
|
301
301
|
|
302
|
-
first_method =
|
302
|
+
first_method = methods.first #: as !nil
|
303
303
|
|
304
304
|
title = "#{message}#{first_method.decorated_parameters}"
|
305
305
|
title << first_method.formatted_signatures
|
@@ -365,7 +365,7 @@ module RubyLsp
|
|
365
365
|
|
366
366
|
# We should only show hover for private constants if the constant is defined in the same namespace as the
|
367
367
|
# reference
|
368
|
-
first_entry =
|
368
|
+
first_entry = entries.first #: as !nil
|
369
369
|
return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{name}"
|
370
370
|
|
371
371
|
categorized_markdown_from_index_entries(name, entries).each do |category, content|
|
@@ -67,12 +67,18 @@ module RubyLsp
|
|
67
67
|
return if special_method?(message)
|
68
68
|
|
69
69
|
if Requests::Support::Sorbet.annotation?(node)
|
70
|
-
@response_builder.add_token(
|
70
|
+
@response_builder.add_token(
|
71
|
+
node.message_loc, #: as !nil
|
72
|
+
:type,
|
73
|
+
)
|
71
74
|
elsif !node.receiver && !node.opening_loc
|
72
75
|
# If the node has a receiver, then the syntax is not ambiguous and semantic highlighting is not necessary to
|
73
76
|
# determine that the token is a method call. The only ambiguous case is method calls with implicit self
|
74
77
|
# receiver and no parenthesis, which may be confused with local variables
|
75
|
-
@response_builder.add_token(
|
78
|
+
@response_builder.add_token(
|
79
|
+
node.message_loc, #: as !nil
|
80
|
+
:method,
|
81
|
+
)
|
76
82
|
end
|
77
83
|
end
|
78
84
|
|
@@ -98,7 +104,7 @@ module RubyLsp
|
|
98
104
|
|
99
105
|
#: (Prism::DefNode node) -> void
|
100
106
|
def on_def_node_leave(node)
|
101
|
-
@current_scope =
|
107
|
+
@current_scope = @current_scope.parent #: as !nil
|
102
108
|
end
|
103
109
|
|
104
110
|
#: (Prism::BlockNode node) -> void
|
@@ -108,7 +114,7 @@ module RubyLsp
|
|
108
114
|
|
109
115
|
#: (Prism::BlockNode node) -> void
|
110
116
|
def on_block_node_leave(node)
|
111
|
-
@current_scope =
|
117
|
+
@current_scope = @current_scope.parent #: as !nil
|
112
118
|
end
|
113
119
|
|
114
120
|
#: (Prism::BlockLocalVariableNode node) -> void
|
@@ -301,7 +307,8 @@ module RubyLsp
|
|
301
307
|
# For each capture name we find in the regexp, look for a local in the current_scope
|
302
308
|
Regexp.new(content, Regexp::FIXEDENCODING).names.each do |name|
|
303
309
|
# The +3 is to compensate for the "(?<" part of the capture name
|
304
|
-
|
310
|
+
capture_name_index = content.index("(?<#{name}>") #: as !nil
|
311
|
+
capture_name_offset = capture_name_index + 3
|
305
312
|
local_var_loc = loc.copy(start_offset: loc.start_offset + capture_name_offset, length: name.length)
|
306
313
|
|
307
314
|
local = @current_scope.lookup(name)
|
@@ -69,7 +69,11 @@ module RubyLsp
|
|
69
69
|
signature.matches?(arguments)
|
70
70
|
end || 0
|
71
71
|
|
72
|
-
parameter_length = [
|
72
|
+
parameter_length = [
|
73
|
+
signatures[active_sig_index] #: as !nil
|
74
|
+
.parameters.length - 1,
|
75
|
+
0,
|
76
|
+
].max
|
73
77
|
active_parameter = (arguments.length - 1).clamp(0, parameter_length)
|
74
78
|
|
75
79
|
# If there are arguments, then we need to check if there's a trailing comma after the end of the last argument
|
@@ -22,7 +22,7 @@ module RubyLsp
|
|
22
22
|
queue = items.dup
|
23
23
|
|
24
24
|
until queue.empty?
|
25
|
-
item =
|
25
|
+
item = queue.shift #: as !nil
|
26
26
|
tags = Set.new(item[:tags])
|
27
27
|
next unless tags.include?("framework:minitest") || tags.include?("framework:test_unit")
|
28
28
|
|
@@ -133,8 +133,8 @@ module RubyLsp
|
|
133
133
|
|
134
134
|
include Requests::Support::Common
|
135
135
|
|
136
|
-
MINITEST_REPORTER_PATH = File.expand_path("../
|
137
|
-
TEST_UNIT_REPORTER_PATH = File.expand_path("../
|
136
|
+
MINITEST_REPORTER_PATH = File.expand_path("../test_reporters/minitest_reporter.rb", __dir__) #: String
|
137
|
+
TEST_UNIT_REPORTER_PATH = File.expand_path("../test_reporters/test_unit_reporter.rb", __dir__) #: String
|
138
138
|
ACCESS_MODIFIERS = [:public, :private, :protected].freeze
|
139
139
|
BASE_COMMAND = begin
|
140
140
|
Bundler.with_original_env { Bundler.default_lockfile }
|
@@ -97,7 +97,7 @@ module RubyLsp
|
|
97
97
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
98
98
|
|
99
99
|
start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
|
100
|
-
extracted_source =
|
100
|
+
extracted_source = @document.source[start_index...end_index] #: as !nil
|
101
101
|
|
102
102
|
# Find the closest statements node, so that we place the refactor in a valid position
|
103
103
|
node_context = RubyDocument
|
@@ -153,7 +153,8 @@ module RubyLsp
|
|
153
153
|
indentation_line = lines[indentation_line_number]
|
154
154
|
return Error::InvalidTargetRange unless indentation_line
|
155
155
|
|
156
|
-
indentation =
|
156
|
+
indentation = indentation_line[/\A */] #: as !nil
|
157
|
+
.size
|
157
158
|
|
158
159
|
target_range = {
|
159
160
|
start: { line: target_line, character: indentation },
|
@@ -195,7 +196,7 @@ module RubyLsp
|
|
195
196
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
196
197
|
|
197
198
|
start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
|
198
|
-
extracted_source =
|
199
|
+
extracted_source = @document.source[start_index...end_index] #: as !nil
|
199
200
|
|
200
201
|
# Find the closest method declaration node, so that we place the refactor in a valid position
|
201
202
|
node_context = RubyDocument.locate(
|
@@ -51,7 +51,9 @@ module RubyLsp
|
|
51
51
|
# But if it's a RBS signature starting with `#:`, we'll ignore it
|
52
52
|
# so users can immediately continue typing the method definition
|
53
53
|
if (comment_match = @previous_line.match(/^#(?!:)(\s*)/))
|
54
|
-
handle_comment_line(
|
54
|
+
handle_comment_line(
|
55
|
+
comment_match[1], #: as !nil
|
56
|
+
)
|
55
57
|
elsif @document.syntax_error?
|
56
58
|
match = /(<<((-|~)?))(?<quote>['"`]?)(?<delimiter>\w+)\k<quote>/.match(@previous_line)
|
57
59
|
heredoc_delimiter = match && match.named_captures["delimiter"]
|
@@ -76,7 +78,7 @@ module RubyLsp
|
|
76
78
|
current_line = @lines[@position[:line]]
|
77
79
|
return unless /((?<=do)|(?<={))\s+\|/.match?(current_line)
|
78
80
|
|
79
|
-
line =
|
81
|
+
line = current_line #: as !nil
|
80
82
|
|
81
83
|
# If the user inserts the closing pipe manually to the end of the block argument, we need to avoid adding
|
82
84
|
# an additional one and remove the previous one. This also helps to remove the user who accidentally
|
@@ -49,8 +49,7 @@ module RubyLsp
|
|
49
49
|
|
50
50
|
# While the spec allows for multiple entries, VSCode seems to only support one
|
51
51
|
# We'll just return the first one for now
|
52
|
-
first_entry =
|
53
|
-
|
52
|
+
first_entry = entries.first #: as !nil
|
54
53
|
range = range_from_location(first_entry.location)
|
55
54
|
|
56
55
|
[
|
@@ -106,7 +106,8 @@ module RubyLsp
|
|
106
106
|
entries = @global_state.index.resolve(name, node_context.nesting)
|
107
107
|
return unless entries
|
108
108
|
|
109
|
-
fully_qualified_name =
|
109
|
+
fully_qualified_name = entries.first #: as !nil
|
110
|
+
.name
|
110
111
|
RubyIndexer::ReferenceFinder::ConstTarget.new(fully_qualified_name)
|
111
112
|
when
|
112
113
|
Prism::InstanceVariableAndWriteNode,
|
@@ -63,10 +63,11 @@ module RubyLsp
|
|
63
63
|
return unless entries
|
64
64
|
|
65
65
|
if (conflict_entries = @global_state.index.resolve(@new_name, node_context.nesting))
|
66
|
-
raise InvalidNameError, "The new name is already in use by #{
|
66
|
+
raise InvalidNameError, "The new name is already in use by #{conflict_entries.first&.name}"
|
67
67
|
end
|
68
68
|
|
69
|
-
fully_qualified_name =
|
69
|
+
fully_qualified_name = entries.first #: as !nil
|
70
|
+
.name
|
70
71
|
reference_target = RubyIndexer::ReferenceFinder::ConstTarget.new(fully_qualified_name)
|
71
72
|
changes = collect_text_edits(reference_target, name)
|
72
73
|
|
@@ -97,9 +98,9 @@ module RubyLsp
|
|
97
98
|
# rename the files for the user.
|
98
99
|
#
|
99
100
|
# We also look for an associated test file and rename it too
|
100
|
-
short_name =
|
101
|
+
short_name = fully_qualified_name.split("::").last #: as !nil
|
101
102
|
|
102
|
-
|
103
|
+
@global_state.index[fully_qualified_name]&.each do |entry|
|
103
104
|
# Do not rename files that are not part of the workspace
|
104
105
|
uri = entry.uri
|
105
106
|
file_path = uri.full_path
|
@@ -112,7 +113,9 @@ module RubyLsp
|
|
112
113
|
file_name = file_from_constant_name(short_name)
|
113
114
|
|
114
115
|
if "#{file_name}.rb" == entry.file_name
|
115
|
-
new_file_name = file_from_constant_name(
|
116
|
+
new_file_name = file_from_constant_name(
|
117
|
+
@new_name.split("::").last, #: as !nil
|
118
|
+
)
|
116
119
|
|
117
120
|
new_uri = URI::Generic.from_path(path: File.join(
|
118
121
|
File.dirname(file_path),
|
@@ -43,8 +43,8 @@ module RubyLsp
|
|
43
43
|
# Filter the tokens based on the first different position. This must happen at this stage, before we try to
|
44
44
|
# find the next position from the end or else we risk confusing sets of token that may have different lengths,
|
45
45
|
# but end with the exact same token
|
46
|
-
old_tokens =
|
47
|
-
new_tokens =
|
46
|
+
old_tokens = previous_tokens[first_different_position...] #: as !nil
|
47
|
+
new_tokens = current_tokens[first_different_position...] #: as !nil
|
48
48
|
|
49
49
|
# Then search from the end to find the first token that doesn't match. Since the user is normally editing the
|
50
50
|
# middle of the file, this will minimize the number of edits since the end of the token array has not changed
|
@@ -53,8 +53,8 @@ module RubyLsp
|
|
53
53
|
end || 0
|
54
54
|
|
55
55
|
# Filter the old and new tokens to only the section that will be replaced/inserted/deleted
|
56
|
-
old_tokens =
|
57
|
-
new_tokens =
|
56
|
+
old_tokens = old_tokens[...old_tokens.length - first_different_token_from_end] #: as !nil
|
57
|
+
new_tokens = new_tokens[...new_tokens.length - first_different_token_from_end] #: as !nil
|
58
58
|
|
59
59
|
# And we send back a single edit, replacing an entire section for the new tokens
|
60
60
|
Interface::SemanticTokensDelta.new(
|
@@ -54,7 +54,9 @@ module RubyLsp
|
|
54
54
|
#: (String file_path) -> bool?
|
55
55
|
def not_in_dependencies?(file_path)
|
56
56
|
BUNDLE_PATH &&
|
57
|
-
!file_path.start_with?(
|
57
|
+
!file_path.start_with?(
|
58
|
+
BUNDLE_PATH, #: as !nil
|
59
|
+
) &&
|
58
60
|
!file_path.start_with?(RbConfig::CONFIG["rubylibdir"])
|
59
61
|
end
|
60
62
|
|
@@ -21,7 +21,7 @@ module RubyLsp
|
|
21
21
|
# @override
|
22
22
|
#: (URI::Generic uri, RubyDocument document) -> String?
|
23
23
|
def run_formatting(uri, document)
|
24
|
-
filename =
|
24
|
+
filename = uri.to_standardized_path || uri.opaque #: as !nil
|
25
25
|
|
26
26
|
# Invoke RuboCop with just this file in `paths`
|
27
27
|
@format_runner.run(filename, document.source)
|
@@ -38,7 +38,7 @@ module RubyLsp
|
|
38
38
|
# @override
|
39
39
|
#: (URI::Generic uri, RubyDocument document) -> Array[Interface::Diagnostic]?
|
40
40
|
def run_diagnostic(uri, document)
|
41
|
-
filename =
|
41
|
+
filename = uri.to_standardized_path || uri.opaque #: as !nil
|
42
42
|
# Invoke RuboCop with just this file in `paths`
|
43
43
|
@diagnostic_runner.run(filename, document.source)
|
44
44
|
|
@@ -71,7 +71,7 @@ module URI
|
|
71
71
|
if URI.respond_to?(:register_scheme)
|
72
72
|
URI.register_scheme("SOURCE", self)
|
73
73
|
else
|
74
|
-
@@schemes = @@schemes # rubocop:disable Style/ClassVars
|
74
|
+
@@schemes = @@schemes #: Hash[String, untyped] # rubocop:disable Style/ClassVars
|
75
75
|
@@schemes["SOURCE"] = self
|
76
76
|
end
|
77
77
|
end
|
@@ -38,13 +38,14 @@ module RubyLsp
|
|
38
38
|
|
39
39
|
#: -> (SymbolHierarchyRoot | Interface::DocumentSymbol)
|
40
40
|
def last
|
41
|
-
|
41
|
+
@stack.last #: as !nil
|
42
42
|
end
|
43
43
|
|
44
44
|
# @override
|
45
45
|
#: -> Array[Interface::DocumentSymbol]
|
46
46
|
def response
|
47
|
-
|
47
|
+
@stack.first #: as !nil
|
48
|
+
.children
|
48
49
|
end
|
49
50
|
end
|
50
51
|
end
|
@@ -35,7 +35,7 @@ module RubyLsp
|
|
35
35
|
# @override
|
36
36
|
#: -> ResponseType
|
37
37
|
def response
|
38
|
-
result =
|
38
|
+
result = @response[:title] #: as !nil
|
39
39
|
result << "\n" << @response[:links] if @response[:links]
|
40
40
|
result << "\n" << @response[:documentation] if @response[:documentation]
|
41
41
|
|
@@ -64,7 +64,7 @@ module RubyLsp
|
|
64
64
|
start_line: location.start_line,
|
65
65
|
start_code_unit_column: location.cached_start_code_units_column(@code_units_cache),
|
66
66
|
length: length,
|
67
|
-
type:
|
67
|
+
type: TOKEN_TYPES[type], #: as !nil
|
68
68
|
modifier: modifiers_indices,
|
69
69
|
),
|
70
70
|
)
|
@@ -13,8 +13,10 @@ def compose(raw_initialize)
|
|
13
13
|
workspace_path ||= Dir.pwd
|
14
14
|
|
15
15
|
env = RubyLsp::SetupBundler.new(workspace_path, launcher: true).setup!
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
|
17
|
+
File.open(File.join(".ruby-lsp", "bundle_env"), "w") do |f|
|
18
|
+
f.flock(File::LOCK_EX)
|
19
|
+
f.write(env.map { |k, v| "#{k}=#{v}" }.join("\n"))
|
20
|
+
f.flush
|
21
|
+
end
|
20
22
|
end
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -223,7 +223,8 @@ module RubyLsp
|
|
223
223
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
224
224
|
|
225
225
|
configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
|
226
|
-
|
226
|
+
@store.features_configuration.dig(:inlayHint) #: as !nil
|
227
|
+
.configuration.merge!(configured_hints) if configured_hints
|
227
228
|
|
228
229
|
enabled_features = case configured_features
|
229
230
|
when Array
|
@@ -294,6 +295,7 @@ module RubyLsp
|
|
294
295
|
addon_detection: true,
|
295
296
|
compose_bundle: true,
|
296
297
|
go_to_relevant_file: true,
|
298
|
+
full_test_discovery: true,
|
297
299
|
},
|
298
300
|
),
|
299
301
|
serverInfo: {
|
@@ -488,7 +490,11 @@ module RubyLsp
|
|
488
490
|
document_symbol = Requests::DocumentSymbol.new(uri, dispatcher)
|
489
491
|
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
|
490
492
|
code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
|
491
|
-
inlay_hint = Requests::InlayHints.new(
|
493
|
+
inlay_hint = Requests::InlayHints.new(
|
494
|
+
document,
|
495
|
+
@store.features_configuration.dig(:inlayHint), #: as !nil
|
496
|
+
dispatcher,
|
497
|
+
)
|
492
498
|
|
493
499
|
if document.is_a?(RubyDocument) && document.should_index?
|
494
500
|
# Re-index the file as it is modified. This mode of indexing updates entries only. Require path trees are only
|
@@ -815,7 +821,7 @@ module RubyLsp
|
|
815
821
|
return
|
816
822
|
end
|
817
823
|
|
818
|
-
hints_configurations =
|
824
|
+
hints_configurations = @store.features_configuration.dig(:inlayHint) #: as !nil
|
819
825
|
dispatcher = Prism::Dispatcher.new
|
820
826
|
|
821
827
|
unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
|
@@ -1393,7 +1399,9 @@ module RubyLsp
|
|
1393
1399
|
Open3.capture3(
|
1394
1400
|
Gem.ruby,
|
1395
1401
|
"-I",
|
1396
|
-
File.dirname(
|
1402
|
+
File.dirname(
|
1403
|
+
__dir__, #: as !nil
|
1404
|
+
),
|
1397
1405
|
File.expand_path("../../exe/ruby-lsp-launcher", __dir__),
|
1398
1406
|
@global_state.workspace_uri.to_s,
|
1399
1407
|
chdir: @global_state.workspace_path,
|
@@ -405,12 +405,15 @@ module RubyLsp
|
|
405
405
|
def correct_relative_remote_paths
|
406
406
|
content = @custom_lockfile.read
|
407
407
|
content.gsub!(/remote: (.*)/) do |match|
|
408
|
-
|
408
|
+
last_match = Regexp.last_match #: as !nil
|
409
|
+
path = last_match[1]
|
409
410
|
|
410
411
|
# We should only apply the correction if the remote is a relative path. It might also be a URI, like
|
411
412
|
# `https://rubygems.org` or an absolute path, in which case we shouldn't do anything
|
412
413
|
if path && !URI(path).scheme
|
413
|
-
|
414
|
+
bundle_dir = @gemfile #: as !nil
|
415
|
+
.dirname
|
416
|
+
"remote: #{File.expand_path(path, bundle_dir)}"
|
414
417
|
else
|
415
418
|
match
|
416
419
|
end
|
data/lib/ruby_lsp/static_docs.rb
CHANGED
@@ -3,7 +3,14 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
# The path to the `static_docs` directory, where we keep long-form static documentation
|
6
|
-
STATIC_DOCS_PATH = File.join(
|
6
|
+
STATIC_DOCS_PATH = File.join(
|
7
|
+
File.dirname(
|
8
|
+
File.dirname(
|
9
|
+
__dir__, #: as !nil
|
10
|
+
),
|
11
|
+
),
|
12
|
+
"static_docs",
|
13
|
+
) #: String
|
7
14
|
|
8
15
|
# A map of keyword => short documentation to be displayed on hover or completion
|
9
16
|
KEYWORD_DOCS = {
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -46,7 +46,7 @@ module RubyLsp
|
|
46
46
|
end
|
47
47
|
|
48
48
|
set(uri: uri, source: File.binread(path), version: 0, language_id: language_id)
|
49
|
-
|
49
|
+
@state[uri.to_s] #: as !nil
|
50
50
|
rescue Errno::ENOENT
|
51
51
|
raise NonExistingDocumentError, uri.to_s
|
52
52
|
end
|
@@ -65,7 +65,8 @@ module RubyLsp
|
|
65
65
|
|
66
66
|
#: (uri: URI::Generic, edits: Array[Hash[Symbol, untyped]], version: Integer) -> void
|
67
67
|
def push_edits(uri:, edits:, version:)
|
68
|
-
|
68
|
+
@state[uri.to_s] #: as !nil
|
69
|
+
.push_edits(edits, version: version)
|
69
70
|
end
|
70
71
|
|
71
72
|
#: -> void
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "json"
|
5
|
+
require "socket"
|
6
|
+
require "singleton"
|
7
|
+
|
8
|
+
module RubyLsp
|
9
|
+
class LspReporter
|
10
|
+
include Singleton
|
11
|
+
|
12
|
+
#: -> void
|
13
|
+
def initialize
|
14
|
+
port = ENV["RUBY_LSP_REPORTER_PORT"]
|
15
|
+
@io = if port
|
16
|
+
TCPSocket.new("localhost", port)
|
17
|
+
else
|
18
|
+
# For tests that don't spawn the TCP server
|
19
|
+
require "stringio"
|
20
|
+
StringIO.new
|
21
|
+
end #: IO | StringIO
|
22
|
+
end
|
23
|
+
|
24
|
+
#: -> void
|
25
|
+
def shutdown
|
26
|
+
send_message("finish")
|
27
|
+
@io.close
|
28
|
+
end
|
29
|
+
|
30
|
+
#: (id: String, uri: URI::Generic) -> void
|
31
|
+
def start_test(id:, uri:)
|
32
|
+
send_message("start", id: id, uri: uri.to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
#: (id: String, uri: URI::Generic) -> void
|
36
|
+
def record_pass(id:, uri:)
|
37
|
+
send_message("pass", id: id, uri: uri.to_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
#: (id: String, message: String, uri: URI::Generic) -> void
|
41
|
+
def record_fail(id:, message:, uri:)
|
42
|
+
send_message("fail", id: id, message: message, uri: uri.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
#: (id: String, uri: URI::Generic) -> void
|
46
|
+
def record_skip(id:, uri:)
|
47
|
+
send_message("skip", id: id, uri: uri.to_s)
|
48
|
+
end
|
49
|
+
|
50
|
+
#: (id: String, message: String?, uri: URI::Generic) -> void
|
51
|
+
def record_error(id:, message:, uri:)
|
52
|
+
send_message("error", id: id, message: message, uri: uri.to_s)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Gather the results returned by Coverage.result and format like the VS Code test explorer expects
|
56
|
+
#
|
57
|
+
# Coverage result format:
|
58
|
+
#
|
59
|
+
# Lines are reported in order as an array where each number is the number of times it was executed. For example,
|
60
|
+
# the following says that line 0 was executed 1 time and line 1 executed 3 times: [1, 3].
|
61
|
+
# Nil values represent lines for which coverage is not available, like empty lines, comments or keywords like
|
62
|
+
# `else`
|
63
|
+
#
|
64
|
+
# Branches are a hash containing the name of the branch and the location where it is found in tuples with the
|
65
|
+
# following elements: [NAME, ID, START_LINE, START_COLUMN, END_LINE, END_COLUMN] as the keys and the value is the
|
66
|
+
# number of times it was executed
|
67
|
+
#
|
68
|
+
# Methods are a similar hash [ClassName, :method_name, START_LINE, START_COLUMN, END_LINE, END_COLUMN] => NUMBER
|
69
|
+
# OF EXECUTIONS
|
70
|
+
#
|
71
|
+
# Example:
|
72
|
+
# {
|
73
|
+
# "file_path" => {
|
74
|
+
# "lines" => [1, 2, 3, nil],
|
75
|
+
# "branches" => {
|
76
|
+
# ["&.", 0, 6, 21, 6, 65] => { [:then, 1, 6, 21, 6, 65] => 0, [:else, 5, 7, 0, 7, 87] => 1 }
|
77
|
+
# },
|
78
|
+
# "methods" => {
|
79
|
+
# ["Foo", :bar, 6, 21, 6, 65] => 0
|
80
|
+
# }
|
81
|
+
# }
|
82
|
+
#: -> Hash[String, StatementCoverage]
|
83
|
+
def gather_coverage_results
|
84
|
+
# Ignore coverage results inside dependencies
|
85
|
+
bundle_path = Bundler.bundle_path.to_s
|
86
|
+
default_gems_path = File.dirname(RbConfig::CONFIG["rubylibdir"])
|
87
|
+
|
88
|
+
result = Coverage.result.reject do |file_path, _coverage_info|
|
89
|
+
file_path.start_with?(bundle_path, default_gems_path, "eval")
|
90
|
+
end
|
91
|
+
|
92
|
+
result.to_h do |file_path, coverage_info|
|
93
|
+
# Format the branch coverage information as VS Code expects it and then group it based on the start line of
|
94
|
+
# the conditional that causes the branching. We need to match each line coverage data with the branches that
|
95
|
+
# spawn from that line
|
96
|
+
branch_by_line = coverage_info[:branches]
|
97
|
+
.flat_map do |branch, data|
|
98
|
+
branch_name, _branch_id, branch_start_line, _branch_start_col, _branch_end_line, _branch_end_col = branch
|
99
|
+
|
100
|
+
data.map do |then_or_else, execution_count|
|
101
|
+
name, _id, start_line, start_column, end_line, end_column = then_or_else
|
102
|
+
|
103
|
+
{
|
104
|
+
groupingLine: branch_start_line,
|
105
|
+
executed: execution_count,
|
106
|
+
location: {
|
107
|
+
start: { line: start_line, character: start_column },
|
108
|
+
end: { line: end_line, character: end_column },
|
109
|
+
},
|
110
|
+
label: "#{branch_name} #{name}",
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
.group_by { |branch| branch[:groupingLine] }
|
115
|
+
|
116
|
+
# Format the line coverage information, gathering any branch coverage data associated with that line
|
117
|
+
data = coverage_info[:lines].filter_map.with_index do |execution_count, line_index|
|
118
|
+
next if execution_count.nil?
|
119
|
+
|
120
|
+
{
|
121
|
+
executed: execution_count,
|
122
|
+
location: { line: line_index, character: 0 },
|
123
|
+
branches: branch_by_line[line_index] || [],
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
# The expected format is URI => { executed: number_of_times_executed, location: { ... }, branches: [ ... ] }
|
128
|
+
[URI::Generic.from_path(path: File.expand_path(file_path)).to_s, data]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
#: (method_name: String?, params: untyped) -> void
|
135
|
+
def send_message(method_name, **params)
|
136
|
+
json_message = { method: method_name, params: params }.to_json
|
137
|
+
@io.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
if ENV["RUBY_LSP_TEST_RUNNER"] == "coverage"
|
143
|
+
# Auto start coverage when running tests under that profile. This avoids the user from having to configure coverage
|
144
|
+
# manually for their project or adding extra dependencies
|
145
|
+
require "coverage"
|
146
|
+
Coverage.start(:all)
|
147
|
+
|
148
|
+
at_exit do
|
149
|
+
coverage_results = RubyLsp::LspReporter.instance.gather_coverage_results
|
150
|
+
File.write(File.join(".ruby-lsp", "coverage_result.json"), coverage_results.to_json)
|
151
|
+
end
|
152
|
+
end
|