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.
@@ -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 visit(node)
10
- return unless node
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
- method_name = "visit_#{node.class.name.split('::').last.downcase}"
13
- if respond_to?(method_name)
14
- send(method_name, node)
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
- visit_children(node)
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 visit_children(node)
21
- node.child_nodes.each { |child| visit(child) if child }
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 visit_defnode(node)
107
+ def visit_call_node(node)
108
+ return unless node.name && node.location
109
+
25
110
  method_name = node.name.to_s
26
- qualified_name = current_scope_name.empty? ? method_name : "#{current_scope_name}##{method_name}"
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: sig.join
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
- visit_children(node)
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
- # Additional visitor methods...
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