ruby-lsp 0.27.0.beta2 → 0.27.0.beta3
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 +0 -46
- data/exe/ruby-lsp-check +0 -15
- data/lib/ruby_lsp/global_state.rb +0 -5
- data/lib/ruby_lsp/internal.rb +2 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +1 -1
- data/lib/ruby_lsp/listeners/completion.rb +246 -382
- data/lib/ruby_lsp/listeners/definition.rb +6 -9
- data/lib/ruby_lsp/listeners/hover.rb +11 -9
- data/lib/ruby_lsp/listeners/signature_help.rb +11 -12
- data/lib/ruby_lsp/listeners/test_discovery.rb +17 -1
- data/lib/ruby_lsp/listeners/test_style.rb +1 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +49 -29
- data/lib/ruby_lsp/requests/references.rb +21 -7
- data/lib/ruby_lsp/requests/rename.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +69 -68
- data/lib/ruby_lsp/ruby_document.rb +0 -73
- data/lib/ruby_lsp/rubydex/declaration.rb +128 -2
- data/lib/ruby_lsp/rubydex/definition.rb +16 -0
- data/lib/ruby_lsp/rubydex/signature.rb +107 -0
- data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
- data/lib/ruby_lsp/server.rb +7 -162
- data/lib/ruby_lsp/test_helper.rb +0 -1
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +2 -2
- metadata +11 -14
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +0 -276
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +0 -1101
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +0 -44
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +0 -605
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +0 -1077
- data/lib/ruby_indexer/lib/ruby_indexer/location.rb +0 -37
- data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +0 -149
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +0 -294
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +0 -32
- data/lib/ruby_indexer/ruby_indexer.rb +0 -19
- /data/lib/{ruby_indexer/lib/ruby_indexer → ruby_lsp}/uri.rb +0 -0
|
@@ -106,7 +106,7 @@ module RubyLsp
|
|
|
106
106
|
|
|
107
107
|
#: (Prism::ConstantPathNode node) -> void
|
|
108
108
|
def on_constant_path_node_enter(node)
|
|
109
|
-
name =
|
|
109
|
+
name = constant_name(node)
|
|
110
110
|
return if name.nil?
|
|
111
111
|
|
|
112
112
|
handle_constant_definition(name)
|
|
@@ -114,7 +114,7 @@ module RubyLsp
|
|
|
114
114
|
|
|
115
115
|
#: (Prism::ConstantReadNode node) -> void
|
|
116
116
|
def on_constant_read_node_enter(node)
|
|
117
|
-
name =
|
|
117
|
+
name = constant_name(node)
|
|
118
118
|
return if name.nil?
|
|
119
119
|
|
|
120
120
|
handle_constant_definition(name)
|
|
@@ -302,6 +302,9 @@ module RubyLsp
|
|
|
302
302
|
return unless declaration
|
|
303
303
|
|
|
304
304
|
Array(declaration).each do |decl|
|
|
305
|
+
next if decl.is_a?(Rubydex::Method) &&
|
|
306
|
+
!method_reachable_from_call_site?(decl, receiver_type, @graph, @node_context)
|
|
307
|
+
|
|
305
308
|
decl.definitions.each do |definition|
|
|
306
309
|
location = definition.location
|
|
307
310
|
uri = URI(location.uri)
|
|
@@ -367,13 +370,7 @@ module RubyLsp
|
|
|
367
370
|
def handle_constant_definition(value)
|
|
368
371
|
declaration = @graph.resolve_constant(value, @node_context.nesting)
|
|
369
372
|
return unless declaration
|
|
370
|
-
|
|
371
|
-
# [RUBYDEX] TODO: temporarily commented out until we have the visibility API
|
|
372
|
-
#
|
|
373
|
-
# We should only allow jumping to the definition of private constants if the constant is defined in the same
|
|
374
|
-
# namespace as the reference
|
|
375
|
-
#
|
|
376
|
-
# return if declaration.private? && declaration.name != "#{@node_context.fully_qualified_name}::#{value}"
|
|
373
|
+
return unless constant_reachable_from_call_site?(declaration, value, @node_context)
|
|
377
374
|
|
|
378
375
|
declaration.definitions.each do |definition|
|
|
379
376
|
# If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
|
|
@@ -15,7 +15,6 @@ module RubyLsp
|
|
|
15
15
|
def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level, position) # rubocop:disable Metrics/ParameterLists
|
|
16
16
|
@response_builder = response_builder
|
|
17
17
|
@global_state = global_state
|
|
18
|
-
@index = global_state.index #: RubyIndexer::Index
|
|
19
18
|
@graph = global_state.graph #: Rubydex::Graph
|
|
20
19
|
@type_inferrer = global_state.type_inferrer #: TypeInferrer
|
|
21
20
|
@path = uri.to_standardized_path #: String?
|
|
@@ -117,7 +116,7 @@ module RubyLsp
|
|
|
117
116
|
def on_constant_read_node_enter(node)
|
|
118
117
|
return unless @sorbet_level.ignore?
|
|
119
118
|
|
|
120
|
-
name =
|
|
119
|
+
name = constant_name(node)
|
|
121
120
|
return if name.nil?
|
|
122
121
|
|
|
123
122
|
generate_hover(name, node.location)
|
|
@@ -134,7 +133,7 @@ module RubyLsp
|
|
|
134
133
|
def on_constant_path_node_enter(node)
|
|
135
134
|
return unless @sorbet_level.ignore?
|
|
136
135
|
|
|
137
|
-
name =
|
|
136
|
+
name = constant_name(node)
|
|
138
137
|
return if name.nil?
|
|
139
138
|
|
|
140
139
|
generate_hover(name, node.location)
|
|
@@ -457,20 +456,22 @@ module RubyLsp
|
|
|
457
456
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
|
458
457
|
return unless type
|
|
459
458
|
|
|
460
|
-
|
|
461
|
-
return unless
|
|
459
|
+
owner = @graph[type.name]
|
|
460
|
+
return unless owner.is_a?(Rubydex::Namespace)
|
|
462
461
|
|
|
463
|
-
|
|
462
|
+
method = owner.find_member("#{message}()", only_inherited: inherited_only)
|
|
463
|
+
return unless method.is_a?(Rubydex::Method)
|
|
464
|
+
return unless method_reachable_from_call_site?(method, type, @graph, @node_context)
|
|
464
465
|
|
|
465
|
-
title = "#{message}#{
|
|
466
|
-
title <<
|
|
466
|
+
title = +"#{message}#{method.decorated_parameters}"
|
|
467
|
+
title << method.formatted_signatures
|
|
467
468
|
|
|
468
469
|
if type.is_a?(TypeInferrer::GuessedType)
|
|
469
470
|
title << "\n\nGuessed receiver: #{type.name}"
|
|
470
471
|
@response_builder.push("[Learn more about guessed types](#{GUESSED_TYPES_URL})\n", category: :links)
|
|
471
472
|
end
|
|
472
473
|
|
|
473
|
-
|
|
474
|
+
categorized_markdown_from_definitions(title, method.definitions).each do |category, content|
|
|
474
475
|
@response_builder.push(content, category: category)
|
|
475
476
|
end
|
|
476
477
|
end
|
|
@@ -513,6 +514,7 @@ module RubyLsp
|
|
|
513
514
|
def generate_hover(name, location)
|
|
514
515
|
declaration = @graph.resolve_constant(name, @node_context.nesting)
|
|
515
516
|
return unless declaration
|
|
517
|
+
return unless constant_reachable_from_call_site?(declaration, name, @node_context)
|
|
516
518
|
|
|
517
519
|
categorized_markdown_from_definitions(declaration.name, declaration.definitions).each do |category, content|
|
|
518
520
|
@response_builder.push(content, category: category)
|
|
@@ -11,7 +11,7 @@ module RubyLsp
|
|
|
11
11
|
@sorbet_level = sorbet_level
|
|
12
12
|
@response_builder = response_builder
|
|
13
13
|
@global_state = global_state
|
|
14
|
-
@
|
|
14
|
+
@graph = global_state.graph #: Rubydex::Graph
|
|
15
15
|
@type_inferrer = global_state.type_inferrer #: TypeInferrer
|
|
16
16
|
@node_context = node_context
|
|
17
17
|
dispatcher.register(self, :on_call_node_enter)
|
|
@@ -27,18 +27,17 @@ module RubyLsp
|
|
|
27
27
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
|
28
28
|
return unless type
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
return unless
|
|
30
|
+
owner = @graph[type.name]
|
|
31
|
+
return unless owner.is_a?(Rubydex::Namespace)
|
|
32
32
|
|
|
33
|
-
target_method =
|
|
34
|
-
return unless target_method
|
|
33
|
+
target_method = owner.find_member("#{message}()")
|
|
34
|
+
return unless target_method.is_a?(Rubydex::Method)
|
|
35
35
|
|
|
36
36
|
signatures = target_method.signatures
|
|
37
37
|
|
|
38
|
-
# If the method doesn't have any
|
|
38
|
+
# If the method doesn't have any signatures, there's nothing to show
|
|
39
39
|
return if signatures.empty?
|
|
40
40
|
|
|
41
|
-
name = target_method.name
|
|
42
41
|
title = +""
|
|
43
42
|
|
|
44
43
|
extra_links = if type.is_a?(TypeInferrer::GuessedType)
|
|
@@ -49,7 +48,7 @@ module RubyLsp
|
|
|
49
48
|
active_signature, active_parameter = determine_active_signature_and_parameter(node, signatures)
|
|
50
49
|
|
|
51
50
|
signature_help = Interface::SignatureHelp.new(
|
|
52
|
-
signatures: generate_signatures(signatures,
|
|
51
|
+
signatures: generate_signatures(signatures, message, target_method, title, extra_links),
|
|
53
52
|
active_signature: active_signature,
|
|
54
53
|
active_parameter: active_parameter,
|
|
55
54
|
)
|
|
@@ -58,7 +57,7 @@ module RubyLsp
|
|
|
58
57
|
|
|
59
58
|
private
|
|
60
59
|
|
|
61
|
-
#: (Prism::CallNode node, Array[
|
|
60
|
+
#: (Prism::CallNode node, Array[Rubydex::Signature] signatures) -> [Integer, Integer]
|
|
62
61
|
def determine_active_signature_and_parameter(node, signatures)
|
|
63
62
|
arguments_node = node.arguments
|
|
64
63
|
arguments = arguments_node&.arguments || []
|
|
@@ -86,15 +85,15 @@ module RubyLsp
|
|
|
86
85
|
[active_sig_index, active_parameter]
|
|
87
86
|
end
|
|
88
87
|
|
|
89
|
-
#: (Array[
|
|
90
|
-
def generate_signatures(signatures, method_name,
|
|
88
|
+
#: (Array[Rubydex::Signature] signatures, String method_name, Rubydex::Method method, String title, String? extra_links) -> Array[Interface::SignatureInformation]
|
|
89
|
+
def generate_signatures(signatures, method_name, method, title, extra_links)
|
|
91
90
|
signatures.map do |signature|
|
|
92
91
|
Interface::SignatureInformation.new(
|
|
93
92
|
label: "#{method_name}(#{signature.format})",
|
|
94
93
|
parameters: signature.parameters.map { |param| Interface::ParameterInformation.new(label: param.name) },
|
|
95
94
|
documentation: Interface::MarkupContent.new(
|
|
96
95
|
kind: "markdown",
|
|
97
|
-
value:
|
|
96
|
+
value: markdown_from_definitions(title, method.definitions, extra_links: extra_links),
|
|
98
97
|
),
|
|
99
98
|
)
|
|
100
99
|
end
|
|
@@ -56,7 +56,23 @@ module RubyLsp
|
|
|
56
56
|
|
|
57
57
|
#: (String? name) -> String
|
|
58
58
|
def calc_fully_qualified_name(name)
|
|
59
|
-
|
|
59
|
+
parts = name ? @nesting + [name] : @nesting
|
|
60
|
+
return "" if parts.empty?
|
|
61
|
+
|
|
62
|
+
last = parts.last #: as !nil
|
|
63
|
+
rest = parts[0...-1] #: as !nil
|
|
64
|
+
|
|
65
|
+
resolved = @graph.resolve_constant(last, rest)
|
|
66
|
+
return resolved.name if resolved
|
|
67
|
+
|
|
68
|
+
# Fallback for unresolved constants (e.g. dynamic references): preserve top-level reset semantics by
|
|
69
|
+
# truncating at the first `::`-prefixed part when scanning from the innermost out.
|
|
70
|
+
corrected = []
|
|
71
|
+
parts.reverse_each do |part|
|
|
72
|
+
corrected.prepend(part.delete_prefix("::"))
|
|
73
|
+
break if part.start_with?("::")
|
|
74
|
+
end
|
|
75
|
+
corrected.join("::")
|
|
60
76
|
end
|
|
61
77
|
|
|
62
78
|
#: (Prism::ClassNode node, String fully_qualified_name) -> Array[String]
|
|
@@ -220,7 +220,7 @@ module RubyLsp
|
|
|
220
220
|
name = node.name.to_s
|
|
221
221
|
return unless name.start_with?("test_")
|
|
222
222
|
|
|
223
|
-
current_group_name =
|
|
223
|
+
current_group_name = calc_fully_qualified_name(nil)
|
|
224
224
|
parent = @parent_stack.last
|
|
225
225
|
return unless parent.is_a?(Requests::Support::TestItem)
|
|
226
226
|
|
|
@@ -16,6 +16,12 @@ module RubyLsp
|
|
|
16
16
|
class CompletionResolve < Request
|
|
17
17
|
include Requests::Support::Common
|
|
18
18
|
|
|
19
|
+
METHOD_KINDS = [
|
|
20
|
+
Constant::CompletionItemKind::METHOD,
|
|
21
|
+
Constant::CompletionItemKind::CONSTRUCTOR,
|
|
22
|
+
Constant::CompletionItemKind::FUNCTION,
|
|
23
|
+
].freeze #: Array[Integer]
|
|
24
|
+
|
|
19
25
|
# set a limit on the number of documentation entries returned, to avoid rendering performance issues
|
|
20
26
|
# https://github.com/Shopify/ruby-lsp/pull/1798
|
|
21
27
|
MAX_DOCUMENTATION_ENTRIES = 10
|
|
@@ -23,7 +29,6 @@ module RubyLsp
|
|
|
23
29
|
#: (GlobalState global_state, Hash[Symbol, untyped] item) -> void
|
|
24
30
|
def initialize(global_state, item)
|
|
25
31
|
super()
|
|
26
|
-
@index = global_state.index #: RubyIndexer::Index
|
|
27
32
|
@graph = global_state.graph #: Rubydex::Graph
|
|
28
33
|
@item = item
|
|
29
34
|
end
|
|
@@ -32,6 +37,8 @@ module RubyLsp
|
|
|
32
37
|
#: -> Hash[Symbol, untyped]
|
|
33
38
|
def perform
|
|
34
39
|
return @item if @item.dig(:data, :skip_resolve)
|
|
40
|
+
return keyword_resolve if @item.dig(:data, :keyword)
|
|
41
|
+
return @item if @item[:kind] == Constant::CompletionItemKind::FILE
|
|
35
42
|
|
|
36
43
|
# Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
|
|
37
44
|
# a completion resolve request must always return the original completion item without modifying ANY fields
|
|
@@ -40,48 +47,61 @@ module RubyLsp
|
|
|
40
47
|
#
|
|
41
48
|
# For example, forgetting to return the `insertText` included in the original item will make the editor use the
|
|
42
49
|
# `label` for the text edit instead
|
|
43
|
-
|
|
44
|
-
return
|
|
50
|
+
declaration = resolve_declaration
|
|
51
|
+
return @item unless declaration
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
owner_name = @item.dig(:data, :owner_name)
|
|
49
|
-
|
|
50
|
-
if owner_name
|
|
51
|
-
entries = entries.select do |entry|
|
|
52
|
-
(entry.is_a?(RubyIndexer::Entry::Member) || entry.is_a?(RubyIndexer::Entry::InstanceVariable) ||
|
|
53
|
-
entry.is_a?(RubyIndexer::Entry::MethodAlias) || entry.is_a?(RubyIndexer::Entry::ClassVariable)) &&
|
|
54
|
-
entry.owner&.name == owner_name
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
first_entry = entries.first #: as !nil
|
|
53
|
+
guessed_type = @item.dig(:data, :guessed_type)
|
|
54
|
+
title = @item[:label].dup
|
|
59
55
|
|
|
60
|
-
if
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
if declaration.is_a?(Rubydex::Method)
|
|
57
|
+
title << declaration.decorated_parameters
|
|
58
|
+
title << declaration.formatted_signatures
|
|
63
59
|
end
|
|
64
60
|
|
|
65
|
-
guessed_type = @item.dig(:data, :guessed_type)
|
|
66
|
-
|
|
67
61
|
extra_links = if guessed_type
|
|
68
|
-
|
|
62
|
+
title << "\n\nGuessed receiver: #{guessed_type}"
|
|
69
63
|
"[Learn more about guessed types](#{GUESSED_TYPES_URL})"
|
|
70
64
|
end
|
|
71
65
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
66
|
+
@item[:documentation] = Interface::MarkupContent.new(
|
|
67
|
+
kind: "markdown",
|
|
68
|
+
value: markdown_from_definitions(
|
|
69
|
+
title,
|
|
70
|
+
declaration.definitions,
|
|
71
|
+
MAX_DOCUMENTATION_ENTRIES,
|
|
72
|
+
extra_links: extra_links,
|
|
73
|
+
),
|
|
74
|
+
)
|
|
78
75
|
|
|
79
76
|
@item
|
|
80
77
|
end
|
|
81
78
|
|
|
82
79
|
private
|
|
83
80
|
|
|
84
|
-
|
|
81
|
+
# Find the Rubydex declaration that matches the completion item. Constants are looked up by their fully qualified
|
|
82
|
+
# name (set when the completion was produced); members (methods, instance/class variables) are resolved by walking
|
|
83
|
+
# the owner namespace and its ancestors so that inherited and aliased members are surfaced correctly.
|
|
84
|
+
#: -> Rubydex::Declaration?
|
|
85
|
+
def resolve_declaration
|
|
86
|
+
data = @item[:data] || {}
|
|
87
|
+
|
|
88
|
+
if (fully_qualified_name = data[:fully_qualified_name])
|
|
89
|
+
@graph[fully_qualified_name]
|
|
90
|
+
elsif (owner_name = data[:owner_name])
|
|
91
|
+
owner = @graph[owner_name]
|
|
92
|
+
return unless owner.is_a?(Rubydex::Namespace)
|
|
93
|
+
|
|
94
|
+
member_name = if METHOD_KINDS.include?(@item[:kind])
|
|
95
|
+
"#{@item[:label]}()"
|
|
96
|
+
else
|
|
97
|
+
@item[:label]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
owner.find_member(member_name)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
#: -> Hash[Symbol, untyped]
|
|
85
105
|
def keyword_resolve
|
|
86
106
|
keyword = @graph.keyword(@item[:label])
|
|
87
107
|
|
|
@@ -145,7 +145,7 @@ module RubyLsp
|
|
|
145
145
|
end
|
|
146
146
|
return if declarations.empty?
|
|
147
147
|
|
|
148
|
-
collect_references(method_references_for(message), declarations, include_declarations)
|
|
148
|
+
collect_references(method_references_for(message, declarations), declarations, include_declarations)
|
|
149
149
|
end
|
|
150
150
|
|
|
151
151
|
# Handles instance and class variable references. Resolves the receiver type from the node context to locate
|
|
@@ -187,14 +187,28 @@ module RubyLsp
|
|
|
187
187
|
declaration = owner.find_member("#{method_name}()")
|
|
188
188
|
return unless declaration
|
|
189
189
|
|
|
190
|
-
collect_references(method_references_for(method_name), [declaration], include_declarations)
|
|
190
|
+
collect_references(method_references_for(method_name, [declaration]), [declaration], include_declarations)
|
|
191
191
|
end
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
193
|
+
#: (String, Array[Rubydex::Declaration]) -> Array[Rubydex::MethodReference]
|
|
194
|
+
def method_references_for(method_name, declarations)
|
|
195
|
+
target_owner_names = declarations.map do |d|
|
|
196
|
+
d.owner #: as Rubydex::Namespace
|
|
197
|
+
.name
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
@graph.method_references.select do |reference|
|
|
201
|
+
next false unless reference.name == method_name
|
|
202
|
+
|
|
203
|
+
receiver = reference.receiver
|
|
204
|
+
next true if receiver.nil? || target_owner_names.empty?
|
|
205
|
+
|
|
206
|
+
if receiver.is_a?(Rubydex::Namespace)
|
|
207
|
+
receiver.ancestors.any? { |ancestor| target_owner_names.include?(ancestor.name) }
|
|
208
|
+
else
|
|
209
|
+
target_owner_names.include?(receiver.name)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
198
212
|
end
|
|
199
213
|
|
|
200
214
|
#: (Enumerable[Rubydex::Reference] references, Array[Rubydex::Declaration] declarations, bool include_declarations) -> void
|
|
@@ -54,7 +54,7 @@ module RubyLsp
|
|
|
54
54
|
|
|
55
55
|
target = target #: as Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode
|
|
56
56
|
|
|
57
|
-
name =
|
|
57
|
+
name = constant_name(target)
|
|
58
58
|
return unless name
|
|
59
59
|
|
|
60
60
|
declaration = @graph.resolve_constant(name, node_context.nesting)
|
|
@@ -23,7 +23,7 @@ module RubyLsp
|
|
|
23
23
|
)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
#: ((Prism::Location |
|
|
26
|
+
#: ((Prism::Location | Rubydex::Location) location) -> Interface::Range
|
|
27
27
|
def range_from_location(location)
|
|
28
28
|
Interface::Range.new(
|
|
29
29
|
start: Interface::Position.new(
|
|
@@ -77,6 +77,44 @@ module RubyLsp
|
|
|
77
77
|
receiver.nil? || receiver.is_a?(Prism::SelfNode)
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
# Returns true when a constant declaration is reachable from the call site. Private constants are only
|
|
81
|
+
# reachable from within the namespace where they are defined.
|
|
82
|
+
#
|
|
83
|
+
#: (Rubydex::Declaration declaration, String value, NodeContext node_context) -> bool
|
|
84
|
+
def constant_reachable_from_call_site?(declaration, value, node_context)
|
|
85
|
+
return true unless declaration.is_a?(Rubydex::Visibility) && declaration.private?
|
|
86
|
+
|
|
87
|
+
declaration.name == "#{node_context.fully_qualified_name}::#{value}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns true when a method is reachable from the call site, considering visibility and receiver type.
|
|
91
|
+
# A method is reachable when:
|
|
92
|
+
# - there's no concrete receiver type to compare against
|
|
93
|
+
# - the call site is inside the receiver's own namespace (implicit/self call)
|
|
94
|
+
# - it is public
|
|
95
|
+
# - it is protected and the call site's class is in the same hierarchy as the method's defining class
|
|
96
|
+
#
|
|
97
|
+
# The `method_decl` is duck-typed to support `Rubydex::Method`, `RubyIndexer::Entry::Member` and
|
|
98
|
+
# `RubyIndexer::Entry::MethodAlias`. All respond to `public?`, `private?` and `owner` (an object with a
|
|
99
|
+
# `name` attribute).
|
|
100
|
+
#
|
|
101
|
+
#: (Rubydex::Method method_decl, TypeInferrer::Type? receiver_type, Rubydex::Graph graph, NodeContext node_context) -> bool
|
|
102
|
+
def method_reachable_from_call_site?(method_decl, receiver_type, graph, node_context)
|
|
103
|
+
return true unless receiver_type
|
|
104
|
+
|
|
105
|
+
caller_namespace = node_context.fully_qualified_name
|
|
106
|
+
return true if caller_namespace == receiver_type.name
|
|
107
|
+
|
|
108
|
+
return true if method_decl.public?
|
|
109
|
+
return false if method_decl.private?
|
|
110
|
+
|
|
111
|
+
caller_declaration = graph[caller_namespace]
|
|
112
|
+
return false unless caller_declaration.is_a?(Rubydex::Namespace)
|
|
113
|
+
|
|
114
|
+
owner_name = method_decl.owner.name
|
|
115
|
+
caller_declaration.ancestors.any? { |ancestor| ancestor.name == owner_name }
|
|
116
|
+
end
|
|
117
|
+
|
|
80
118
|
#: (String, Enumerable[Rubydex::Definition], ?Integer?) -> Hash[Symbol, String]
|
|
81
119
|
def categorized_markdown_from_definitions(title, definitions, max_entries = nil)
|
|
82
120
|
markdown_title = "```ruby\n#{title}\n```"
|
|
@@ -87,17 +125,22 @@ module RubyLsp
|
|
|
87
125
|
# For Markdown links, we need 1 based display locations
|
|
88
126
|
loc = definition.location.to_display
|
|
89
127
|
uri = URI(loc.uri)
|
|
90
|
-
|
|
128
|
+
|
|
129
|
+
file_name = case uri.scheme
|
|
130
|
+
when "file"
|
|
131
|
+
full_path = uri.full_path #: as !nil
|
|
132
|
+
File.basename(full_path)
|
|
133
|
+
when "untitled"
|
|
91
134
|
uri.opaque #: as !nil
|
|
92
|
-
else
|
|
93
|
-
File.basename(
|
|
94
|
-
uri.full_path, #: as !nil
|
|
95
|
-
)
|
|
96
135
|
end
|
|
97
136
|
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
137
|
+
# Omit the link for magic schemes like rubydex:built-in
|
|
138
|
+
if file_name
|
|
139
|
+
# The format for VS Code file URIs is `file:///path/to/file.rb#Lstart_line,start_column-end_line,end_column`
|
|
140
|
+
string_uri = "#{loc.uri}#L#{loc.start_line},#{loc.start_column}-#{loc.end_line},#{loc.end_column}"
|
|
141
|
+
file_links << "[#{file_name}](#{string_uri})"
|
|
142
|
+
end
|
|
143
|
+
|
|
101
144
|
content << "\n\n#{definition.comments.map { |comment| comment.string.delete_prefix("# ") }.join("\n")}" unless definition.comments.empty?
|
|
102
145
|
end
|
|
103
146
|
|
|
@@ -117,42 +160,9 @@ module RubyLsp
|
|
|
117
160
|
}
|
|
118
161
|
end
|
|
119
162
|
|
|
120
|
-
#: (String title,
|
|
121
|
-
def
|
|
122
|
-
|
|
123
|
-
definitions = []
|
|
124
|
-
content = +""
|
|
125
|
-
entries = Array(entries)
|
|
126
|
-
entries_to_format = max_entries ? entries.take(max_entries) : entries
|
|
127
|
-
entries_to_format.each do |entry|
|
|
128
|
-
loc = entry.location
|
|
129
|
-
|
|
130
|
-
# We always handle locations as zero based. However, for file links in Markdown we need them to be one
|
|
131
|
-
# based, which is why instead of the usual subtraction of 1 to line numbers, we are actually adding 1 to
|
|
132
|
-
# columns. The format for VS Code file URIs is
|
|
133
|
-
# `file:///path/to/file.rb#Lstart_line,start_column-end_line,end_column`
|
|
134
|
-
uri = "#{entry.uri}#L#{loc.start_line},#{loc.start_column + 1}-#{loc.end_line},#{loc.end_column + 1}"
|
|
135
|
-
definitions << "[#{entry.file_name}](#{uri})"
|
|
136
|
-
content << "\n\n#{entry.comments}" unless entry.comments.empty?
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
additional_entries_text = if max_entries && entries.length > max_entries
|
|
140
|
-
additional = entries.length - max_entries
|
|
141
|
-
" | #{additional} other#{additional > 1 ? "s" : ""}"
|
|
142
|
-
else
|
|
143
|
-
""
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
title: markdown_title,
|
|
148
|
-
links: "**Definitions**: #{definitions.join(" | ")}#{additional_entries_text}",
|
|
149
|
-
documentation: content,
|
|
150
|
-
}
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
#: (String title, (Array[RubyIndexer::Entry] | RubyIndexer::Entry) entries, ?Integer? max_entries, ?extra_links: String?) -> String
|
|
154
|
-
def markdown_from_index_entries(title, entries, max_entries = nil, extra_links: nil)
|
|
155
|
-
categorized_markdown = categorized_markdown_from_index_entries(title, entries, max_entries)
|
|
163
|
+
#: (String title, Enumerable[Rubydex::Definition] definitions, ?Integer? max_entries, ?extra_links: String?) -> String
|
|
164
|
+
def markdown_from_definitions(title, definitions, max_entries = nil, extra_links: nil)
|
|
165
|
+
categorized_markdown = categorized_markdown_from_definitions(title, definitions, max_entries)
|
|
156
166
|
|
|
157
167
|
markdown = +(categorized_markdown[:title] || "")
|
|
158
168
|
markdown << "\n\n#{extra_links}" if extra_links
|
|
@@ -168,7 +178,20 @@ module RubyLsp
|
|
|
168
178
|
|
|
169
179
|
#: ((Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode) node) -> String?
|
|
170
180
|
def constant_name(node)
|
|
171
|
-
|
|
181
|
+
Common.constant_name(node)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
class << self
|
|
185
|
+
#: ((Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode) node) -> String?
|
|
186
|
+
def constant_name(node)
|
|
187
|
+
case node
|
|
188
|
+
when Prism::ConstantPathNode, Prism::ConstantReadNode, Prism::ConstantPathTargetNode
|
|
189
|
+
node.full_name
|
|
190
|
+
end
|
|
191
|
+
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
|
|
192
|
+
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
|
193
|
+
nil
|
|
194
|
+
end
|
|
172
195
|
end
|
|
173
196
|
|
|
174
197
|
#: ((Prism::ModuleNode | Prism::ClassNode) node) -> String?
|
|
@@ -192,28 +215,6 @@ module RubyLsp
|
|
|
192
215
|
current = current.parent
|
|
193
216
|
end
|
|
194
217
|
end
|
|
195
|
-
|
|
196
|
-
#: (RubyIndexer::Entry entry) -> Integer
|
|
197
|
-
def kind_for_entry(entry)
|
|
198
|
-
case entry
|
|
199
|
-
when RubyIndexer::Entry::Class
|
|
200
|
-
Constant::SymbolKind::CLASS
|
|
201
|
-
when RubyIndexer::Entry::Module
|
|
202
|
-
Constant::SymbolKind::NAMESPACE
|
|
203
|
-
when RubyIndexer::Entry::Constant, RubyIndexer::Entry::UnresolvedConstantAlias, RubyIndexer::Entry::ConstantAlias
|
|
204
|
-
Constant::SymbolKind::CONSTANT
|
|
205
|
-
when RubyIndexer::Entry::Method, RubyIndexer::Entry::UnresolvedMethodAlias, RubyIndexer::Entry::MethodAlias
|
|
206
|
-
entry.name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
|
|
207
|
-
when RubyIndexer::Entry::Accessor
|
|
208
|
-
Constant::SymbolKind::PROPERTY
|
|
209
|
-
when RubyIndexer::Entry::InstanceVariable, RubyIndexer::Entry::ClassVariable
|
|
210
|
-
Constant::SymbolKind::FIELD
|
|
211
|
-
when RubyIndexer::Entry::GlobalVariable
|
|
212
|
-
Constant::SymbolKind::VARIABLE
|
|
213
|
-
else
|
|
214
|
-
Constant::SymbolKind::NULL
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
218
|
end
|
|
218
219
|
end
|
|
219
220
|
end
|
|
@@ -4,22 +4,6 @@
|
|
|
4
4
|
module RubyLsp
|
|
5
5
|
#: [ParseResultType = Prism::ParseLexResult]
|
|
6
6
|
class RubyDocument < Document
|
|
7
|
-
METHODS_THAT_CHANGE_DECLARATIONS = [
|
|
8
|
-
:private_constant,
|
|
9
|
-
:attr_reader,
|
|
10
|
-
:attr_writer,
|
|
11
|
-
:attr_accessor,
|
|
12
|
-
:alias_method,
|
|
13
|
-
:include,
|
|
14
|
-
:prepend,
|
|
15
|
-
:extend,
|
|
16
|
-
:public,
|
|
17
|
-
:protected,
|
|
18
|
-
:private,
|
|
19
|
-
:module_function,
|
|
20
|
-
:private_class_method,
|
|
21
|
-
].freeze
|
|
22
|
-
|
|
23
7
|
class << self
|
|
24
8
|
#: (Prism::Node node, Integer char_position, code_units_cache: (^(Integer arg0) -> Integer | Prism::CodeUnitsCache), ?node_types: Array[singleton(Prism::Node)]) -> NodeContext
|
|
25
9
|
def locate(node, char_position, code_units_cache:, node_types: [])
|
|
@@ -190,62 +174,5 @@ module RubyLsp
|
|
|
190
174
|
node_types: node_types,
|
|
191
175
|
)
|
|
192
176
|
end
|
|
193
|
-
|
|
194
|
-
#: -> bool
|
|
195
|
-
def should_index?
|
|
196
|
-
# This method controls when we should index documents. If there's no recent edit and the document has just been
|
|
197
|
-
# opened, we need to index it
|
|
198
|
-
return true unless @last_edit
|
|
199
|
-
|
|
200
|
-
last_edit_may_change_declarations?
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
private
|
|
204
|
-
|
|
205
|
-
#: -> bool
|
|
206
|
-
def last_edit_may_change_declarations?
|
|
207
|
-
case @last_edit
|
|
208
|
-
when Delete
|
|
209
|
-
# Not optimized yet. It's not trivial to identify that a declaration has been removed since the source is no
|
|
210
|
-
# longer there and we don't remember the deleted text
|
|
211
|
-
true
|
|
212
|
-
when Insert, Replace
|
|
213
|
-
position_may_impact_declarations?(@last_edit.range[:start])
|
|
214
|
-
else
|
|
215
|
-
false
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
#: (Hash[Symbol, Integer] position) -> bool
|
|
220
|
-
def position_may_impact_declarations?(position)
|
|
221
|
-
node_context = locate_node(position)
|
|
222
|
-
node_at_edit = node_context.node
|
|
223
|
-
|
|
224
|
-
# Adjust to the parent when editing the constant of a class/module declaration
|
|
225
|
-
if node_at_edit.is_a?(Prism::ConstantReadNode) || node_at_edit.is_a?(Prism::ConstantPathNode)
|
|
226
|
-
node_at_edit = node_context.parent
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
case node_at_edit
|
|
230
|
-
when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode,
|
|
231
|
-
Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
|
|
232
|
-
Prism::ConstantPathAndWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantWriteNode,
|
|
233
|
-
Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode, Prism::GlobalVariableAndWriteNode,
|
|
234
|
-
Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableTargetNode,
|
|
235
|
-
Prism::GlobalVariableWriteNode, Prism::InstanceVariableWriteNode, Prism::InstanceVariableAndWriteNode,
|
|
236
|
-
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode,
|
|
237
|
-
Prism::InstanceVariableTargetNode, Prism::AliasMethodNode
|
|
238
|
-
true
|
|
239
|
-
when Prism::MultiWriteNode
|
|
240
|
-
[*node_at_edit.lefts, *node_at_edit.rest, *node_at_edit.rights].any? do |node|
|
|
241
|
-
node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
|
|
242
|
-
end
|
|
243
|
-
when Prism::CallNode
|
|
244
|
-
receiver = node_at_edit.receiver
|
|
245
|
-
(!receiver || receiver.is_a?(Prism::SelfNode)) && METHODS_THAT_CHANGE_DECLARATIONS.include?(node_at_edit.name)
|
|
246
|
-
else
|
|
247
|
-
false
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
177
|
end
|
|
251
178
|
end
|