hind 0.1.4 → 0.1.6
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/exe/hind +4 -2
- data/lib/hind/cli.rb +181 -64
- data/lib/hind/lsif/edge.rb +3 -1
- data/lib/hind/lsif/generator.rb +215 -86
- data/lib/hind/lsif/global_state.rb +152 -11
- data/lib/hind/lsif/vertex.rb +3 -1
- data/lib/hind/lsif/visitor.rb +211 -19
- data/lib/hind/lsif/visitors/declaration_visitor.rb +239 -0
- data/lib/hind/lsif/visitors/reference_visitor.rb +221 -0
- data/lib/hind/lsif.rb +6 -5
- data/lib/hind/parser.rb +3 -0
- data/lib/hind/scip/generator.rb +2 -0
- data/lib/hind/scip/visitor.rb +2 -0
- data/lib/hind/scip.rb +2 -0
- data/lib/hind/version.rb +1 -1
- data/lib/hind.rb +4 -4
- metadata +4 -2
data/lib/hind/lsif/visitor.rb
CHANGED
@@ -1,29 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Hind
|
2
4
|
module LSIF
|
3
|
-
class Visitor
|
5
|
+
class Visitor < Prism::Visitor
|
4
6
|
def initialize(generator)
|
5
7
|
@generator = generator
|
6
8
|
@current_scope = []
|
7
9
|
end
|
8
10
|
|
9
|
-
def
|
10
|
-
|
11
|
+
def visit_def_node(node)
|
12
|
+
method_name = node.name.to_s
|
13
|
+
qualified_name = current_scope_name.empty? ? method_name : "#{current_scope_name}##{method_name}"
|
14
|
+
|
15
|
+
range_id = @generator.create_range(node.location, node.location)
|
16
|
+
result_set_id = @generator.emit_vertex('resultSet')
|
17
|
+
@generator.emit_edge('next', range_id, result_set_id)
|
18
|
+
|
19
|
+
def_result_id = @generator.emit_vertex('definitionResult')
|
20
|
+
@generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
|
21
|
+
@generator.emit_edge('item', def_result_id, [range_id], 'definitions')
|
22
|
+
|
23
|
+
# Generate method signature for hover
|
24
|
+
sig = []
|
25
|
+
sig << "def #{qualified_name}"
|
26
|
+
sig << "(#{node.parameters.slice})" if node.parameters
|
11
27
|
|
12
|
-
|
13
|
-
|
14
|
-
|
28
|
+
hover_id = @generator.emit_vertex('hoverResult', {
|
29
|
+
contents: [{
|
30
|
+
language: 'ruby',
|
31
|
+
value: sig.join
|
32
|
+
}]
|
33
|
+
})
|
34
|
+
@generator.emit_edge('textDocument/hover', result_set_id, hover_id)
|
35
|
+
|
36
|
+
@generator.add_to_global_state(qualified_name, result_set_id, range_id)
|
37
|
+
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def visit_class_node(node)
|
42
|
+
@current_scope.push(node.constant_path.slice)
|
43
|
+
class_name = current_scope_name
|
44
|
+
|
45
|
+
range_id = @generator.create_range(node.location, node.location)
|
46
|
+
result_set_id = @generator.emit_vertex('resultSet')
|
47
|
+
@generator.emit_edge('next', range_id, result_set_id)
|
48
|
+
|
49
|
+
def_result_id = @generator.emit_vertex('definitionResult')
|
50
|
+
@generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
|
51
|
+
@generator.emit_edge('item', def_result_id, [range_id], 'definitions')
|
52
|
+
|
53
|
+
# Generate hover with inheritance info
|
54
|
+
hover = []
|
55
|
+
class_def = "class #{class_name}"
|
56
|
+
class_def += " < #{node.superclass.slice}" if node.superclass
|
57
|
+
|
58
|
+
hover << if node.superclass
|
59
|
+
"#{class_def}\n\nInherits from: #{node.superclass.slice}"
|
15
60
|
else
|
16
|
-
|
61
|
+
class_def
|
17
62
|
end
|
63
|
+
|
64
|
+
hover_id = @generator.emit_vertex('hoverResult', {
|
65
|
+
contents: [{
|
66
|
+
language: 'ruby',
|
67
|
+
value: hover.join("\n")
|
68
|
+
}]
|
69
|
+
})
|
70
|
+
@generator.emit_edge('textDocument/hover', result_set_id, hover_id)
|
71
|
+
|
72
|
+
@generator.add_to_global_state(class_name, result_set_id, range_id)
|
73
|
+
|
74
|
+
# Handle inheritance
|
75
|
+
visit_inheritance(node.superclass) if node.superclass
|
76
|
+
|
77
|
+
super
|
78
|
+
@current_scope.pop
|
18
79
|
end
|
19
80
|
|
20
|
-
def
|
21
|
-
node.
|
81
|
+
def visit_module_node(node)
|
82
|
+
@current_scope.push(node.constant_path.slice)
|
83
|
+
module_name = current_scope_name
|
84
|
+
|
85
|
+
range_id = @generator.create_range(node.location, node.location)
|
86
|
+
result_set_id = @generator.emit_vertex('resultSet')
|
87
|
+
@generator.emit_edge('next', range_id, result_set_id)
|
88
|
+
|
89
|
+
def_result_id = @generator.emit_vertex('definitionResult')
|
90
|
+
@generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
|
91
|
+
@generator.emit_edge('item', def_result_id, [range_id], 'definitions')
|
92
|
+
|
93
|
+
hover_id = @generator.emit_vertex('hoverResult', {
|
94
|
+
contents: [{
|
95
|
+
language: 'ruby',
|
96
|
+
value: "module #{module_name}"
|
97
|
+
}]
|
98
|
+
})
|
99
|
+
@generator.emit_edge('textDocument/hover', result_set_id, hover_id)
|
100
|
+
|
101
|
+
@generator.add_to_global_state(module_name, result_set_id, range_id)
|
102
|
+
|
103
|
+
super
|
104
|
+
@current_scope.pop
|
22
105
|
end
|
23
106
|
|
24
|
-
def
|
107
|
+
def visit_call_node(node)
|
108
|
+
return unless node.name && node.location
|
109
|
+
|
25
110
|
method_name = node.name.to_s
|
26
|
-
|
111
|
+
qualified_names = []
|
112
|
+
|
113
|
+
# Try with current scope first
|
114
|
+
qualified_names << "#{current_scope_name}##{method_name}" unless current_scope_name.empty?
|
115
|
+
|
116
|
+
# Try with receiver's type if available
|
117
|
+
if node.receiver
|
118
|
+
case node.receiver.type
|
119
|
+
when :constant_read
|
120
|
+
qualified_names << "#{node.receiver.name}##{method_name}"
|
121
|
+
when :call
|
122
|
+
# Handle method chaining
|
123
|
+
qualified_names << "#{node.receiver.name}##{method_name}" if node.receiver.name
|
124
|
+
when :instance_variable_read
|
125
|
+
# Handle instance variable calls
|
126
|
+
qualified_names << "#{current_scope_name}##{method_name}" if current_scope_name
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Try as a standalone method
|
131
|
+
qualified_names << method_name
|
132
|
+
|
133
|
+
# Add references for matching qualified names
|
134
|
+
qualified_names.each do |qualified_name|
|
135
|
+
next unless @generator.global_state.result_sets[qualified_name]
|
136
|
+
|
137
|
+
range_id = @generator.create_range(node.location, node.location)
|
138
|
+
@generator.global_state.add_reference(qualified_name, @generator.metadata[:uri], range_id)
|
139
|
+
@generator.emit_edge('next', range_id, @generator.global_state.result_sets[qualified_name])
|
140
|
+
break # Stop after finding first match
|
141
|
+
end
|
142
|
+
|
143
|
+
super
|
144
|
+
end
|
145
|
+
|
146
|
+
def visit_constant_read_node(node)
|
147
|
+
return unless node.name
|
148
|
+
|
149
|
+
constant_name = node.name.to_s
|
150
|
+
qualified_name = @current_scope.empty? ? constant_name : "#{current_scope_name}::#{constant_name}"
|
151
|
+
|
152
|
+
return unless @generator.global_state.result_sets[qualified_name]
|
153
|
+
|
154
|
+
range_id = @generator.create_range(node.location, node.location)
|
155
|
+
@generator.global_state.add_reference(qualified_name, @generator.metadata[:uri], range_id)
|
156
|
+
@generator.emit_edge('next', range_id, @generator.global_state.result_sets[qualified_name])
|
157
|
+
|
158
|
+
super
|
159
|
+
end
|
160
|
+
|
161
|
+
def visit_constant_write_node(node)
|
162
|
+
return unless node.name
|
163
|
+
|
164
|
+
constant_name = node.name.to_s
|
165
|
+
qualified_name = @current_scope.empty? ? constant_name : "#{current_scope_name}::#{constant_name}"
|
27
166
|
|
28
167
|
range_id = @generator.create_range(node.location, node.location)
|
29
168
|
result_set_id = @generator.emit_vertex('resultSet')
|
@@ -33,31 +172,84 @@ module Hind
|
|
33
172
|
@generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
|
34
173
|
@generator.emit_edge('item', def_result_id, [range_id], 'definitions')
|
35
174
|
|
36
|
-
# Add hover information
|
37
|
-
sig = []
|
38
|
-
sig << "def #{qualified_name}"
|
39
|
-
sig << "(#{node.parameters.slice})" if node.parameters
|
40
|
-
|
41
175
|
hover_id = @generator.emit_vertex('hoverResult', {
|
42
176
|
contents: [{
|
43
177
|
language: 'ruby',
|
44
|
-
value:
|
178
|
+
value: "#{qualified_name} = ..."
|
45
179
|
}]
|
46
180
|
})
|
47
181
|
@generator.emit_edge('textDocument/hover', result_set_id, hover_id)
|
48
182
|
|
49
183
|
@generator.add_to_global_state(qualified_name, result_set_id, range_id)
|
50
184
|
|
51
|
-
|
185
|
+
super
|
186
|
+
end
|
187
|
+
|
188
|
+
def visit_instance_variable_read_node(node)
|
189
|
+
return unless node.name && current_scope_name
|
190
|
+
|
191
|
+
var_name = node.name.to_s
|
192
|
+
qualified_name = "#{current_scope_name}##{var_name}"
|
193
|
+
|
194
|
+
return unless @generator.global_state.result_sets[qualified_name]
|
195
|
+
|
196
|
+
range_id = @generator.create_range(node.location, node.location)
|
197
|
+
@generator.global_state.add_reference(qualified_name, @generator.metadata[:uri], range_id)
|
198
|
+
@generator.emit_edge('next', range_id, @generator.global_state.result_sets[qualified_name])
|
199
|
+
|
200
|
+
super
|
52
201
|
end
|
53
202
|
|
54
|
-
|
203
|
+
def visit_instance_variable_write_node(node)
|
204
|
+
return unless node.name && current_scope_name
|
205
|
+
|
206
|
+
var_name = node.name.to_s
|
207
|
+
qualified_name = "#{current_scope_name}##{var_name}"
|
208
|
+
|
209
|
+
range_id = @generator.create_range(node.location, node.location)
|
210
|
+
result_set_id = @generator.emit_vertex('resultSet')
|
211
|
+
@generator.emit_edge('next', range_id, result_set_id)
|
212
|
+
|
213
|
+
def_result_id = @generator.emit_vertex('definitionResult')
|
214
|
+
@generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
|
215
|
+
@generator.emit_edge('item', def_result_id, [range_id], 'definitions')
|
216
|
+
|
217
|
+
hover_id = @generator.emit_vertex('hoverResult', {
|
218
|
+
contents: [{
|
219
|
+
language: 'ruby',
|
220
|
+
value: "Instance variable #{var_name} in #{current_scope_name}"
|
221
|
+
}]
|
222
|
+
})
|
223
|
+
@generator.emit_edge('textDocument/hover', result_set_id, hover_id)
|
224
|
+
|
225
|
+
@generator.add_to_global_state(qualified_name, result_set_id, range_id)
|
226
|
+
|
227
|
+
super
|
228
|
+
end
|
55
229
|
|
56
230
|
private
|
57
231
|
|
58
232
|
def current_scope_name
|
59
233
|
@current_scope.join('::')
|
60
234
|
end
|
235
|
+
|
236
|
+
def visit_inheritance(node)
|
237
|
+
case node.type
|
238
|
+
when :constant_read_node, :constant_path_node
|
239
|
+
range_id = @generator.create_range(node.location, node.location)
|
240
|
+
qualified_name = case node.type
|
241
|
+
when :constant_read_node
|
242
|
+
node.name.to_s
|
243
|
+
when :constant_path_node
|
244
|
+
node.slice
|
245
|
+
end
|
246
|
+
|
247
|
+
return unless @generator.global_state.result_sets[qualified_name]
|
248
|
+
|
249
|
+
@generator.global_state.add_reference(qualified_name, @generator.metadata[:uri], range_id)
|
250
|
+
@generator.emit_edge('next', range_id, @generator.global_state.result_sets[qualified_name])
|
251
|
+
end
|
252
|
+
end
|
61
253
|
end
|
62
254
|
end
|
63
255
|
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
# lib/hind/lsif/visitors/declaration_visitor.rb
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Hind
|
5
|
+
module LSIF
|
6
|
+
class DeclarationVisitor < Prism::Visitor
|
7
|
+
attr_reader :current_scope
|
8
|
+
|
9
|
+
def initialize(generator, file_path)
|
10
|
+
@generator = generator
|
11
|
+
@file_path = file_path
|
12
|
+
@current_scope = []
|
13
|
+
@current_visibility = :public
|
14
|
+
@visibility_stack = []
|
15
|
+
@in_singleton_class = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def visit_class_node(node)
|
19
|
+
@current_scope.push(node.constant_path.slice)
|
20
|
+
class_name = current_scope_name
|
21
|
+
|
22
|
+
# Register class declaration
|
23
|
+
@generator.register_declaration({
|
24
|
+
type: :class,
|
25
|
+
name: class_name,
|
26
|
+
node: node,
|
27
|
+
scope: @current_scope[0..-2].join('::'),
|
28
|
+
superclass: node.superclass&.slice
|
29
|
+
})
|
30
|
+
|
31
|
+
# Process the class body with proper scope and visibility
|
32
|
+
push_visibility(:public)
|
33
|
+
super
|
34
|
+
pop_visibility
|
35
|
+
@current_scope.pop
|
36
|
+
end
|
37
|
+
|
38
|
+
def visit_module_node(node)
|
39
|
+
@current_scope.push(node.constant_path.slice)
|
40
|
+
module_name = current_scope_name
|
41
|
+
|
42
|
+
@generator.register_declaration({
|
43
|
+
type: :module,
|
44
|
+
name: module_name,
|
45
|
+
node: node,
|
46
|
+
scope: @current_scope[0..-2].join('::')
|
47
|
+
})
|
48
|
+
|
49
|
+
push_visibility(:public)
|
50
|
+
super
|
51
|
+
pop_visibility
|
52
|
+
@current_scope.pop
|
53
|
+
end
|
54
|
+
|
55
|
+
def visit_def_node(node)
|
56
|
+
method_name = node.name.to_s
|
57
|
+
# Use '#' for instance methods
|
58
|
+
qualified_name = @in_singleton_class ?
|
59
|
+
"#{current_scope_name}.#{method_name}" :
|
60
|
+
"#{current_scope_name}##{method_name}"
|
61
|
+
|
62
|
+
@generator.register_declaration({
|
63
|
+
type: :method,
|
64
|
+
name: qualified_name,
|
65
|
+
node: node,
|
66
|
+
scope: current_scope_name,
|
67
|
+
visibility: current_visibility,
|
68
|
+
params: node.parameters&.slice,
|
69
|
+
instance_method: !@in_singleton_class
|
70
|
+
})
|
71
|
+
|
72
|
+
super
|
73
|
+
end
|
74
|
+
|
75
|
+
def visit_constant_write_node(node)
|
76
|
+
return unless node.name
|
77
|
+
|
78
|
+
constant_name = node.name.to_s
|
79
|
+
qualified_name = @current_scope.empty? ? constant_name : "#{current_scope_name}::#{constant_name}"
|
80
|
+
|
81
|
+
@generator.register_declaration({
|
82
|
+
type: :constant,
|
83
|
+
name: qualified_name,
|
84
|
+
node: node,
|
85
|
+
scope: current_scope_name
|
86
|
+
})
|
87
|
+
|
88
|
+
super
|
89
|
+
end
|
90
|
+
|
91
|
+
def visit_singleton_class_node(node)
|
92
|
+
if node.expression.is_a?(Prism::SelfNode)
|
93
|
+
# class << self
|
94
|
+
@in_singleton_class = true
|
95
|
+
super
|
96
|
+
@in_singleton_class = false
|
97
|
+
else
|
98
|
+
# Process regular singleton class
|
99
|
+
super
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def visit_call_node(node)
|
104
|
+
method_name = node.name.to_s
|
105
|
+
case method_name
|
106
|
+
when 'private', 'protected', 'public'
|
107
|
+
handle_visibility_method(node)
|
108
|
+
when 'attr_reader', 'attr_writer', 'attr_accessor'
|
109
|
+
handle_attribute_method(node)
|
110
|
+
end
|
111
|
+
|
112
|
+
super
|
113
|
+
end
|
114
|
+
|
115
|
+
def visit_class_variable_write_node(node)
|
116
|
+
return unless node.name
|
117
|
+
|
118
|
+
var_name = node.name.to_s
|
119
|
+
qualified_name = "#{current_scope_name}::#{var_name}"
|
120
|
+
|
121
|
+
@generator.register_declaration({
|
122
|
+
type: :class_variable,
|
123
|
+
name: qualified_name,
|
124
|
+
node: node,
|
125
|
+
scope: current_scope_name
|
126
|
+
})
|
127
|
+
|
128
|
+
super
|
129
|
+
end
|
130
|
+
|
131
|
+
def visit_instance_variable_write_node(node)
|
132
|
+
return unless node.name && current_scope_name
|
133
|
+
|
134
|
+
var_name = node.name.to_s
|
135
|
+
qualified_name = "#{current_scope_name}##{var_name}"
|
136
|
+
|
137
|
+
@generator.register_declaration({
|
138
|
+
type: :instance_variable,
|
139
|
+
name: qualified_name,
|
140
|
+
node: node,
|
141
|
+
scope: current_scope_name
|
142
|
+
})
|
143
|
+
|
144
|
+
super
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def current_scope_name
|
150
|
+
@current_scope.join('::')
|
151
|
+
end
|
152
|
+
|
153
|
+
def current_visibility
|
154
|
+
@current_visibility
|
155
|
+
end
|
156
|
+
|
157
|
+
def push_visibility(visibility)
|
158
|
+
@visibility_stack.push(@current_visibility)
|
159
|
+
@current_visibility = visibility
|
160
|
+
end
|
161
|
+
|
162
|
+
def pop_visibility
|
163
|
+
@current_visibility = @visibility_stack.pop || :public
|
164
|
+
end
|
165
|
+
|
166
|
+
def handle_visibility_method(node)
|
167
|
+
visibility = node.name.to_sym
|
168
|
+
|
169
|
+
if node.arguments&.arguments&.empty?
|
170
|
+
# Global visibility change
|
171
|
+
@current_visibility = visibility
|
172
|
+
elsif node.arguments
|
173
|
+
# Per-method visibility change
|
174
|
+
node.arguments.arguments.each do |arg|
|
175
|
+
next unless arg.is_a?(Prism::SymbolNode) || arg.is_a?(Prism::StringNode)
|
176
|
+
|
177
|
+
method_name = arg.value.to_s
|
178
|
+
qualified_name = "#{current_scope_name}##{method_name}"
|
179
|
+
|
180
|
+
if @generator.global_state.has_declaration?(qualified_name)
|
181
|
+
@generator.global_state.get_declaration(qualified_name)[:visibility] = visibility
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def handle_attribute_method(node)
|
188
|
+
return unless node.arguments
|
189
|
+
|
190
|
+
attr_type = node.name.to_s
|
191
|
+
node.arguments.arguments.each do |arg|
|
192
|
+
next unless arg.respond_to?(:value)
|
193
|
+
|
194
|
+
attr_name = arg.value.to_s
|
195
|
+
register_attribute(attr_name, attr_type)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def register_attribute(attr_name, attr_type)
|
200
|
+
base_name = "#{current_scope_name}##{attr_name}"
|
201
|
+
|
202
|
+
# Register reader method if applicable
|
203
|
+
if %w[attr_reader attr_accessor].include?(attr_type)
|
204
|
+
@generator.register_declaration({
|
205
|
+
type: :method,
|
206
|
+
name: base_name,
|
207
|
+
node: nil, # Synthetic node
|
208
|
+
scope: current_scope_name,
|
209
|
+
visibility: current_visibility,
|
210
|
+
synthetic: true,
|
211
|
+
kind: :reader
|
212
|
+
})
|
213
|
+
end
|
214
|
+
|
215
|
+
# Register writer method if applicable
|
216
|
+
if %w[attr_writer attr_accessor].include?(attr_type)
|
217
|
+
@generator.register_declaration({
|
218
|
+
type: :method,
|
219
|
+
name: "#{base_name}=",
|
220
|
+
node: nil, # Synthetic node
|
221
|
+
scope: current_scope_name,
|
222
|
+
visibility: current_visibility,
|
223
|
+
synthetic: true,
|
224
|
+
kind: :writer
|
225
|
+
})
|
226
|
+
end
|
227
|
+
|
228
|
+
# Register the instance variable
|
229
|
+
@generator.register_declaration({
|
230
|
+
type: :instance_variable,
|
231
|
+
name: "@#{attr_name}",
|
232
|
+
node: nil, # Synthetic node
|
233
|
+
scope: current_scope_name,
|
234
|
+
synthetic: true
|
235
|
+
})
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|