ruby-lsp 0.23.10 → 0.23.12
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 +2 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp-launcher +12 -11
- data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +3 -5
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +52 -77
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +61 -144
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +8 -6
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +74 -183
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +55 -181
- data/lib/ruby_indexer/lib/ruby_indexer/location.rb +4 -27
- data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +12 -14
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +21 -44
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +40 -58
- data/lib/ruby_indexer/lib/ruby_indexer/uri.rb +9 -16
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +5 -9
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +75 -0
- data/lib/ruby_indexer/test/configuration_test.rb +32 -2
- data/lib/ruby_indexer/test/index_test.rb +21 -0
- data/lib/ruby_indexer/test/method_test.rb +2 -2
- data/lib/ruby_lsp/addon.rb +32 -67
- data/lib/ruby_lsp/base_server.rb +12 -11
- data/lib/ruby_lsp/client_capabilities.rb +4 -6
- data/lib/ruby_lsp/document.rb +21 -32
- data/lib/ruby_lsp/erb_document.rb +17 -27
- data/lib/ruby_lsp/global_state.rb +30 -32
- data/lib/ruby_lsp/internal.rb +6 -0
- data/lib/ruby_lsp/listeners/code_lens.rb +21 -39
- data/lib/ruby_lsp/listeners/completion.rb +34 -53
- data/lib/ruby_lsp/listeners/definition.rb +35 -49
- data/lib/ruby_lsp/listeners/document_highlight.rb +60 -69
- data/lib/ruby_lsp/listeners/document_link.rb +9 -19
- data/lib/ruby_lsp/listeners/document_symbol.rb +34 -48
- data/lib/ruby_lsp/listeners/folding_ranges.rb +31 -38
- data/lib/ruby_lsp/listeners/hover.rb +37 -47
- data/lib/ruby_lsp/listeners/inlay_hints.rb +3 -10
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +29 -35
- data/lib/ruby_lsp/listeners/signature_help.rb +4 -23
- data/lib/ruby_lsp/listeners/spec_style.rb +199 -0
- data/lib/ruby_lsp/listeners/test_style.rb +270 -0
- data/lib/ruby_lsp/node_context.rb +8 -35
- data/lib/ruby_lsp/rbs_document.rb +7 -5
- data/lib/ruby_lsp/requests/code_action_resolve.rb +10 -10
- data/lib/ruby_lsp/requests/code_actions.rb +5 -14
- data/lib/ruby_lsp/requests/code_lens.rb +4 -13
- data/lib/ruby_lsp/requests/completion.rb +4 -15
- data/lib/ruby_lsp/requests/completion_resolve.rb +4 -4
- data/lib/ruby_lsp/requests/definition.rb +4 -12
- data/lib/ruby_lsp/requests/diagnostics.rb +6 -9
- data/lib/ruby_lsp/requests/discover_tests.rb +74 -0
- data/lib/ruby_lsp/requests/document_highlight.rb +3 -11
- data/lib/ruby_lsp/requests/document_link.rb +4 -13
- data/lib/ruby_lsp/requests/document_symbol.rb +4 -7
- data/lib/ruby_lsp/requests/folding_ranges.rb +4 -7
- data/lib/ruby_lsp/requests/formatting.rb +4 -7
- data/lib/ruby_lsp/requests/go_to_relevant_file.rb +87 -0
- data/lib/ruby_lsp/requests/hover.rb +6 -16
- data/lib/ruby_lsp/requests/inlay_hints.rb +4 -13
- data/lib/ruby_lsp/requests/on_type_formatting.rb +17 -24
- data/lib/ruby_lsp/requests/prepare_rename.rb +3 -8
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +4 -13
- data/lib/ruby_lsp/requests/range_formatting.rb +3 -4
- data/lib/ruby_lsp/requests/references.rb +5 -35
- data/lib/ruby_lsp/requests/rename.rb +9 -35
- data/lib/ruby_lsp/requests/request.rb +5 -17
- data/lib/ruby_lsp/requests/selection_ranges.rb +3 -3
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +6 -23
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +4 -5
- data/lib/ruby_lsp/requests/signature_help.rb +6 -24
- data/lib/ruby_lsp/requests/support/annotation.rb +4 -10
- data/lib/ruby_lsp/requests/support/common.rb +12 -47
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -14
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +7 -10
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +9 -15
- data/lib/ruby_lsp/requests/support/selection_range.rb +1 -3
- data/lib/ruby_lsp/requests/support/sorbet.rb +1 -7
- data/lib/ruby_lsp/requests/support/source_uri.rb +5 -16
- data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +7 -10
- data/lib/ruby_lsp/requests/support/test_item.rb +60 -0
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +4 -5
- data/lib/ruby_lsp/requests/workspace_symbol.rb +3 -3
- data/lib/ruby_lsp/response_builders/collection_response_builder.rb +4 -4
- data/lib/ruby_lsp/response_builders/document_symbol.rb +8 -11
- data/lib/ruby_lsp/response_builders/hover.rb +5 -5
- data/lib/ruby_lsp/response_builders/response_builder.rb +1 -1
- data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +18 -40
- data/lib/ruby_lsp/response_builders/signature_help.rb +4 -5
- data/lib/ruby_lsp/response_builders/test_collection.rb +34 -0
- data/lib/ruby_lsp/ruby_document.rb +15 -40
- data/lib/ruby_lsp/ruby_lsp_reporter_plugin.rb +106 -0
- data/lib/ruby_lsp/scope.rb +6 -10
- data/lib/ruby_lsp/server.rb +169 -72
- data/lib/ruby_lsp/setup_bundler.rb +25 -17
- data/lib/ruby_lsp/store.rb +12 -28
- data/lib/ruby_lsp/test_helper.rb +3 -12
- data/lib/ruby_lsp/test_reporter.rb +71 -0
- data/lib/ruby_lsp/test_unit_test_runner.rb +96 -0
- data/lib/ruby_lsp/type_inferrer.rb +9 -13
- data/lib/ruby_lsp/utils.rb +27 -65
- metadata +12 -3
@@ -0,0 +1,199 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Listeners
|
6
|
+
class SpecStyle
|
7
|
+
extend T::Sig
|
8
|
+
include Requests::Support::Common
|
9
|
+
|
10
|
+
DYNAMIC_REFERENCE_MARKER = "<dynamic_reference>"
|
11
|
+
|
12
|
+
#: (response_builder: ResponseBuilders::TestCollection, global_state: GlobalState, dispatcher: Prism::Dispatcher, uri: URI::Generic) -> void
|
13
|
+
def initialize(response_builder, global_state, dispatcher, uri)
|
14
|
+
@response_builder = response_builder
|
15
|
+
@uri = uri
|
16
|
+
@index = T.let(global_state.index, RubyIndexer::Index)
|
17
|
+
@visibility_stack = T.let([:public], T::Array[Symbol])
|
18
|
+
@nesting = T.let([], T::Array[String])
|
19
|
+
@describe_block_nesting = T.let([], T::Array[String])
|
20
|
+
@spec_class_stack = T.let([], T::Array[T::Boolean])
|
21
|
+
|
22
|
+
dispatcher.register(
|
23
|
+
self,
|
24
|
+
:on_class_node_enter,
|
25
|
+
:on_class_node_leave,
|
26
|
+
:on_module_node_enter,
|
27
|
+
:on_module_node_leave,
|
28
|
+
:on_call_node_enter, # e.g. `describe` or `it`
|
29
|
+
:on_call_node_leave,
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
#: (node: Prism::ClassNode) -> void
|
34
|
+
def on_class_node_enter(node)
|
35
|
+
@visibility_stack << :public
|
36
|
+
name = constant_name(node.constant_path)
|
37
|
+
name ||= name_with_dynamic_reference(node.constant_path)
|
38
|
+
|
39
|
+
fully_qualified_name = RubyIndexer::Index.actual_nesting(@nesting, name).join("::")
|
40
|
+
|
41
|
+
attached_ancestors = begin
|
42
|
+
@index.linearized_ancestors_of(fully_qualified_name)
|
43
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
44
|
+
# When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still
|
45
|
+
# provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test
|
46
|
+
[node.superclass&.slice].compact
|
47
|
+
end
|
48
|
+
|
49
|
+
is_spec = attached_ancestors.include?("Minitest::Spec")
|
50
|
+
@spec_class_stack.push(is_spec)
|
51
|
+
|
52
|
+
@nesting << name
|
53
|
+
end
|
54
|
+
|
55
|
+
#: (node: Prism::ModuleNode) -> void
|
56
|
+
def on_module_node_enter(node)
|
57
|
+
@visibility_stack << :public
|
58
|
+
|
59
|
+
name = constant_name(node.constant_path)
|
60
|
+
name ||= name_with_dynamic_reference(node.constant_path)
|
61
|
+
|
62
|
+
@nesting << name
|
63
|
+
end
|
64
|
+
|
65
|
+
#: (node: Prism::ModuleNode) -> void
|
66
|
+
def on_module_node_leave(node)
|
67
|
+
@visibility_stack.pop
|
68
|
+
@nesting.pop
|
69
|
+
end
|
70
|
+
|
71
|
+
#: (node: Prism::ClassNode) -> void
|
72
|
+
def on_class_node_leave(node)
|
73
|
+
@visibility_stack.pop
|
74
|
+
@nesting.pop
|
75
|
+
@spec_class_stack.pop
|
76
|
+
end
|
77
|
+
|
78
|
+
#: (node: Prism::CallNode) -> void
|
79
|
+
def on_call_node_enter(node)
|
80
|
+
case node.name
|
81
|
+
when :describe
|
82
|
+
handle_describe(node)
|
83
|
+
when :it, :specify
|
84
|
+
handle_example(node)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
#: (node: Prism::CallNode) -> void
|
89
|
+
def on_call_node_leave(node)
|
90
|
+
return unless node.name == :describe && !node.receiver
|
91
|
+
|
92
|
+
@describe_block_nesting.pop
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
#: (node: Prism::CallNode) -> void
|
98
|
+
def handle_describe(node)
|
99
|
+
return if node.block.nil?
|
100
|
+
|
101
|
+
description = extract_description(node)
|
102
|
+
return unless description
|
103
|
+
|
104
|
+
return unless in_spec_context?
|
105
|
+
|
106
|
+
if @nesting.empty? && @describe_block_nesting.empty?
|
107
|
+
test_item = Requests::Support::TestItem.new(
|
108
|
+
description,
|
109
|
+
description,
|
110
|
+
@uri,
|
111
|
+
range_from_node(node),
|
112
|
+
tags: [:minitest],
|
113
|
+
)
|
114
|
+
@response_builder.add(test_item)
|
115
|
+
else
|
116
|
+
add_to_parent_test_group(description, node)
|
117
|
+
end
|
118
|
+
|
119
|
+
@describe_block_nesting << description
|
120
|
+
end
|
121
|
+
|
122
|
+
#: (node: Prism::CallNode) -> void
|
123
|
+
def handle_example(node)
|
124
|
+
return unless in_spec_context?
|
125
|
+
|
126
|
+
return if @describe_block_nesting.empty? && @nesting.empty?
|
127
|
+
|
128
|
+
description = extract_description(node)
|
129
|
+
return unless description
|
130
|
+
|
131
|
+
add_to_parent_test_group(description, node)
|
132
|
+
end
|
133
|
+
|
134
|
+
#: (description: String, node: Prism::CallNode) -> void
|
135
|
+
def add_to_parent_test_group(description, node)
|
136
|
+
parent_test_group = find_parent_test_group
|
137
|
+
return unless parent_test_group
|
138
|
+
|
139
|
+
test_item = Requests::Support::TestItem.new(
|
140
|
+
description,
|
141
|
+
description,
|
142
|
+
@uri,
|
143
|
+
range_from_node(node),
|
144
|
+
tags: [:minitest],
|
145
|
+
)
|
146
|
+
parent_test_group.add(test_item)
|
147
|
+
end
|
148
|
+
|
149
|
+
#: -> Requests::Support::TestItem?
|
150
|
+
def find_parent_test_group
|
151
|
+
root_group_name, nested_describe_groups = if @nesting.empty?
|
152
|
+
[@describe_block_nesting.first, @describe_block_nesting[1..]]
|
153
|
+
else
|
154
|
+
[RubyIndexer::Index.actual_nesting(@nesting, nil).join("::"), @describe_block_nesting]
|
155
|
+
end
|
156
|
+
return unless root_group_name
|
157
|
+
|
158
|
+
test_group = T.let(@response_builder[root_group_name], T.nilable(Requests::Support::TestItem))
|
159
|
+
return unless test_group
|
160
|
+
|
161
|
+
return test_group unless nested_describe_groups
|
162
|
+
|
163
|
+
nested_describe_groups.each do |description|
|
164
|
+
test_group = test_group[description]
|
165
|
+
end
|
166
|
+
|
167
|
+
test_group
|
168
|
+
end
|
169
|
+
|
170
|
+
#: (node: Prism::CallNode) -> String?
|
171
|
+
def extract_description(node)
|
172
|
+
first_argument = node.arguments&.arguments&.first
|
173
|
+
return unless first_argument
|
174
|
+
|
175
|
+
case first_argument
|
176
|
+
when Prism::StringNode
|
177
|
+
first_argument.content
|
178
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
179
|
+
constant_name(first_argument)
|
180
|
+
else
|
181
|
+
first_argument.slice
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
#: -> bool
|
186
|
+
def in_spec_context?
|
187
|
+
return true if @nesting.empty?
|
188
|
+
|
189
|
+
T.must(@spec_class_stack.last)
|
190
|
+
end
|
191
|
+
|
192
|
+
#: (node: Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode) -> String
|
193
|
+
def name_with_dynamic_reference(node)
|
194
|
+
slice = node.slice
|
195
|
+
slice.gsub(/((?<=::)|^)[a-z]\w*/, DYNAMIC_REFERENCE_MARKER)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Listeners
|
6
|
+
class TestStyle
|
7
|
+
class << self
|
8
|
+
# Resolves the minimal set of commands required to execute the requested tests
|
9
|
+
#: (Array[Hash[Symbol, untyped]]) -> Array[String]
|
10
|
+
def resolve_test_commands(items)
|
11
|
+
# A nested hash of file_path => test_group => { tags: [], examples: [test_example] } to ensure we build the
|
12
|
+
# minimum amount of commands needed to execute the requested tests. This is only used for specific examples
|
13
|
+
# where we will need more complex regexes to execute it all at the same time
|
14
|
+
aggregated_tests = Hash.new do |hash, key|
|
15
|
+
hash[key] = Hash.new do |inner_h, inner_k|
|
16
|
+
inner_h[inner_k] = { tags: Set.new, examples: [] }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Full files are paths that should be executed as a whole e.g.: an entire test file or directory
|
21
|
+
full_files = []
|
22
|
+
queue = items.dup
|
23
|
+
|
24
|
+
until queue.empty?
|
25
|
+
item = T.must(queue.shift)
|
26
|
+
tags = Set.new(item[:tags])
|
27
|
+
|
28
|
+
children = item[:children]
|
29
|
+
uri = URI(item[:uri])
|
30
|
+
path = uri.full_path
|
31
|
+
next unless path
|
32
|
+
|
33
|
+
if tags.include?("test_dir")
|
34
|
+
full_files << "#{path}/**/*" if children.empty?
|
35
|
+
elsif tags.include?("test_file")
|
36
|
+
full_files << path if children.empty?
|
37
|
+
elsif tags.include?("test_group")
|
38
|
+
# If all of the children of the current test group are other groups, then there's no need to add it to the
|
39
|
+
# aggregated examples
|
40
|
+
unless children.any? && children.all? { |child| child[:tags].include?("test_group") }
|
41
|
+
aggregated_tests[path][item[:label]] = { tags: tags, examples: [] }
|
42
|
+
end
|
43
|
+
elsif tags.include?("minitest") || tags.include?("test_unit")
|
44
|
+
class_name, method_name = item[:id].split("#")
|
45
|
+
aggregated_tests[path][class_name][:examples] << method_name
|
46
|
+
aggregated_tests[path][class_name][:tags].merge(tags)
|
47
|
+
end
|
48
|
+
|
49
|
+
queue.concat(children) unless children.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
commands = []
|
53
|
+
|
54
|
+
aggregated_tests.each do |file_path, groups_and_examples|
|
55
|
+
# Separate groups into Minitest and Test Unit. You can have both frameworks in the same file, but you cannot
|
56
|
+
# have a group belongs to both at the same time
|
57
|
+
minitest_groups, test_unit_groups = groups_and_examples.partition do |_, info|
|
58
|
+
info[:tags].include?("minitest")
|
59
|
+
end
|
60
|
+
|
61
|
+
if minitest_groups.any?
|
62
|
+
commands << handle_minitest_groups(file_path, minitest_groups)
|
63
|
+
end
|
64
|
+
|
65
|
+
if test_unit_groups.any?
|
66
|
+
commands.concat(handle_test_unit_groups(file_path, test_unit_groups))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
commands << "#{BASE_COMMAND} -Itest #{full_files.join(" ")}" unless full_files.empty?
|
71
|
+
commands
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
#: (String, Hash[String, Hash[Symbol, untyped]]) -> String
|
77
|
+
def handle_minitest_groups(file_path, groups_and_examples)
|
78
|
+
regexes = groups_and_examples.flat_map do |group, info|
|
79
|
+
examples = info[:examples]
|
80
|
+
group_regex = Shellwords.escape(group).gsub(Shellwords.escape(DYNAMIC_REFERENCE_MARKER), ".*")
|
81
|
+
if examples.empty?
|
82
|
+
"^#{group_regex}(#|::)"
|
83
|
+
elsif examples.length == 1
|
84
|
+
"^#{group_regex}##{examples[0]}$"
|
85
|
+
else
|
86
|
+
"^#{group_regex}#(#{examples.join("|")})$"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
regex = if regexes.length == 1
|
91
|
+
regexes[0]
|
92
|
+
else
|
93
|
+
"(#{regexes.join("|")})"
|
94
|
+
end
|
95
|
+
|
96
|
+
"#{BASE_COMMAND} -Itest #{file_path} --name \"/#{regex}/\""
|
97
|
+
end
|
98
|
+
|
99
|
+
#: (String, Hash[String, Hash[Symbol, untyped]]) -> Array[String]
|
100
|
+
def handle_test_unit_groups(file_path, groups_and_examples)
|
101
|
+
groups_and_examples.map do |group, info|
|
102
|
+
examples = info[:examples]
|
103
|
+
group_regex = Shellwords.escape(group).gsub(Shellwords.escape(DYNAMIC_REFERENCE_MARKER), ".*")
|
104
|
+
command = +"#{BASE_COMMAND} -Itest #{file_path} --testcase \"/^#{group_regex}$/\""
|
105
|
+
|
106
|
+
unless examples.empty?
|
107
|
+
command << if examples.length == 1
|
108
|
+
" --name \"/#{examples[0]}$/\""
|
109
|
+
else
|
110
|
+
" --name \"/(#{examples.join("|")})$/\""
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
command
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
include Requests::Support::Common
|
120
|
+
|
121
|
+
ACCESS_MODIFIERS = [:public, :private, :protected].freeze
|
122
|
+
DYNAMIC_REFERENCE_MARKER = "<dynamic_reference>"
|
123
|
+
BASE_COMMAND = T.let(
|
124
|
+
begin
|
125
|
+
Bundler.with_original_env { Bundler.default_lockfile }
|
126
|
+
"bundle exec ruby"
|
127
|
+
rescue Bundler::GemfileNotFound
|
128
|
+
"ruby"
|
129
|
+
end,
|
130
|
+
String,
|
131
|
+
)
|
132
|
+
|
133
|
+
#: (ResponseBuilders::TestCollection response_builder, GlobalState global_state, Prism::Dispatcher dispatcher, URI::Generic uri) -> void
|
134
|
+
def initialize(response_builder, global_state, dispatcher, uri)
|
135
|
+
@response_builder = response_builder
|
136
|
+
@uri = uri
|
137
|
+
@index = T.let(global_state.index, RubyIndexer::Index)
|
138
|
+
@visibility_stack = T.let([:public], T::Array[Symbol])
|
139
|
+
@nesting = T.let([], T::Array[String])
|
140
|
+
@framework_tag = T.let(:minitest, Symbol)
|
141
|
+
|
142
|
+
dispatcher.register(
|
143
|
+
self,
|
144
|
+
:on_class_node_enter,
|
145
|
+
:on_class_node_leave,
|
146
|
+
:on_module_node_enter,
|
147
|
+
:on_module_node_leave,
|
148
|
+
:on_def_node_enter,
|
149
|
+
:on_call_node_enter,
|
150
|
+
:on_call_node_leave,
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
154
|
+
#: (Prism::ClassNode node) -> void
|
155
|
+
def on_class_node_enter(node)
|
156
|
+
@visibility_stack << :public
|
157
|
+
name = constant_name(node.constant_path)
|
158
|
+
name ||= name_with_dynamic_reference(node.constant_path)
|
159
|
+
|
160
|
+
fully_qualified_name = RubyIndexer::Index.actual_nesting(@nesting, name).join("::")
|
161
|
+
|
162
|
+
attached_ancestors = begin
|
163
|
+
@index.linearized_ancestors_of(fully_qualified_name)
|
164
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
165
|
+
# When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still
|
166
|
+
# provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test
|
167
|
+
[node.superclass&.slice].compact
|
168
|
+
end
|
169
|
+
|
170
|
+
@framework_tag = :test_unit if attached_ancestors.include?("Test::Unit::TestCase")
|
171
|
+
|
172
|
+
if @framework_tag == :test_unit || non_declarative_minitest?(attached_ancestors, fully_qualified_name)
|
173
|
+
@response_builder.add(Requests::Support::TestItem.new(
|
174
|
+
fully_qualified_name,
|
175
|
+
fully_qualified_name,
|
176
|
+
@uri,
|
177
|
+
range_from_node(node),
|
178
|
+
tags: [@framework_tag],
|
179
|
+
))
|
180
|
+
end
|
181
|
+
|
182
|
+
@nesting << name
|
183
|
+
end
|
184
|
+
|
185
|
+
#: (Prism::ModuleNode node) -> void
|
186
|
+
def on_module_node_enter(node)
|
187
|
+
@visibility_stack << :public
|
188
|
+
|
189
|
+
name = constant_name(node.constant_path)
|
190
|
+
name ||= name_with_dynamic_reference(node.constant_path)
|
191
|
+
|
192
|
+
@nesting << name
|
193
|
+
end
|
194
|
+
|
195
|
+
#: (Prism::ModuleNode node) -> void
|
196
|
+
def on_module_node_leave(node)
|
197
|
+
@visibility_stack.pop
|
198
|
+
@nesting.pop
|
199
|
+
end
|
200
|
+
|
201
|
+
#: (Prism::ClassNode node) -> void
|
202
|
+
def on_class_node_leave(node)
|
203
|
+
@visibility_stack.pop
|
204
|
+
@nesting.pop
|
205
|
+
end
|
206
|
+
|
207
|
+
#: (Prism::DefNode node) -> void
|
208
|
+
def on_def_node_enter(node)
|
209
|
+
return if @visibility_stack.last != :public
|
210
|
+
|
211
|
+
name = node.name.to_s
|
212
|
+
return unless name.start_with?("test_")
|
213
|
+
|
214
|
+
current_group_name = RubyIndexer::Index.actual_nesting(@nesting, nil).join("::")
|
215
|
+
|
216
|
+
# If we're finding a test method, but for the wrong framework, then the group test item will not have been
|
217
|
+
# previously pushed and thus we return early and avoid adding items for a framework this listener is not
|
218
|
+
# interested in
|
219
|
+
test_item = @response_builder[current_group_name]
|
220
|
+
return unless test_item
|
221
|
+
|
222
|
+
test_item.add(Requests::Support::TestItem.new(
|
223
|
+
"#{current_group_name}##{name}",
|
224
|
+
name,
|
225
|
+
@uri,
|
226
|
+
range_from_node(node),
|
227
|
+
tags: [@framework_tag],
|
228
|
+
))
|
229
|
+
end
|
230
|
+
|
231
|
+
#: (Prism::CallNode node) -> void
|
232
|
+
def on_call_node_enter(node)
|
233
|
+
name = node.name
|
234
|
+
return unless ACCESS_MODIFIERS.include?(name)
|
235
|
+
|
236
|
+
@visibility_stack << name
|
237
|
+
end
|
238
|
+
|
239
|
+
#: (Prism::CallNode node) -> void
|
240
|
+
def on_call_node_leave(node)
|
241
|
+
name = node.name
|
242
|
+
return unless ACCESS_MODIFIERS.include?(name)
|
243
|
+
return unless node.arguments&.arguments
|
244
|
+
|
245
|
+
@visibility_stack.pop
|
246
|
+
end
|
247
|
+
|
248
|
+
private
|
249
|
+
|
250
|
+
#: (Array[String] attached_ancestors, String fully_qualified_name) -> bool
|
251
|
+
def non_declarative_minitest?(attached_ancestors, fully_qualified_name)
|
252
|
+
return false unless attached_ancestors.include?("Minitest::Test")
|
253
|
+
|
254
|
+
# We only support regular Minitest tests. The declarative syntax provided by ActiveSupport is handled by the
|
255
|
+
# Rails add-on
|
256
|
+
name_parts = fully_qualified_name.split("::")
|
257
|
+
singleton_name = "#{name_parts.join("::")}::<Class:#{name_parts.last}>"
|
258
|
+
!@index.linearized_ancestors_of(singleton_name).include?("ActiveSupport::Testing::Declarative")
|
259
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
260
|
+
true
|
261
|
+
end
|
262
|
+
|
263
|
+
#: ((Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode) node) -> String
|
264
|
+
def name_with_dynamic_reference(node)
|
265
|
+
slice = node.slice
|
266
|
+
slice.gsub(/((?<=::)|^)[a-z]\w*/, DYNAMIC_REFERENCE_MARKER)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -5,36 +5,19 @@ module RubyLsp
|
|
5
5
|
# This class allows listeners to access contextual information about a node in the AST, such as its parent,
|
6
6
|
# its namespace nesting, and the surrounding CallNode (e.g. a method call).
|
7
7
|
class NodeContext
|
8
|
-
|
9
|
-
|
10
|
-
sig { returns(T.nilable(Prism::Node)) }
|
8
|
+
#: Prism::Node?
|
11
9
|
attr_reader :node, :parent
|
12
10
|
|
13
|
-
|
11
|
+
#: Array[String]
|
14
12
|
attr_reader :nesting
|
15
13
|
|
16
|
-
|
14
|
+
#: Prism::CallNode?
|
17
15
|
attr_reader :call_node
|
18
16
|
|
19
|
-
|
17
|
+
#: String?
|
20
18
|
attr_reader :surrounding_method
|
21
19
|
|
22
|
-
|
23
|
-
params(
|
24
|
-
node: T.nilable(Prism::Node),
|
25
|
-
parent: T.nilable(Prism::Node),
|
26
|
-
nesting_nodes: T::Array[T.any(
|
27
|
-
Prism::ClassNode,
|
28
|
-
Prism::ModuleNode,
|
29
|
-
Prism::SingletonClassNode,
|
30
|
-
Prism::DefNode,
|
31
|
-
Prism::BlockNode,
|
32
|
-
Prism::LambdaNode,
|
33
|
-
Prism::ProgramNode,
|
34
|
-
)],
|
35
|
-
call_node: T.nilable(Prism::CallNode),
|
36
|
-
).void
|
37
|
-
end
|
20
|
+
#: (Prism::Node? node, Prism::Node? parent, Array[(Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode | Prism::DefNode | Prism::BlockNode | Prism::LambdaNode | Prism::ProgramNode)] nesting_nodes, Prism::CallNode? call_node) -> void
|
38
21
|
def initialize(node, parent, nesting_nodes, call_node)
|
39
22
|
@node = node
|
40
23
|
@parent = parent
|
@@ -46,12 +29,12 @@ module RubyLsp
|
|
46
29
|
@surrounding_method = T.let(surrounding_method, T.nilable(String))
|
47
30
|
end
|
48
31
|
|
49
|
-
|
32
|
+
#: -> String
|
50
33
|
def fully_qualified_name
|
51
34
|
@fully_qualified_name ||= T.let(@nesting.join("::"), T.nilable(String))
|
52
35
|
end
|
53
36
|
|
54
|
-
|
37
|
+
#: -> Array[Symbol]
|
55
38
|
def locals_for_scope
|
56
39
|
locals = []
|
57
40
|
|
@@ -69,17 +52,7 @@ module RubyLsp
|
|
69
52
|
|
70
53
|
private
|
71
54
|
|
72
|
-
|
73
|
-
params(nodes: T::Array[T.any(
|
74
|
-
Prism::ClassNode,
|
75
|
-
Prism::ModuleNode,
|
76
|
-
Prism::SingletonClassNode,
|
77
|
-
Prism::DefNode,
|
78
|
-
Prism::BlockNode,
|
79
|
-
Prism::LambdaNode,
|
80
|
-
Prism::ProgramNode,
|
81
|
-
)]).returns([T::Array[String], T.nilable(String)])
|
82
|
-
end
|
55
|
+
#: (Array[(Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode | Prism::DefNode | Prism::BlockNode | Prism::LambdaNode | Prism::ProgramNode)] nodes) -> [Array[String], String?]
|
83
56
|
def handle_nesting_nodes(nodes)
|
84
57
|
nesting = []
|
85
58
|
surrounding_method = T.let(nil, T.nilable(String))
|
@@ -3,18 +3,18 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
class RBSDocument < Document
|
6
|
-
extend T::Sig
|
7
6
|
extend T::Generic
|
8
7
|
|
9
8
|
ParseResultType = type_member { { fixed: T::Array[RBS::AST::Declarations::Base] } }
|
10
9
|
|
11
|
-
|
10
|
+
#: (source: String, version: Integer, uri: URI::Generic, global_state: GlobalState) -> void
|
12
11
|
def initialize(source:, version:, uri:, global_state:)
|
13
12
|
@syntax_error = T.let(false, T::Boolean)
|
14
13
|
super
|
15
14
|
end
|
16
15
|
|
17
|
-
|
16
|
+
# @override
|
17
|
+
#: -> bool
|
18
18
|
def parse!
|
19
19
|
return false unless @needs_parsing
|
20
20
|
|
@@ -29,12 +29,14 @@ module RubyLsp
|
|
29
29
|
true
|
30
30
|
end
|
31
31
|
|
32
|
-
|
32
|
+
# @override
|
33
|
+
#: -> bool
|
33
34
|
def syntax_error?
|
34
35
|
@syntax_error
|
35
36
|
end
|
36
37
|
|
37
|
-
|
38
|
+
# @override
|
39
|
+
#: -> LanguageId
|
38
40
|
def language_id
|
39
41
|
LanguageId::RBS
|
40
42
|
end
|
@@ -7,7 +7,6 @@ module RubyLsp
|
|
7
7
|
# request is used to to resolve the edit field for a given code action, if it is not already provided in the
|
8
8
|
# textDocument/codeAction response. We can use it for scenarios that require more computation such as refactoring.
|
9
9
|
class CodeActionResolve < Request
|
10
|
-
extend T::Sig
|
11
10
|
include Support::Common
|
12
11
|
|
13
12
|
NEW_VARIABLE_NAME = "new_variable"
|
@@ -23,7 +22,7 @@ module RubyLsp
|
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
26
|
-
|
25
|
+
#: (RubyDocument document, GlobalState global_state, Hash[Symbol, untyped] code_action) -> void
|
27
26
|
def initialize(document, global_state, code_action)
|
28
27
|
super()
|
29
28
|
@document = document
|
@@ -31,7 +30,8 @@ module RubyLsp
|
|
31
30
|
@code_action = code_action
|
32
31
|
end
|
33
32
|
|
34
|
-
|
33
|
+
# @override
|
34
|
+
#: -> (Interface::CodeAction | Error)
|
35
35
|
def perform
|
36
36
|
return Error::EmptySelection if @document.source.empty?
|
37
37
|
|
@@ -53,7 +53,7 @@ module RubyLsp
|
|
53
53
|
|
54
54
|
private
|
55
55
|
|
56
|
-
|
56
|
+
#: -> (Interface::CodeAction | Error)
|
57
57
|
def switch_block_style
|
58
58
|
source_range = @code_action.dig(:data, :range)
|
59
59
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
@@ -91,7 +91,7 @@ module RubyLsp
|
|
91
91
|
)
|
92
92
|
end
|
93
93
|
|
94
|
-
|
94
|
+
#: -> (Interface::CodeAction | Error)
|
95
95
|
def refactor_variable
|
96
96
|
source_range = @code_action.dig(:data, :range)
|
97
97
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
@@ -189,7 +189,7 @@ module RubyLsp
|
|
189
189
|
)
|
190
190
|
end
|
191
191
|
|
192
|
-
|
192
|
+
#: -> (Interface::CodeAction | Error)
|
193
193
|
def refactor_method
|
194
194
|
source_range = @code_action.dig(:data, :range)
|
195
195
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
@@ -261,7 +261,7 @@ module RubyLsp
|
|
261
261
|
)
|
262
262
|
end
|
263
263
|
|
264
|
-
|
264
|
+
#: (Hash[Symbol, untyped] range, String new_text) -> Interface::TextEdit
|
265
265
|
def create_text_edit(range, new_text)
|
266
266
|
Interface::TextEdit.new(
|
267
267
|
range: Interface::Range.new(
|
@@ -272,7 +272,7 @@ module RubyLsp
|
|
272
272
|
)
|
273
273
|
end
|
274
274
|
|
275
|
-
|
275
|
+
#: (Prism::BlockNode node, String? indentation) -> String
|
276
276
|
def recursively_switch_nested_block_styles(node, indentation)
|
277
277
|
parameters = node.parameters
|
278
278
|
body = node.body
|
@@ -301,7 +301,7 @@ module RubyLsp
|
|
301
301
|
source
|
302
302
|
end
|
303
303
|
|
304
|
-
|
304
|
+
#: (Prism::Node body, String? indentation) -> String
|
305
305
|
def switch_block_body(body, indentation)
|
306
306
|
# Check if there are any nested blocks inside of the current block
|
307
307
|
body_loc = body.location
|
@@ -330,7 +330,7 @@ module RubyLsp
|
|
330
330
|
indentation ? body_content.gsub(";", "\n") : "#{body_content.gsub("\n", ";")} "
|
331
331
|
end
|
332
332
|
|
333
|
-
|
333
|
+
#: -> (Interface::CodeAction | Error)
|
334
334
|
def create_attribute_accessor
|
335
335
|
source_range = @code_action.dig(:data, :range)
|
336
336
|
|