hind 0.1.17 → 0.1.18
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/lib/hind/lsif/generator.rb +38 -0
- data/lib/hind/lsif/global_state.rb +73 -14
- data/lib/hind/lsif/visitors/declaration_visitor.rb +60 -0
- data/lib/hind/lsif/visitors/reference_visitor.rb +79 -2
- data/lib/hind/scip/generator.rb +122 -5
- data/lib/hind/scip/global_state.rb +51 -6
- data/lib/hind/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df06e6747a2824041eec9a746d2ba16d6bbabf3cb927da9800157dbaeb2095a9
|
|
4
|
+
data.tar.gz: 93e7b0e378c3d242af3658a0d7b83687d2e454628e57f91d5c09a9927169b024
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 378c72668b6c0a39ef27fbaafd5053ea51fccdc80fed0d0ca721ace54d0eb1fb980030827503b84d3cc082649c4f627f1ca1f6e3e93bc9ebe44886efa2f0f6ac
|
|
7
|
+
data.tar.gz: 7fe5b6f0614d81bf0ae2181c63e9b11a06be8f02da670dc48e58e900e368c6c63557a8c456afa6e0f2bbc441e5688d50358298e856dfc156b685490b71c4a340
|
data/lib/hind/lsif/generator.rb
CHANGED
|
@@ -223,6 +223,44 @@ module Hind
|
|
|
223
223
|
result_set_id
|
|
224
224
|
end
|
|
225
225
|
|
|
226
|
+
def register_method_declaration(declaration)
|
|
227
|
+
return unless @current_uri && declaration[:node]
|
|
228
|
+
|
|
229
|
+
qualified_name = declaration[:name]
|
|
230
|
+
|
|
231
|
+
setup_document if @document_id.nil?
|
|
232
|
+
current_doc_id = @document_id
|
|
233
|
+
|
|
234
|
+
loc = declaration[:node].respond_to?(:name_loc) ? declaration[:node].name_loc : declaration[:node].location
|
|
235
|
+
range_id = create_range(loc)
|
|
236
|
+
return unless range_id
|
|
237
|
+
|
|
238
|
+
result_set_id = emit_vertex('resultSet')
|
|
239
|
+
emit_edge('next', range_id, result_set_id)
|
|
240
|
+
|
|
241
|
+
def_result_id = emit_vertex('definitionResult')
|
|
242
|
+
emit_edge('textDocument/definition', result_set_id, def_result_id)
|
|
243
|
+
|
|
244
|
+
emit_edge('item', def_result_id, [range_id], 'definitions', current_doc_id)
|
|
245
|
+
|
|
246
|
+
hover_id = emit_vertex('hoverResult', {
|
|
247
|
+
contents: [{
|
|
248
|
+
language: 'ruby',
|
|
249
|
+
value: "def #{declaration[:name].split('#').last}"
|
|
250
|
+
}]
|
|
251
|
+
})
|
|
252
|
+
emit_edge('textDocument/hover', result_set_id, hover_id)
|
|
253
|
+
|
|
254
|
+
declaration[:range_id] = range_id
|
|
255
|
+
declaration[:result_set_id] = result_set_id
|
|
256
|
+
declaration[:document_id] = current_doc_id
|
|
257
|
+
declaration[:file] = @current_uri
|
|
258
|
+
|
|
259
|
+
GlobalState.instance.add_method(qualified_name, declaration)
|
|
260
|
+
|
|
261
|
+
result_set_id
|
|
262
|
+
end
|
|
263
|
+
|
|
226
264
|
def register_reference(reference)
|
|
227
265
|
return unless @current_uri && reference[:node]
|
|
228
266
|
return unless GlobalState.instance.has_declaration?(reference[:name])
|
|
@@ -18,10 +18,12 @@ module Hind
|
|
|
18
18
|
@classes = {} # {qualified_name => {definitions: [{node:, scope:, file:, range_id:, result_set_id:, superclass:}]}}
|
|
19
19
|
@modules = {} # {qualified_name => {definitions: [{node:, scope:, file:, range_id:, result_set_id:}]}}
|
|
20
20
|
@constants = {} # {qualified_name => {node:, scope:, file:, range_id:, result_set_id:, value:}}
|
|
21
|
+
@methods = {} # {qualified_name => {node:, scope:, file:, range_id:, result_set_id:}}
|
|
21
22
|
@references = {} # {qualified_name => [{file:, range_id:, document_id:}, ...]}
|
|
22
23
|
@ranges = {} # {file_path => [range_ids]}
|
|
23
24
|
@range_cache = {} # {file_path => {[start_line, start_col, end_line, end_col] => range_id}}
|
|
24
25
|
@result_sets = {} # {qualified_name => result_set_id}
|
|
26
|
+
@ancestors = {} # {qualified_name => [{name:, type:}]}
|
|
25
27
|
@project_id = nil
|
|
26
28
|
end
|
|
27
29
|
|
|
@@ -58,6 +60,16 @@ module Hind
|
|
|
58
60
|
@result_sets[qualified_name] = data[:result_set_id] if data[:result_set_id]
|
|
59
61
|
end
|
|
60
62
|
|
|
63
|
+
def add_method(qualified_name, data)
|
|
64
|
+
@methods[qualified_name] = data
|
|
65
|
+
@result_sets[qualified_name] = data[:result_set_id] if data[:result_set_id]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def add_ancestor(qualified_name, mixed_in_module, type)
|
|
69
|
+
@ancestors[qualified_name] ||= []
|
|
70
|
+
@ancestors[qualified_name] << { name: mixed_in_module, type: type }
|
|
71
|
+
end
|
|
72
|
+
|
|
61
73
|
def add_reference(qualified_name, file_path, range_id, document_id)
|
|
62
74
|
@references[qualified_name] ||= []
|
|
63
75
|
@references[qualified_name] << {
|
|
@@ -87,7 +99,8 @@ module Hind
|
|
|
87
99
|
def has_declaration?(qualified_name)
|
|
88
100
|
@classes.key?(qualified_name) ||
|
|
89
101
|
@modules.key?(qualified_name) ||
|
|
90
|
-
@constants.key?(qualified_name)
|
|
102
|
+
@constants.key?(qualified_name) ||
|
|
103
|
+
@methods.key?(qualified_name)
|
|
91
104
|
end
|
|
92
105
|
|
|
93
106
|
def get_declaration(qualified_name)
|
|
@@ -95,8 +108,10 @@ module Hind
|
|
|
95
108
|
determine_primary_class_definition(qualified_name)
|
|
96
109
|
elsif @modules.key?(qualified_name)
|
|
97
110
|
determine_primary_module_definition(qualified_name)
|
|
98
|
-
|
|
111
|
+
elsif @constants.key?(qualified_name)
|
|
99
112
|
@constants[qualified_name]
|
|
113
|
+
else
|
|
114
|
+
@methods[qualified_name]
|
|
100
115
|
end
|
|
101
116
|
end
|
|
102
117
|
|
|
@@ -112,31 +127,48 @@ module Hind
|
|
|
112
127
|
@ranges[file_path] || []
|
|
113
128
|
end
|
|
114
129
|
|
|
115
|
-
def
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return nil unless current_scope && !current_scope.empty?
|
|
130
|
+
def find_declaration(name, current_scope)
|
|
131
|
+
# Handle both constants (::) and methods (#)
|
|
132
|
+
is_method = name.start_with?('#')
|
|
133
|
+
sep = is_method ? '' : '::'
|
|
120
134
|
|
|
121
|
-
#
|
|
122
|
-
|
|
135
|
+
# 1. Lexical Scope search
|
|
136
|
+
# Try full current scope
|
|
137
|
+
qualified_name = current_scope.empty? ? name : "#{current_scope}#{sep}#{name}"
|
|
138
|
+
# Strip leading # if simple name
|
|
139
|
+
qualified_name = name if current_scope.empty? && is_method
|
|
123
140
|
return qualified_name if has_declaration?(qualified_name)
|
|
124
141
|
|
|
125
|
-
#
|
|
142
|
+
# 2. Ancestor chain search for current scope
|
|
143
|
+
if !current_scope.empty?
|
|
144
|
+
found = resolve_in_ancestors(name, current_scope, seen: Set.new)
|
|
145
|
+
return found if found
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# 3. Parent Lexical Scopes
|
|
126
149
|
scope_parts = current_scope.split('::')
|
|
127
150
|
while scope_parts.size > 0
|
|
128
151
|
scope_parts.pop
|
|
129
|
-
|
|
152
|
+
prefix = scope_parts.join('::')
|
|
130
153
|
|
|
131
|
-
|
|
132
|
-
qualified_name =
|
|
154
|
+
qualified_name = prefix.empty? ? name : "#{prefix}#{sep}#{name}"
|
|
155
|
+
qualified_name = name if prefix.empty? && is_method
|
|
133
156
|
return qualified_name if has_declaration?(qualified_name)
|
|
157
|
+
|
|
158
|
+
# Search ancestors of the parent scope too
|
|
159
|
+
if !prefix.empty?
|
|
160
|
+
found = resolve_in_ancestors(name, prefix, seen: Set.new)
|
|
161
|
+
return found if found
|
|
162
|
+
end
|
|
134
163
|
end
|
|
135
164
|
|
|
136
|
-
# Not found in any scope
|
|
137
165
|
nil
|
|
138
166
|
end
|
|
139
167
|
|
|
168
|
+
def find_constant_declaration(name, current_scope)
|
|
169
|
+
find_declaration(name, current_scope)
|
|
170
|
+
end
|
|
171
|
+
|
|
140
172
|
def debug_info
|
|
141
173
|
{
|
|
142
174
|
classes_count: @classes.size,
|
|
@@ -247,6 +279,33 @@ module Hind
|
|
|
247
279
|
# Rule 3: Fall back to the first definition
|
|
248
280
|
definitions.first
|
|
249
281
|
end
|
|
282
|
+
|
|
283
|
+
private
|
|
284
|
+
|
|
285
|
+
def resolve_in_ancestors(name, scope, seen:)
|
|
286
|
+
return nil if seen.include?(scope)
|
|
287
|
+
seen.add(scope)
|
|
288
|
+
|
|
289
|
+
is_method = name.start_with?('#')
|
|
290
|
+
sep = is_method ? '' : '::'
|
|
291
|
+
|
|
292
|
+
# Check this scope's ancestors
|
|
293
|
+
ancestors = @ancestors[scope] || []
|
|
294
|
+
ancestors.each do |ancestor|
|
|
295
|
+
mixed_in = ancestor[:name]
|
|
296
|
+
|
|
297
|
+
# Try absolute name first (if it's already qualified)
|
|
298
|
+
qualified = "#{mixed_in}#{sep}#{name}"
|
|
299
|
+
qualified = name if mixed_in.empty? && is_method
|
|
300
|
+
return qualified if has_declaration?(qualified)
|
|
301
|
+
|
|
302
|
+
# Recurse into mixed_in's ancestors
|
|
303
|
+
found = resolve_in_ancestors(name, mixed_in, seen: seen)
|
|
304
|
+
return found if found
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
nil
|
|
308
|
+
end
|
|
250
309
|
end
|
|
251
310
|
end
|
|
252
311
|
end
|
|
@@ -59,6 +59,66 @@ module Hind
|
|
|
59
59
|
super
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
def visit_def_node(node)
|
|
63
|
+
method_name = node.name.to_s
|
|
64
|
+
qualified_name = @current_scope.empty? ? "##{method_name}" : "#{current_scope_name}##{method_name}"
|
|
65
|
+
|
|
66
|
+
@generator.register_method_declaration({
|
|
67
|
+
type: :method,
|
|
68
|
+
name: qualified_name,
|
|
69
|
+
node: node,
|
|
70
|
+
scope: current_scope_name
|
|
71
|
+
})
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def visit_call_node(node)
|
|
76
|
+
# Handle attr_accessor, attr_reader, attr_writer
|
|
77
|
+
if node.receiver.nil? && %w[attr_reader attr_writer attr_accessor].include?(node.name.to_s)
|
|
78
|
+
helpers = node.arguments&.arguments || []
|
|
79
|
+
helpers.each do |arg|
|
|
80
|
+
next unless arg.is_a?(Prism::SymbolNode)
|
|
81
|
+
|
|
82
|
+
name = arg.value
|
|
83
|
+
if %w[attr_reader attr_accessor].include?(node.name.to_s)
|
|
84
|
+
# Getter
|
|
85
|
+
qualified_name = @current_scope.empty? ? "##{name}" : "#{current_scope_name}##{name}"
|
|
86
|
+
@generator.register_method_declaration({
|
|
87
|
+
type: :method,
|
|
88
|
+
name: qualified_name,
|
|
89
|
+
node: arg, # Use symbol node as the 'def' site
|
|
90
|
+
scope: current_scope_name
|
|
91
|
+
})
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if %w[attr_writer attr_accessor].include?(node.name.to_s)
|
|
95
|
+
# Setter
|
|
96
|
+
setter_name = "#{name}="
|
|
97
|
+
qualified_name = @current_scope.empty? ? "##{setter_name}" : "#{current_scope_name}##{setter_name}"
|
|
98
|
+
@generator.register_method_declaration({
|
|
99
|
+
type: :method,
|
|
100
|
+
name: qualified_name,
|
|
101
|
+
node: arg,
|
|
102
|
+
scope: current_scope_name
|
|
103
|
+
})
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Handle include, extend, prepend
|
|
109
|
+
if node.receiver.nil? && %w[include extend prepend].include?(node.name.to_s)
|
|
110
|
+
modules = node.arguments&.arguments || []
|
|
111
|
+
modules.each do |mod_node|
|
|
112
|
+
# Simplified: only handle simple constant names or paths
|
|
113
|
+
next unless mod_node.is_a?(Prism::ConstantReadNode) || mod_node.is_a?(Prism::ConstantPathNode)
|
|
114
|
+
|
|
115
|
+
mixed_in = mod_node.slice
|
|
116
|
+
GlobalState.instance.add_ancestor(current_scope_name, mixed_in, node.name.to_sym)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
super
|
|
120
|
+
end
|
|
121
|
+
|
|
62
122
|
private
|
|
63
123
|
|
|
64
124
|
def current_scope_name
|
|
@@ -46,19 +46,96 @@ module Hind
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def visit_class_node(node)
|
|
49
|
-
@current_scope.push(node.constant_path.slice)
|
|
49
|
+
@current_scope.push(scip_name(node.constant_path.slice))
|
|
50
50
|
super
|
|
51
51
|
@current_scope.pop
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def visit_module_node(node)
|
|
55
|
-
@current_scope.push(node.constant_path.slice)
|
|
55
|
+
@current_scope.push(scip_name(node.constant_path.slice))
|
|
56
56
|
super
|
|
57
57
|
@current_scope.pop
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
def visit_def_node(node)
|
|
61
|
+
# We don't push def to current_scope because methods are usually inside classes/modules
|
|
62
|
+
# and we use #method notation. Pushing 'method' to scope would result in A::B#method#submethod
|
|
63
|
+
# which is not standard for Ruby normally unless we handle nested blocks.
|
|
64
|
+
# But for now, let's just visit.
|
|
65
|
+
super
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def visit_call_node(node)
|
|
69
|
+
# Speculative resolution for method calls
|
|
70
|
+
register_method_call_reference(node)
|
|
71
|
+
super
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def visit_call_operator_write_node(node)
|
|
75
|
+
# Handle self.age += 1
|
|
76
|
+
# This involves both a getter (age) and a setter (age=)
|
|
77
|
+
register_method_call_reference(node, name_override: node.read_name.to_s)
|
|
78
|
+
register_method_call_reference(node, name_override: node.write_name.to_s)
|
|
79
|
+
super
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def visit_call_and_write_node(node)
|
|
83
|
+
register_method_call_reference(node, name_override: node.read_name.to_s)
|
|
84
|
+
register_method_call_reference(node, name_override: node.write_name.to_s)
|
|
85
|
+
super
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def visit_call_or_write_node(node)
|
|
89
|
+
register_method_call_reference(node, name_override: node.read_name.to_s)
|
|
90
|
+
register_method_call_reference(node, name_override: node.write_name.to_s)
|
|
91
|
+
super
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def visit_index_operator_write_node(node)
|
|
95
|
+
# Handle obj[key] += val
|
|
96
|
+
register_method_call_reference(node, name_override: "[]")
|
|
97
|
+
register_method_call_reference(node, name_override: "[]=")
|
|
98
|
+
super
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def visit_index_and_write_node(node)
|
|
102
|
+
register_method_call_reference(node, name_override: "[]")
|
|
103
|
+
register_method_call_reference(node, name_override: "[]=")
|
|
104
|
+
super
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def visit_index_or_write_node(node)
|
|
108
|
+
register_method_call_reference(node, name_override: "[]")
|
|
109
|
+
register_method_call_reference(node, name_override: "[]=")
|
|
110
|
+
super
|
|
111
|
+
end
|
|
112
|
+
|
|
60
113
|
private
|
|
61
114
|
|
|
115
|
+
def register_method_call_reference(node, name_override: nil)
|
|
116
|
+
method_name = name_override || node.name.to_s
|
|
117
|
+
|
|
118
|
+
# Try finding #method_name in current scope
|
|
119
|
+
found_name = GlobalState.instance.find_declaration("##{method_name}", current_scope_name)
|
|
120
|
+
|
|
121
|
+
if found_name
|
|
122
|
+
loc = node.respond_to?(:message_loc) ? (node.message_loc || node.location) : node.location
|
|
123
|
+
@generator.register_reference({
|
|
124
|
+
type: :method,
|
|
125
|
+
name: found_name,
|
|
126
|
+
node: node,
|
|
127
|
+
location: loc
|
|
128
|
+
})
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def scip_name(name)
|
|
133
|
+
# Basic unescaping to match what we store in GlobalState keys if needed
|
|
134
|
+
# We store them unescaped in scope normally or with backticks if scip_name was used.
|
|
135
|
+
# Let's be consistent.
|
|
136
|
+
name.delete('`')
|
|
137
|
+
end
|
|
138
|
+
|
|
62
139
|
def current_scope_name
|
|
63
140
|
@current_scope.join('::')
|
|
64
141
|
end
|
data/lib/hind/scip/generator.rb
CHANGED
|
@@ -280,17 +280,135 @@ module Hind
|
|
|
280
280
|
end
|
|
281
281
|
|
|
282
282
|
def visit_call_node(node)
|
|
283
|
-
|
|
283
|
+
# Handle attr_accessor, attr_reader, attr_writer
|
|
284
|
+
index_attribute_helpers(node)
|
|
285
|
+
index_ancestor_helpers(node)
|
|
286
|
+
|
|
287
|
+
return super if @mode == :index
|
|
284
288
|
|
|
285
289
|
# Skip common methods
|
|
286
290
|
return super if %w[new puts p print].include?(node.name.to_s)
|
|
287
291
|
|
|
292
|
+
register_scip_call_reference(node)
|
|
293
|
+
super
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def visit_call_operator_write_node(node)
|
|
297
|
+
return super if @mode == :index
|
|
298
|
+
register_scip_call_reference(node, name_override: node.read_name.to_s)
|
|
299
|
+
register_scip_call_reference(node, name_override: node.write_name.to_s)
|
|
300
|
+
super
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def visit_call_and_write_node(node)
|
|
304
|
+
return super if @mode == :index
|
|
305
|
+
register_scip_call_reference(node, name_override: node.read_name.to_s)
|
|
306
|
+
register_scip_call_reference(node, name_override: node.write_name.to_s)
|
|
307
|
+
super
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def visit_call_or_write_node(node)
|
|
311
|
+
return super if @mode == :index
|
|
312
|
+
register_scip_call_reference(node, name_override: node.read_name.to_s)
|
|
313
|
+
register_scip_call_reference(node, name_override: node.write_name.to_s)
|
|
314
|
+
super
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def visit_index_operator_write_node(node)
|
|
318
|
+
return super if @mode == :index
|
|
319
|
+
register_scip_call_reference(node, name_override: "[]")
|
|
320
|
+
register_scip_call_reference(node, name_override: "[]=")
|
|
321
|
+
super
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def visit_index_and_write_node(node)
|
|
325
|
+
return super if @mode == :index
|
|
326
|
+
register_scip_call_reference(node, name_override: "[]")
|
|
327
|
+
register_scip_call_reference(node, name_override: "[]=")
|
|
328
|
+
super
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def visit_index_or_write_node(node)
|
|
332
|
+
return super if @mode == :index
|
|
333
|
+
register_scip_call_reference(node, name_override: "[]")
|
|
334
|
+
register_scip_call_reference(node, name_override: "[]=")
|
|
335
|
+
super
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
private
|
|
339
|
+
|
|
340
|
+
def index_attribute_helpers(node)
|
|
341
|
+
return unless node.receiver.nil? && %w[attr_reader attr_writer attr_accessor].include?(node.name.to_s)
|
|
342
|
+
|
|
343
|
+
helpers = node.arguments&.arguments || []
|
|
344
|
+
helpers.each do |arg|
|
|
345
|
+
next unless arg.is_a?(Prism::SymbolNode)
|
|
346
|
+
|
|
347
|
+
name = arg.value
|
|
348
|
+
if %w[attr_reader attr_accessor].include?(node.name.to_s)
|
|
349
|
+
# Getter
|
|
350
|
+
getter_name = scip_name(name)
|
|
351
|
+
qualified_name = "#{current_scope_name}##{name}"
|
|
352
|
+
suffix = @current_scope.empty? ? '' : '#'
|
|
353
|
+
symbol = "#{@package_prefix}#{@current_scope.join("#")}#{suffix}#{getter_name}."
|
|
354
|
+
|
|
355
|
+
if @mode == :index
|
|
356
|
+
GlobalState.instance.add_symbol(qualified_name, symbol)
|
|
357
|
+
GlobalState.instance.add_symbol("##{name}", symbol)
|
|
358
|
+
else
|
|
359
|
+
range = [arg.location.start_line - 1, arg.location.start_column, arg.location.end_column]
|
|
360
|
+
@occurrences << Occurrence.new(range: range, symbol: symbol, symbol_roles: 1, syntax_kind: SyntaxKind::Identifier)
|
|
361
|
+
unless GlobalState.instance.emitted?(symbol)
|
|
362
|
+
@symbols << SymbolInformation.new(symbol: symbol, documentation: ["attr_reader #{name}"], kind: SymbolInformation::Kind::Method)
|
|
363
|
+
GlobalState.instance.mark_emitted(symbol)
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
if %w[attr_writer attr_accessor].include?(node.name.to_s)
|
|
369
|
+
# Setter
|
|
370
|
+
setter_name = "#{name}="
|
|
371
|
+
escaped_setter = scip_name(setter_name)
|
|
372
|
+
qualified_name = "#{current_scope_name}##{setter_name}"
|
|
373
|
+
suffix = @current_scope.empty? ? '' : '#'
|
|
374
|
+
symbol = "#{@package_prefix}#{@current_scope.join("#")}#{suffix}#{escaped_setter}."
|
|
375
|
+
|
|
376
|
+
if @mode == :index
|
|
377
|
+
GlobalState.instance.add_symbol(qualified_name, symbol)
|
|
378
|
+
GlobalState.instance.add_symbol("##{setter_name}", symbol)
|
|
379
|
+
else
|
|
380
|
+
range = [arg.location.start_line - 1, arg.location.start_column, arg.location.end_column]
|
|
381
|
+
@occurrences << Occurrence.new(range: range, symbol: symbol, symbol_roles: 1, syntax_kind: SyntaxKind::Identifier)
|
|
382
|
+
unless GlobalState.instance.emitted?(symbol)
|
|
383
|
+
@symbols << SymbolInformation.new(symbol: symbol, documentation: ["attr_writer #{name}"], kind: SymbolInformation::Kind::Method)
|
|
384
|
+
GlobalState.instance.mark_emitted(symbol)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def index_ancestor_helpers(node)
|
|
392
|
+
return unless node.receiver.nil? && %w[include extend prepend].include?(node.name.to_s)
|
|
393
|
+
|
|
394
|
+
modules = node.arguments&.arguments || []
|
|
395
|
+
modules.each do |mod_node|
|
|
396
|
+
next unless mod_node.is_a?(Prism::ConstantReadNode) || mod_node.is_a?(Prism::ConstantPathNode)
|
|
397
|
+
|
|
398
|
+
mixed_in = mod_node.slice
|
|
399
|
+
GlobalState.instance.add_ancestor(current_scope_name, mixed_in)
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def register_scip_call_reference(node, name_override: nil)
|
|
404
|
+
method_name = name_override || node.name.to_s
|
|
405
|
+
|
|
288
406
|
# Speculative resolution
|
|
289
|
-
symbol = GlobalState.instance.find_symbol("#{current_scope_name}##{
|
|
290
|
-
GlobalState.instance.find_symbol("##{
|
|
407
|
+
symbol = GlobalState.instance.find_symbol("#{current_scope_name}##{method_name}", '') ||
|
|
408
|
+
GlobalState.instance.find_symbol("##{method_name}", '')
|
|
291
409
|
|
|
292
410
|
if symbol
|
|
293
|
-
loc = node.message_loc || node.location
|
|
411
|
+
loc = node.respond_to?(:message_loc) ? (node.message_loc || node.location) : node.location
|
|
294
412
|
range = [
|
|
295
413
|
loc.start_line - 1,
|
|
296
414
|
loc.start_column,
|
|
@@ -304,7 +422,6 @@ module Hind
|
|
|
304
422
|
syntax_kind: SyntaxKind::Identifier
|
|
305
423
|
)
|
|
306
424
|
end
|
|
307
|
-
super
|
|
308
425
|
end
|
|
309
426
|
|
|
310
427
|
private
|
|
@@ -14,9 +14,15 @@ module Hind
|
|
|
14
14
|
|
|
15
15
|
def reset
|
|
16
16
|
@symbols = {} # {qualified_name => symbol_string}
|
|
17
|
+
@ancestors = {} # {qualified_name => [symbol_strings]}
|
|
17
18
|
@emitted_symbols = Set.new
|
|
18
19
|
end
|
|
19
20
|
|
|
21
|
+
def add_ancestor(qualified_name, mixed_in_symbol)
|
|
22
|
+
@ancestors[qualified_name] ||= []
|
|
23
|
+
@ancestors[qualified_name] << mixed_in_symbol
|
|
24
|
+
end
|
|
25
|
+
|
|
20
26
|
def mark_emitted(symbol)
|
|
21
27
|
@emitted_symbols.add(symbol)
|
|
22
28
|
end
|
|
@@ -38,20 +44,59 @@ module Hind
|
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
def find_symbol(name, current_scope)
|
|
41
|
-
#
|
|
42
|
-
|
|
47
|
+
# Handle both constants (::) and methods (#)
|
|
48
|
+
is_method = name.start_with?('#')
|
|
49
|
+
sep = is_method ? '' : '::'
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
qualified_name =
|
|
51
|
+
# 1. Lexical Scope search
|
|
52
|
+
qualified_name = current_scope.empty? ? name : "#{current_scope}#{sep}#{name}"
|
|
53
|
+
qualified_name = name if current_scope.empty? && is_method
|
|
47
54
|
return @symbols[qualified_name] if @symbols.key?(qualified_name)
|
|
48
55
|
|
|
56
|
+
# 2. Ancestor chain search for current scope
|
|
57
|
+
if !current_scope.empty?
|
|
58
|
+
found = resolve_in_ancestors(name, current_scope, seen: Set.new)
|
|
59
|
+
return found if found
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# 3. Parent Lexical Scopes
|
|
49
63
|
scope_parts = current_scope.split('::')
|
|
50
64
|
while scope_parts.size > 0
|
|
51
65
|
scope_parts.pop
|
|
52
66
|
prefix = scope_parts.join('::')
|
|
53
|
-
|
|
67
|
+
|
|
68
|
+
qualified_name = prefix.empty? ? name : "#{prefix}#{sep}#{name}"
|
|
69
|
+
qualified_name = name if prefix.empty? && is_method
|
|
54
70
|
return @symbols[qualified_name] if @symbols.key?(qualified_name)
|
|
71
|
+
|
|
72
|
+
# Search ancestors of the parent scope too
|
|
73
|
+
if !prefix.empty?
|
|
74
|
+
found = resolve_in_ancestors(name, prefix, seen: Set.new)
|
|
75
|
+
return found if found
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def resolve_in_ancestors(name, scope, seen:)
|
|
85
|
+
return nil if seen.include?(scope)
|
|
86
|
+
seen.add(scope)
|
|
87
|
+
|
|
88
|
+
is_method = name.start_with?('#')
|
|
89
|
+
sep = is_method ? '' : '::'
|
|
90
|
+
|
|
91
|
+
ancestors = @ancestors[scope] || []
|
|
92
|
+
ancestors.each do |mixed_in|
|
|
93
|
+
# 1. Try to see if mixed_in has the declaration
|
|
94
|
+
qualified = "#{mixed_in}#{sep}#{name}"
|
|
95
|
+
return @symbols[qualified] if @symbols.key?(qualified)
|
|
96
|
+
|
|
97
|
+
# 2. Recurse into mixed_in's ancestors
|
|
98
|
+
found = resolve_in_ancestors(name, mixed_in, seen: seen)
|
|
99
|
+
return found if found
|
|
55
100
|
end
|
|
56
101
|
|
|
57
102
|
nil
|
data/lib/hind/version.rb
CHANGED