hind 0.1.4 → 0.1.5
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 +11 -15
- data/lib/hind/lsif/edge.rb +3 -1
- data/lib/hind/lsif/generator.rb +7 -6
- data/lib/hind/lsif/global_state.rb +4 -2
- data/lib/hind/lsif/vertex.rb +3 -1
- data/lib/hind/lsif/visitor.rb +208 -3
- 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 +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5fa041a7ffe7e916e1f3715500d21a9dea2a3a6ce60990be8cf4e7578f704702
|
|
4
|
+
data.tar.gz: '0779f87c520ddc1edddf0448c96d596194982d5b87fa5c380bb3a1132500bd9e'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 397847759b6c959511602d276eee22f7e89959c5858d840cf8398b874a39ef6ccfeec8d876e9c0539d5cc0bc0cce77cb1e297b7fabc30c9a4562bfb7e85ddb7b
|
|
7
|
+
data.tar.gz: 5c823067473c8b784a4868bf696b4374735b548eb8eb9341fb887dc78028e9223aeaac7172c621c624d0e8a3de221b50e101e908177626f9c316797e8ce2de70
|
data/exe/hind
CHANGED
data/lib/hind/cli.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'thor'
|
|
2
4
|
require 'json'
|
|
3
5
|
require 'pathname'
|
|
@@ -26,7 +28,7 @@ module Hind
|
|
|
26
28
|
begin
|
|
27
29
|
generate_lsif(files, options)
|
|
28
30
|
say "\nLSIF data has been written to: #{options[:output]}", :green if options[:verbose]
|
|
29
|
-
rescue
|
|
31
|
+
rescue => e
|
|
30
32
|
abort "Error generating LSIF: #{e.message}"
|
|
31
33
|
end
|
|
32
34
|
end
|
|
@@ -49,7 +51,7 @@ module Hind
|
|
|
49
51
|
begin
|
|
50
52
|
generate_scip(files, options)
|
|
51
53
|
say "\nSCIP data has been written to: #{options[:output]}", :green if options[:verbose]
|
|
52
|
-
rescue
|
|
54
|
+
rescue => e
|
|
53
55
|
abort "Error generating SCIP: #{e.message}"
|
|
54
56
|
end
|
|
55
57
|
end
|
|
@@ -66,9 +68,7 @@ module Hind
|
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
def validate_output_file(output, force)
|
|
69
|
-
if File.exist?(output) && !force
|
|
70
|
-
abort "Error: Output file '#{output}' already exists. Use --force to overwrite."
|
|
71
|
-
end
|
|
71
|
+
abort "Error: Output file '#{output}' already exists. Use --force to overwrite." if File.exist?(output) && !force
|
|
72
72
|
|
|
73
73
|
# Ensure output directory exists
|
|
74
74
|
FileUtils.mkdir_p(File.dirname(output))
|
|
@@ -78,10 +78,8 @@ module Hind
|
|
|
78
78
|
pattern = File.join(directory, glob)
|
|
79
79
|
files = Dir.glob(pattern)
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
files.reject! { |f| File.fnmatch?(exclude, f) }
|
|
84
|
-
end
|
|
81
|
+
exclude_patterns&.each do |exclude|
|
|
82
|
+
files.reject! { |f| File.fnmatch?(exclude, f) }
|
|
85
83
|
end
|
|
86
84
|
|
|
87
85
|
files
|
|
@@ -94,9 +92,7 @@ module Hind
|
|
|
94
92
|
|
|
95
93
|
File.open(options[:output], 'w') do |output_file|
|
|
96
94
|
files.each do |file|
|
|
97
|
-
if options[:verbose]
|
|
98
|
-
say "Processing file: #{file}", :cyan
|
|
99
|
-
end
|
|
95
|
+
say "Processing file: #{file}", :cyan if options[:verbose]
|
|
100
96
|
|
|
101
97
|
relative_path = Pathname.new(file).relative_path_from(Pathname.new(options[:directory])).to_s
|
|
102
98
|
|
|
@@ -115,7 +111,7 @@ module Hind
|
|
|
115
111
|
vertex_id = output.last[:id].to_i + 1
|
|
116
112
|
output_file.puts(output.map(&:to_json).join("\n"))
|
|
117
113
|
initial = false
|
|
118
|
-
rescue
|
|
114
|
+
rescue => e
|
|
119
115
|
warn "Warning: Failed to process file '#{file}': #{e.message}"
|
|
120
116
|
next
|
|
121
117
|
end
|
|
@@ -124,7 +120,7 @@ module Hind
|
|
|
124
120
|
end
|
|
125
121
|
|
|
126
122
|
def generate_scip(files, options)
|
|
127
|
-
raise NotImplementedError,
|
|
123
|
+
raise NotImplementedError, 'SCIP generation not yet implemented'
|
|
128
124
|
# Similar to generate_lsif but using SCIP generator
|
|
129
125
|
end
|
|
130
126
|
|
|
@@ -133,7 +129,7 @@ module Hind
|
|
|
133
129
|
|
|
134
130
|
begin
|
|
135
131
|
YAML.load_file(config_path) || {}
|
|
136
|
-
rescue
|
|
132
|
+
rescue => e
|
|
137
133
|
abort "Error loading config file: #{e.message}"
|
|
138
134
|
end
|
|
139
135
|
end
|
data/lib/hind/lsif/edge.rb
CHANGED
data/lib/hind/lsif/generator.rb
CHANGED
|
@@ -91,7 +91,7 @@ module Hind
|
|
|
91
91
|
}
|
|
92
92
|
})
|
|
93
93
|
|
|
94
|
-
@global_state.project_id = emit_vertex('project', {
|
|
94
|
+
@global_state.project_id = emit_vertex('project', {kind: 'ruby'})
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def setup_document
|
|
@@ -110,9 +110,9 @@ module Hind
|
|
|
110
110
|
file_path = File.join(@metadata[:projectRoot], @metadata[:uri])
|
|
111
111
|
ranges = @global_state.ranges[file_path]
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
return unless ranges&.any?
|
|
114
|
+
|
|
115
|
+
emit_edge('contains', @document_id, ranges)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
def update_cross_file_references
|
|
@@ -150,15 +150,16 @@ module Hind
|
|
|
150
150
|
def valid_in_v?(in_v)
|
|
151
151
|
return false unless in_v
|
|
152
152
|
return in_v.any? if in_v.is_a?(Array)
|
|
153
|
+
|
|
153
154
|
true
|
|
154
155
|
end
|
|
155
156
|
|
|
156
157
|
def edge_document(label)
|
|
157
|
-
label == 'item' ? @document_id : nil
|
|
158
|
+
(label == 'item') ? @document_id : nil
|
|
158
159
|
end
|
|
159
160
|
|
|
160
161
|
def path_to_uri(path)
|
|
161
|
-
normalized_path = path.
|
|
162
|
+
normalized_path = path.tr('\\', '/')
|
|
162
163
|
normalized_path = normalized_path.sub(%r{^file://}, '')
|
|
163
164
|
absolute_path = File.expand_path(normalized_path)
|
|
164
165
|
"file://#{absolute_path}"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Hind
|
|
2
4
|
module LSIF
|
|
3
5
|
class GlobalState
|
|
@@ -18,12 +20,12 @@ module Hind
|
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def add_definition(qualified_name, file_path, range_id)
|
|
21
|
-
@definitions[qualified_name] = {
|
|
23
|
+
@definitions[qualified_name] = {file: file_path, range_id: range_id}
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def add_reference(qualified_name, file_path, range_id)
|
|
25
27
|
@references[qualified_name] ||= []
|
|
26
|
-
@references[qualified_name] << {
|
|
28
|
+
@references[qualified_name] << {file: file_path, range_id: range_id}
|
|
27
29
|
end
|
|
28
30
|
end
|
|
29
31
|
end
|
data/lib/hind/lsif/vertex.rb
CHANGED
data/lib/hind/lsif/visitor.rb
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# lib/hind/lsif/visitor.rb
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
1
4
|
module Hind
|
|
2
5
|
module LSIF
|
|
3
6
|
class Visitor
|
|
@@ -9,7 +12,7 @@ module Hind
|
|
|
9
12
|
def visit(node)
|
|
10
13
|
return unless node
|
|
11
14
|
|
|
12
|
-
method_name = "visit_#{node.class.name.split(
|
|
15
|
+
method_name = "visit_#{node.class.name.split("::").last.downcase}"
|
|
13
16
|
if respond_to?(method_name)
|
|
14
17
|
send(method_name, node)
|
|
15
18
|
else
|
|
@@ -22,6 +25,7 @@ module Hind
|
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
def visit_defnode(node)
|
|
28
|
+
# Handle method definitions
|
|
25
29
|
method_name = node.name.to_s
|
|
26
30
|
qualified_name = current_scope_name.empty? ? method_name : "#{current_scope_name}##{method_name}"
|
|
27
31
|
|
|
@@ -33,7 +37,7 @@ module Hind
|
|
|
33
37
|
@generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
|
|
34
38
|
@generator.emit_edge('item', def_result_id, [range_id], 'definitions')
|
|
35
39
|
|
|
36
|
-
#
|
|
40
|
+
# Generate method signature for hover
|
|
37
41
|
sig = []
|
|
38
42
|
sig << "def #{qualified_name}"
|
|
39
43
|
sig << "(#{node.parameters.slice})" if node.parameters
|
|
@@ -51,13 +55,214 @@ module Hind
|
|
|
51
55
|
visit_children(node)
|
|
52
56
|
end
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
def visit_classnode(node)
|
|
59
|
+
@current_scope.push(node.constant_path.slice)
|
|
60
|
+
class_name = current_scope_name
|
|
61
|
+
|
|
62
|
+
range_id = @generator.create_range(node.location, node.location)
|
|
63
|
+
result_set_id = @generator.emit_vertex('resultSet')
|
|
64
|
+
@generator.emit_edge('next', range_id, result_set_id)
|
|
65
|
+
|
|
66
|
+
def_result_id = @generator.emit_vertex('definitionResult')
|
|
67
|
+
@generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
|
|
68
|
+
@generator.emit_edge('item', def_result_id, [range_id], 'definitions')
|
|
69
|
+
|
|
70
|
+
# Generate hover with inheritance info
|
|
71
|
+
hover = []
|
|
72
|
+
class_def = "class #{class_name}"
|
|
73
|
+
class_def += " < #{node.superclass.slice}" if node.superclass
|
|
74
|
+
|
|
75
|
+
hover << if node.superclass
|
|
76
|
+
"#{class_def}\n\nInherits from: #{node.superclass.slice}"
|
|
77
|
+
else
|
|
78
|
+
class_def
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
hover_id = @generator.emit_vertex('hoverResult', {
|
|
82
|
+
contents: [{
|
|
83
|
+
language: 'ruby',
|
|
84
|
+
value: hover.join("\n")
|
|
85
|
+
}]
|
|
86
|
+
})
|
|
87
|
+
@generator.emit_edge('textDocument/hover', result_set_id, hover_id)
|
|
88
|
+
|
|
89
|
+
@generator.add_to_global_state(class_name, result_set_id, range_id)
|
|
90
|
+
|
|
91
|
+
# Handle inheritance
|
|
92
|
+
visit_inheritance(node.superclass) if node.superclass
|
|
93
|
+
|
|
94
|
+
visit_children(node)
|
|
95
|
+
@current_scope.pop
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def visit_modulenode(node)
|
|
99
|
+
@current_scope.push(node.constant_path.slice)
|
|
100
|
+
module_name = current_scope_name
|
|
101
|
+
|
|
102
|
+
range_id = @generator.create_range(node.location, node.location)
|
|
103
|
+
result_set_id = @generator.emit_vertex('resultSet')
|
|
104
|
+
@generator.emit_edge('next', range_id, result_set_id)
|
|
105
|
+
|
|
106
|
+
def_result_id = @generator.emit_vertex('definitionResult')
|
|
107
|
+
@generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
|
|
108
|
+
@generator.emit_edge('item', def_result_id, [range_id], 'definitions')
|
|
109
|
+
|
|
110
|
+
hover_id = @generator.emit_vertex('hoverResult', {
|
|
111
|
+
contents: [{
|
|
112
|
+
language: 'ruby',
|
|
113
|
+
value: "module #{module_name}"
|
|
114
|
+
}]
|
|
115
|
+
})
|
|
116
|
+
@generator.emit_edge('textDocument/hover', result_set_id, hover_id)
|
|
117
|
+
|
|
118
|
+
@generator.add_to_global_state(module_name, result_set_id, range_id)
|
|
119
|
+
|
|
120
|
+
visit_children(node)
|
|
121
|
+
@current_scope.pop
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def visit_callnode(node)
|
|
125
|
+
return unless node.name && node.location
|
|
126
|
+
|
|
127
|
+
method_name = node.name.to_s
|
|
128
|
+
qualified_names = []
|
|
129
|
+
|
|
130
|
+
# Try with current scope first
|
|
131
|
+
qualified_names << "#{current_scope_name}##{method_name}" unless current_scope_name.empty?
|
|
132
|
+
|
|
133
|
+
# Try with receiver's type if available
|
|
134
|
+
if node.receiver
|
|
135
|
+
case node.receiver
|
|
136
|
+
when Prism::ConstantReadNode
|
|
137
|
+
qualified_names << "#{node.receiver.name}##{method_name}"
|
|
138
|
+
when Prism::CallNode
|
|
139
|
+
# Handle method chaining
|
|
140
|
+
qualified_names << "#{node.receiver.name}##{method_name}" if node.receiver.name
|
|
141
|
+
when Prism::InstanceVariableReadNode
|
|
142
|
+
# Handle instance variable calls
|
|
143
|
+
qualified_names << "#{current_scope_name}##{method_name}" if current_scope_name
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Try as a standalone method
|
|
148
|
+
qualified_names << method_name
|
|
149
|
+
|
|
150
|
+
# Add references for matching qualified names
|
|
151
|
+
qualified_names.each do |qualified_name|
|
|
152
|
+
next 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
|
+
break # Stop after finding first match
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
visit_children(node)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def visit_constantreadnode(node)
|
|
164
|
+
return unless node.name
|
|
165
|
+
|
|
166
|
+
constant_name = node.name.to_s
|
|
167
|
+
qualified_name = @current_scope.empty? ? constant_name : "#{current_scope_name}::#{constant_name}"
|
|
168
|
+
|
|
169
|
+
return unless @generator.global_state.result_sets[qualified_name]
|
|
170
|
+
|
|
171
|
+
range_id = @generator.create_range(node.location, node.location)
|
|
172
|
+
@generator.global_state.add_reference(qualified_name, @generator.metadata[:uri], range_id)
|
|
173
|
+
@generator.emit_edge('next', range_id, @generator.global_state.result_sets[qualified_name])
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def visit_constantwritenode(node)
|
|
177
|
+
return unless node.name
|
|
178
|
+
|
|
179
|
+
constant_name = node.name.to_s
|
|
180
|
+
qualified_name = @current_scope.empty? ? constant_name : "#{current_scope_name}::#{constant_name}"
|
|
181
|
+
|
|
182
|
+
range_id = @generator.create_range(node.location, node.location)
|
|
183
|
+
result_set_id = @generator.emit_vertex('resultSet')
|
|
184
|
+
@generator.emit_edge('next', range_id, result_set_id)
|
|
185
|
+
|
|
186
|
+
def_result_id = @generator.emit_vertex('definitionResult')
|
|
187
|
+
@generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
|
|
188
|
+
@generator.emit_edge('item', def_result_id, [range_id], 'definitions')
|
|
189
|
+
|
|
190
|
+
hover_id = @generator.emit_vertex('hoverResult', {
|
|
191
|
+
contents: [{
|
|
192
|
+
language: 'ruby',
|
|
193
|
+
value: "#{qualified_name} = ..."
|
|
194
|
+
}]
|
|
195
|
+
})
|
|
196
|
+
@generator.emit_edge('textDocument/hover', result_set_id, hover_id)
|
|
197
|
+
|
|
198
|
+
@generator.add_to_global_state(qualified_name, result_set_id, range_id)
|
|
199
|
+
|
|
200
|
+
visit_children(node)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def visit_instancevariablereadnode(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
|
+
return unless @generator.global_state.result_sets[qualified_name]
|
|
210
|
+
|
|
211
|
+
range_id = @generator.create_range(node.location, node.location)
|
|
212
|
+
@generator.global_state.add_reference(qualified_name, @generator.metadata[:uri], range_id)
|
|
213
|
+
@generator.emit_edge('next', range_id, @generator.global_state.result_sets[qualified_name])
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def visit_instancevariablewritenode(node)
|
|
217
|
+
return unless node.name && current_scope_name
|
|
218
|
+
|
|
219
|
+
var_name = node.name.to_s
|
|
220
|
+
qualified_name = "#{current_scope_name}##{var_name}"
|
|
221
|
+
|
|
222
|
+
range_id = @generator.create_range(node.location, node.location)
|
|
223
|
+
result_set_id = @generator.emit_vertex('resultSet')
|
|
224
|
+
@generator.emit_edge('next', range_id, result_set_id)
|
|
225
|
+
|
|
226
|
+
def_result_id = @generator.emit_vertex('definitionResult')
|
|
227
|
+
@generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
|
|
228
|
+
@generator.emit_edge('item', def_result_id, [range_id], 'definitions')
|
|
229
|
+
|
|
230
|
+
hover_id = @generator.emit_vertex('hoverResult', {
|
|
231
|
+
contents: [{
|
|
232
|
+
language: 'ruby',
|
|
233
|
+
value: "Instance variable #{var_name} in #{current_scope_name}"
|
|
234
|
+
}]
|
|
235
|
+
})
|
|
236
|
+
@generator.emit_edge('textDocument/hover', result_set_id, hover_id)
|
|
237
|
+
|
|
238
|
+
@generator.add_to_global_state(qualified_name, result_set_id, range_id)
|
|
239
|
+
|
|
240
|
+
visit_children(node)
|
|
241
|
+
end
|
|
55
242
|
|
|
56
243
|
private
|
|
57
244
|
|
|
58
245
|
def current_scope_name
|
|
59
246
|
@current_scope.join('::')
|
|
60
247
|
end
|
|
248
|
+
|
|
249
|
+
def visit_inheritance(node)
|
|
250
|
+
case node
|
|
251
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
|
252
|
+
range_id = @generator.create_range(node.location, node.location)
|
|
253
|
+
qualified_name = case node
|
|
254
|
+
when Prism::ConstantReadNode
|
|
255
|
+
node.name.to_s
|
|
256
|
+
when Prism::ConstantPathNode
|
|
257
|
+
node.slice
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
return unless @generator.global_state.result_sets[qualified_name]
|
|
261
|
+
|
|
262
|
+
@generator.global_state.add_reference(qualified_name, @generator.metadata[:uri], range_id)
|
|
263
|
+
@generator.emit_edge('next', range_id, @generator.global_state.result_sets[qualified_name])
|
|
264
|
+
end
|
|
265
|
+
end
|
|
61
266
|
end
|
|
62
267
|
end
|
|
63
268
|
end
|
data/lib/hind/lsif.rb
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
require_relative "lsif/edge"
|
|
3
|
-
require_relative "lsif/generator"
|
|
4
|
-
require_relative "lsif/vertex"
|
|
5
|
-
require_relative "lsif/visitor"
|
|
1
|
+
# frozen_string_literal: true
|
|
6
2
|
|
|
3
|
+
require_relative 'lsif/global_state'
|
|
4
|
+
require_relative 'lsif/edge'
|
|
5
|
+
require_relative 'lsif/generator'
|
|
6
|
+
require_relative 'lsif/vertex'
|
|
7
|
+
require_relative 'lsif/visitor'
|
|
7
8
|
|
|
8
9
|
module Hind
|
|
9
10
|
module LSIF
|
data/lib/hind/parser.rb
CHANGED
data/lib/hind/scip/generator.rb
CHANGED
data/lib/hind/scip/visitor.rb
CHANGED
data/lib/hind/scip.rb
CHANGED
data/lib/hind/version.rb
CHANGED
data/lib/hind.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
3
|
+
require_relative 'hind/version'
|
|
4
|
+
require_relative 'hind/lsif'
|
|
5
|
+
require_relative 'hind/scip'
|
|
6
|
+
require_relative 'hind/parser'
|
|
7
7
|
|
|
8
8
|
module Hind
|
|
9
9
|
class Error < StandardError; end
|