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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66f58f99f053b938815d051ff6404a478a4f340178f74556ce6ab63f0a93c2e4
|
4
|
+
data.tar.gz: 02e04a6c88687f9baf37013fc216dc7df52b5fa6e81f60170cbb31146335c91b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 529d648a5f0677180c4c73daa8d316661f4b82b7bd4802318c8bb42a7ed7bd8d2ffb38b69a82a274483638ea7bc1c161ea9d6bb6f3c7f84e3ff75464a1968c75
|
7
|
+
data.tar.gz: dc734f7623cffa1c0cc6260627d14916e6e9809c3b06afcad1bd87015c8c32b8bbe979ed978abb922adc3af7a19c7c3099f3217c00016711260aacd5079fab6c
|
data/exe/hind
CHANGED
data/lib/hind/cli.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thor'
|
2
4
|
require 'json'
|
3
5
|
require 'pathname'
|
4
6
|
require 'fileutils'
|
7
|
+
require 'yaml'
|
5
8
|
|
6
9
|
module Hind
|
7
10
|
class CLI < Thor
|
@@ -14,43 +17,47 @@ module Hind
|
|
14
17
|
method_option :glob, type: :string, aliases: '-g', default: '**/*.rb', desc: 'File pattern to match'
|
15
18
|
method_option :force, type: :boolean, aliases: '-f', desc: 'Overwrite output file if it exists'
|
16
19
|
method_option :exclude, type: :array, aliases: '-e', desc: 'Patterns to exclude'
|
20
|
+
method_option :workers, type: :numeric, aliases: '-w', default: 1, desc: 'Number of parallel workers'
|
17
21
|
def lsif
|
18
|
-
|
19
|
-
|
22
|
+
config = load_config(options[:config])
|
23
|
+
opts = config.merge(symbolize_keys(options))
|
24
|
+
|
25
|
+
validate_directory(opts[:directory])
|
26
|
+
validate_output_file(opts[:output], opts[:force])
|
20
27
|
|
21
|
-
files = find_files(
|
22
|
-
abort "No files found matching pattern '#{
|
28
|
+
files = find_files(opts[:directory], opts[:glob], opts[:exclude])
|
29
|
+
abort "No files found matching pattern '#{opts[:glob]}'" if files.empty?
|
23
30
|
|
24
|
-
say "Found #{files.length} files to process", :green if
|
31
|
+
say "Found #{files.length} files to process", :green if opts[:verbose]
|
25
32
|
|
26
33
|
begin
|
27
|
-
generate_lsif(files,
|
28
|
-
say "\nLSIF data has been written to: #{
|
34
|
+
generate_lsif(files, opts)
|
35
|
+
say "\nLSIF data has been written to: #{opts[:output]}", :green if opts[:verbose]
|
29
36
|
rescue StandardError => e
|
30
|
-
|
37
|
+
handle_error(e, opts[:verbose])
|
31
38
|
end
|
32
39
|
end
|
33
40
|
|
34
|
-
desc '
|
35
|
-
method_option :
|
36
|
-
method_option :
|
37
|
-
method_option :
|
38
|
-
|
39
|
-
|
40
|
-
def scip
|
41
|
-
validate_directory(options[:directory])
|
42
|
-
validate_output_file(options[:output], options[:force])
|
41
|
+
desc 'check', 'Check LSIF dump file for validity and provide insights'
|
42
|
+
method_option :file, type: :string, aliases: '-f', default: 'dump.lsif', desc: 'LSIF dump file to check'
|
43
|
+
method_option :json, type: :boolean, desc: 'Output results in JSON format'
|
44
|
+
method_option :strict, type: :boolean, desc: 'Treat warnings as errors'
|
45
|
+
def check
|
46
|
+
abort "Error: File '#{options[:file]}' does not exist" unless File.exist?(options[:file])
|
43
47
|
|
44
|
-
|
45
|
-
|
48
|
+
begin
|
49
|
+
checker = Hind::LSIF::Checker.new(options[:file])
|
50
|
+
results = checker.check
|
46
51
|
|
47
|
-
|
52
|
+
if options[:json]
|
53
|
+
puts JSON.pretty_generate(results)
|
54
|
+
else
|
55
|
+
print_check_results(results)
|
56
|
+
end
|
48
57
|
|
49
|
-
|
50
|
-
generate_scip(files, options)
|
51
|
-
say "\nSCIP data has been written to: #{options[:output]}", :green if options[:verbose]
|
58
|
+
exit(1) if !results[:valid] || (options[:strict] && results[:warnings].any?)
|
52
59
|
rescue StandardError => e
|
53
|
-
|
60
|
+
handle_error(e, options[:verbose])
|
54
61
|
end
|
55
62
|
end
|
56
63
|
|
@@ -59,8 +66,73 @@ module Hind
|
|
59
66
|
say "Hind version #{Hind::VERSION}"
|
60
67
|
end
|
61
68
|
|
69
|
+
desc 'init', 'Initialize Hind configuration file'
|
70
|
+
method_option :force, type: :boolean, aliases: '-f', desc: 'Overwrite existing configuration'
|
71
|
+
def init
|
72
|
+
config_file = '.hind.yml'
|
73
|
+
if File.exist?(config_file) && !options[:force]
|
74
|
+
abort "Configuration file already exists. Use --force to overwrite."
|
75
|
+
end
|
76
|
+
|
77
|
+
create_default_config(config_file)
|
78
|
+
say "Created configuration file: #{config_file}", :green
|
79
|
+
end
|
80
|
+
|
62
81
|
private
|
63
82
|
|
83
|
+
def generate_lsif(files, options)
|
84
|
+
# Initialize generator with absolute project root
|
85
|
+
generator = Hind::LSIF::Generator.new(
|
86
|
+
{
|
87
|
+
vertex_id: 1,
|
88
|
+
initial: true,
|
89
|
+
projectRoot: File.expand_path(options[:directory])
|
90
|
+
}
|
91
|
+
)
|
92
|
+
|
93
|
+
# Create file content map with relative paths
|
94
|
+
file_contents = {}
|
95
|
+
files.each do |file|
|
96
|
+
absolute_path = File.expand_path(file)
|
97
|
+
relative_path = Pathname.new(absolute_path)
|
98
|
+
.relative_path_from(Pathname.new(generator.metadata[:projectRoot]))
|
99
|
+
.to_s
|
100
|
+
file_contents[relative_path] = File.read(absolute_path)
|
101
|
+
rescue StandardError => e
|
102
|
+
warn "Warning: Failed to read file '#{file}': #{e.message}"
|
103
|
+
next
|
104
|
+
end
|
105
|
+
|
106
|
+
File.open(options[:output], 'w') do |output_file|
|
107
|
+
say "First pass: Collecting declarations...", :cyan if options[:verbose]
|
108
|
+
|
109
|
+
# First pass: Process all files to collect declarations
|
110
|
+
declaration_data = generator.collect_declarations(file_contents)
|
111
|
+
|
112
|
+
say "Found #{declaration_data[:declarations].size} declarations", :cyan if options[:verbose]
|
113
|
+
say "Processing files...", :cyan if options[:verbose]
|
114
|
+
|
115
|
+
# Second pass: Process each file
|
116
|
+
file_contents.each do |relative_path, content|
|
117
|
+
if options[:verbose]
|
118
|
+
say "Processing file: #{relative_path}", :cyan
|
119
|
+
end
|
120
|
+
|
121
|
+
lsif_data = generator.process_file(
|
122
|
+
content: content,
|
123
|
+
uri: relative_path
|
124
|
+
)
|
125
|
+
|
126
|
+
output_file.puts(lsif_data.map(&:to_json).join("\n"))
|
127
|
+
end
|
128
|
+
|
129
|
+
# Write cross-reference data
|
130
|
+
say "Finalizing cross-references...", :cyan if options[:verbose]
|
131
|
+
cross_refs = generator.finalize_cross_references
|
132
|
+
output_file.puts(cross_refs.map(&:to_json).join("\n")) if cross_refs&.any?
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
64
136
|
def validate_directory(directory)
|
65
137
|
abort "Error: Directory '#{directory}' does not exist" unless Dir.exist?(directory)
|
66
138
|
end
|
@@ -87,55 +159,100 @@ module Hind
|
|
87
159
|
files
|
88
160
|
end
|
89
161
|
|
90
|
-
def
|
91
|
-
|
92
|
-
vertex_id = 1
|
93
|
-
initial = true
|
162
|
+
def load_config(config_path)
|
163
|
+
return {} unless config_path && File.exist?(config_path)
|
94
164
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
165
|
+
begin
|
166
|
+
YAML.load_file(config_path) || {}
|
167
|
+
rescue StandardError => e
|
168
|
+
abort "Error loading config file: #{e.message}"
|
169
|
+
end
|
170
|
+
end
|
100
171
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
172
|
+
def create_default_config(config_file)
|
173
|
+
config = {
|
174
|
+
'directory' => '.',
|
175
|
+
'output' => 'dump.lsif',
|
176
|
+
'glob' => '**/*.rb',
|
177
|
+
'exclude' => [
|
178
|
+
'test/**/*',
|
179
|
+
'spec/**/*',
|
180
|
+
'vendor/**/*'
|
181
|
+
],
|
182
|
+
'workers' => 1
|
183
|
+
}
|
184
|
+
|
185
|
+
File.write(config_file, config.to_yaml)
|
186
|
+
end
|
187
|
+
|
188
|
+
def print_check_results(results)
|
189
|
+
print_check_status(results[:valid])
|
190
|
+
print_check_errors(results[:errors])
|
191
|
+
print_check_warnings(results[:warnings])
|
192
|
+
print_check_statistics(results[:statistics])
|
193
|
+
end
|
194
|
+
|
195
|
+
def print_check_status(valid)
|
196
|
+
status = valid ? "✅ LSIF dump is valid" : "❌ LSIF dump contains errors"
|
197
|
+
say(status, valid ? :green : :red)
|
198
|
+
puts
|
199
|
+
end
|
200
|
+
|
201
|
+
def print_check_errors(errors)
|
202
|
+
return if errors.empty?
|
203
|
+
|
204
|
+
say "Errors:", :red
|
205
|
+
errors.each do |error|
|
206
|
+
say " • #{error}", :red
|
123
207
|
end
|
208
|
+
puts
|
124
209
|
end
|
125
210
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
211
|
+
def print_check_warnings(warnings)
|
212
|
+
return if warnings.empty?
|
213
|
+
|
214
|
+
say "Warnings:", :yellow
|
215
|
+
warnings.each do |warning|
|
216
|
+
say " • #{warning}", :yellow
|
217
|
+
end
|
218
|
+
puts
|
129
219
|
end
|
130
220
|
|
131
|
-
def
|
132
|
-
|
221
|
+
def print_check_statistics(stats)
|
222
|
+
say "Statistics:", :cyan
|
223
|
+
say " Total Elements: #{stats[:total_elements]}"
|
224
|
+
say " Vertices: #{stats[:vertices][:total]}"
|
225
|
+
say " Edges: #{stats[:edges][:total]}"
|
226
|
+
say " Vertex/Edge Ratio: #{stats[:vertex_to_edge_ratio]}"
|
227
|
+
puts
|
133
228
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
229
|
+
say " Documents: #{stats[:documents]}"
|
230
|
+
say " Ranges: #{stats[:ranges]}"
|
231
|
+
say " Definitions: #{stats[:definitions]}"
|
232
|
+
say " References: #{stats[:references]}"
|
233
|
+
say " Hovers: #{stats[:hovers]}"
|
234
|
+
puts
|
235
|
+
|
236
|
+
say " Vertex Types:", :cyan
|
237
|
+
stats[:vertices][:by_type].each do |type, count|
|
238
|
+
say " #{type}: #{count}"
|
239
|
+
end
|
240
|
+
puts
|
241
|
+
|
242
|
+
say " Edge Types:", :cyan
|
243
|
+
stats[:edges][:by_type].each do |type, count|
|
244
|
+
say " #{type}: #{count}"
|
138
245
|
end
|
139
246
|
end
|
247
|
+
|
248
|
+
def handle_error(error, verbose)
|
249
|
+
message = "Error: #{error.message}"
|
250
|
+
message += "\n#{error.backtrace.join("\n")}" if verbose
|
251
|
+
abort message
|
252
|
+
end
|
253
|
+
|
254
|
+
def symbolize_keys(hash)
|
255
|
+
hash.transform_keys(&:to_sym)
|
256
|
+
end
|
140
257
|
end
|
141
258
|
end
|
data/lib/hind/lsif/edge.rb
CHANGED