hind 0.1.6 → 0.1.7
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/cli.rb +24 -159
- data/lib/hind/lsif/generator.rb +52 -83
- data/lib/hind/lsif/global_state.rb +4 -81
- data/lib/hind/lsif/visitors/declaration_visitor.rb +0 -170
- data/lib/hind/lsif/visitors/reference_visitor.rb +0 -159
- data/lib/hind/lsif.rb +0 -1
- data/lib/hind/version.rb +1 -1
- metadata +1 -2
- data/lib/hind/lsif/visitor.rb +0 -255
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65e6158fb36117206f66c5989febbd5c336f7b8e88aa3b4a241a7d2cac091fa1
|
4
|
+
data.tar.gz: 753d17f81c708b80ebac94b1e17c1cfa2c34e4ed5bc767f9e2daed525b7485ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 649463b8b55e5016d822334667c1e61f6bc4fefa62230a5f7846f2b396f9ad467c8ff848d9637377c4851bda3d7bb068cc1a319a16d06b31a2269384bc6486c9
|
7
|
+
data.tar.gz: 0376aec80b6dbd5eba49244103d093a004e428e284e644b17109010a3175969f0df9eb261edc94fc60d071a8594a925aaa7ffa975af112c575b2781380e85beb
|
data/lib/hind/cli.rb
CHANGED
@@ -4,59 +4,30 @@ require 'thor'
|
|
4
4
|
require 'json'
|
5
5
|
require 'pathname'
|
6
6
|
require 'fileutils'
|
7
|
-
require 'yaml'
|
8
7
|
|
9
8
|
module Hind
|
10
9
|
class CLI < Thor
|
11
10
|
class_option :verbose, type: :boolean, aliases: '-v', desc: 'Enable verbose output'
|
12
|
-
class_option :config, type: :string, aliases: '-c', desc: 'Path to configuration file'
|
13
11
|
|
14
|
-
desc 'lsif', 'Generate LSIF index'
|
12
|
+
desc 'lsif', 'Generate LSIF index for Ruby classes, modules, and constants'
|
15
13
|
method_option :directory, type: :string, aliases: '-d', default: '.', desc: 'Root directory to process'
|
16
14
|
method_option :output, type: :string, aliases: '-o', default: 'dump.lsif', desc: 'Output file path'
|
17
15
|
method_option :glob, type: :string, aliases: '-g', default: '**/*.rb', desc: 'File pattern to match'
|
18
16
|
method_option :force, type: :boolean, aliases: '-f', desc: 'Overwrite output file if it exists'
|
19
17
|
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'
|
21
18
|
def lsif
|
22
|
-
|
23
|
-
|
19
|
+
validate_directory(options[:directory])
|
20
|
+
validate_output_file(options[:output], options[:force])
|
24
21
|
|
25
|
-
|
26
|
-
|
22
|
+
files = find_files(options[:directory], options[:glob], options[:exclude])
|
23
|
+
abort "No files found matching pattern '#{options[:glob]}'" if files.empty?
|
27
24
|
|
28
|
-
files
|
29
|
-
abort "No files found matching pattern '#{opts[:glob]}'" if files.empty?
|
30
|
-
|
31
|
-
say "Found #{files.length} files to process", :green if opts[:verbose]
|
32
|
-
|
33
|
-
begin
|
34
|
-
generate_lsif(files, opts)
|
35
|
-
say "\nLSIF data has been written to: #{opts[:output]}", :green if opts[:verbose]
|
36
|
-
rescue StandardError => e
|
37
|
-
handle_error(e, opts[:verbose])
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
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])
|
25
|
+
say "Found #{files.length} files to process", :green if options[:verbose]
|
47
26
|
|
48
27
|
begin
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
if options[:json]
|
53
|
-
puts JSON.pretty_generate(results)
|
54
|
-
else
|
55
|
-
print_check_results(results)
|
56
|
-
end
|
57
|
-
|
58
|
-
exit(1) if !results[:valid] || (options[:strict] && results[:warnings].any?)
|
59
|
-
rescue StandardError => e
|
28
|
+
generate_lsif(files, options)
|
29
|
+
say "\nLSIF data has been written to: #{options[:output]}", :green if options[:verbose]
|
30
|
+
rescue => e
|
60
31
|
handle_error(e, options[:verbose])
|
61
32
|
end
|
62
33
|
end
|
@@ -66,18 +37,6 @@ module Hind
|
|
66
37
|
say "Hind version #{Hind::VERSION}"
|
67
38
|
end
|
68
39
|
|
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
|
-
|
81
40
|
private
|
82
41
|
|
83
42
|
def generate_lsif(files, options)
|
@@ -95,24 +54,27 @@ module Hind
|
|
95
54
|
files.each do |file|
|
96
55
|
absolute_path = File.expand_path(file)
|
97
56
|
relative_path = Pathname.new(absolute_path)
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
57
|
+
.relative_path_from(Pathname.new(generator.metadata[:projectRoot]))
|
58
|
+
.to_s
|
59
|
+
|
60
|
+
begin
|
61
|
+
file_contents[relative_path] = File.read(absolute_path)
|
62
|
+
rescue => e
|
63
|
+
warn "Warning: Failed to read file '#{file}': #{e.message}"
|
64
|
+
next
|
65
|
+
end
|
104
66
|
end
|
105
67
|
|
106
68
|
File.open(options[:output], 'w') do |output_file|
|
107
|
-
say
|
69
|
+
say 'First pass: Collecting declarations...', :cyan if options[:verbose]
|
108
70
|
|
109
71
|
# First pass: Process all files to collect declarations
|
110
72
|
declaration_data = generator.collect_declarations(file_contents)
|
111
73
|
|
112
|
-
say "Found #{declaration_data[:declarations].size} declarations", :cyan if options[:verbose]
|
113
|
-
say
|
74
|
+
say "Found #{declaration_data[:declarations].size} declarations (classes, modules, constants)", :cyan if options[:verbose]
|
75
|
+
say 'Processing files for references...', :cyan if options[:verbose]
|
114
76
|
|
115
|
-
# Second pass: Process each file
|
77
|
+
# Second pass: Process each file for references
|
116
78
|
file_contents.each do |relative_path, content|
|
117
79
|
if options[:verbose]
|
118
80
|
say "Processing file: #{relative_path}", :cyan
|
@@ -125,11 +87,6 @@ module Hind
|
|
125
87
|
|
126
88
|
output_file.puts(lsif_data.map(&:to_json).join("\n"))
|
127
89
|
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
90
|
end
|
134
91
|
end
|
135
92
|
|
@@ -150,109 +107,17 @@ module Hind
|
|
150
107
|
pattern = File.join(directory, glob)
|
151
108
|
files = Dir.glob(pattern)
|
152
109
|
|
153
|
-
|
154
|
-
|
155
|
-
files.reject! { |f| File.fnmatch?(exclude, f) }
|
156
|
-
end
|
110
|
+
exclude_patterns&.each do |exclude|
|
111
|
+
files.reject! { |f| File.fnmatch?(exclude, f) }
|
157
112
|
end
|
158
113
|
|
159
114
|
files
|
160
115
|
end
|
161
116
|
|
162
|
-
def load_config(config_path)
|
163
|
-
return {} unless config_path && File.exist?(config_path)
|
164
|
-
|
165
|
-
begin
|
166
|
-
YAML.load_file(config_path) || {}
|
167
|
-
rescue StandardError => e
|
168
|
-
abort "Error loading config file: #{e.message}"
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
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
|
207
|
-
end
|
208
|
-
puts
|
209
|
-
end
|
210
|
-
|
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
|
219
|
-
end
|
220
|
-
|
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
|
228
|
-
|
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}"
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
117
|
def handle_error(error, verbose)
|
249
118
|
message = "Error: #{error.message}"
|
250
119
|
message += "\n#{error.backtrace.join("\n")}" if verbose
|
251
120
|
abort message
|
252
121
|
end
|
253
|
-
|
254
|
-
def symbolize_keys(hash)
|
255
|
-
hash.transform_keys(&:to_sym)
|
256
|
-
end
|
257
122
|
end
|
258
123
|
end
|
data/lib/hind/lsif/generator.rb
CHANGED
@@ -24,6 +24,7 @@ module Hind
|
|
24
24
|
|
25
25
|
@global_state = GlobalState.new
|
26
26
|
@document_ids = {}
|
27
|
+
@current_document_id = nil
|
27
28
|
@lsif_data = []
|
28
29
|
@current_uri = nil
|
29
30
|
|
@@ -33,37 +34,49 @@ module Hind
|
|
33
34
|
def collect_declarations(files)
|
34
35
|
files.each do |path, content|
|
35
36
|
@current_uri = path
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
@document_id = nil
|
38
|
+
@current_document_id = nil
|
39
|
+
|
40
|
+
begin
|
41
|
+
ast = Parser.new(content).parse
|
42
|
+
setup_document
|
43
|
+
visitor = DeclarationVisitor.new(self, path)
|
44
|
+
visitor.visit(ast)
|
45
|
+
finalize_document_state
|
46
|
+
rescue => e
|
47
|
+
warn "Warning: Failed to collect declarations from '#{path}': #{e.message}"
|
48
|
+
end
|
39
49
|
end
|
40
50
|
|
41
|
-
{
|
51
|
+
{declarations: @global_state.declarations}
|
42
52
|
end
|
43
53
|
|
44
54
|
def process_file(params)
|
45
|
-
content = params[:content]
|
46
55
|
@current_uri = params[:uri]
|
56
|
+
content = params[:content]
|
57
|
+
|
58
|
+
@document_id = nil
|
59
|
+
@current_document_id = nil
|
47
60
|
|
48
61
|
setup_document
|
49
62
|
ast = Parser.new(content).parse
|
50
63
|
|
51
|
-
# Process declarations first to update any missing ones
|
52
|
-
visitor = DeclarationVisitor.new(self, @current_uri)
|
53
|
-
visitor.visit(ast)
|
54
|
-
|
55
|
-
# Then process references
|
56
64
|
visitor = ReferenceVisitor.new(self, @current_uri)
|
57
65
|
visitor.visit(ast)
|
58
66
|
|
59
|
-
|
60
|
-
|
67
|
+
result = @lsif_data
|
68
|
+
finalize_document_state
|
69
|
+
result
|
61
70
|
end
|
62
71
|
|
63
72
|
def register_declaration(declaration)
|
64
73
|
return unless @current_uri && declaration[:node]
|
65
74
|
|
66
75
|
qualified_name = declaration[:name]
|
76
|
+
|
77
|
+
setup_document if @document_id.nil?
|
78
|
+
current_doc_id = @document_id
|
79
|
+
|
67
80
|
range_id = create_range(declaration[:node].location, declaration[:node].location)
|
68
81
|
return unless range_id
|
69
82
|
|
@@ -72,7 +85,8 @@ module Hind
|
|
72
85
|
|
73
86
|
def_result_id = emit_vertex('definitionResult')
|
74
87
|
emit_edge('textDocument/definition', result_set_id, def_result_id)
|
75
|
-
|
88
|
+
|
89
|
+
emit_edge('item', def_result_id, [range_id], 'definitions', current_doc_id)
|
76
90
|
|
77
91
|
hover_content = generate_hover_content(declaration)
|
78
92
|
hover_id = emit_vertex('hoverResult', {
|
@@ -88,74 +102,28 @@ module Hind
|
|
88
102
|
scope: declaration[:scope],
|
89
103
|
file: @current_uri,
|
90
104
|
range_id: range_id,
|
91
|
-
result_set_id: result_set_id
|
105
|
+
result_set_id: result_set_id,
|
106
|
+
document_id: current_doc_id
|
92
107
|
}.merge(declaration))
|
108
|
+
|
109
|
+
result_set_id
|
93
110
|
end
|
94
111
|
|
95
112
|
def register_reference(reference)
|
96
113
|
return unless @current_uri && reference[:node]
|
97
114
|
return unless @global_state.has_declaration?(reference[:name])
|
98
115
|
|
116
|
+
setup_document if @document_id.nil?
|
117
|
+
current_doc_id = @document_id
|
118
|
+
|
99
119
|
range_id = create_range(reference[:node].location, reference[:node].location)
|
100
120
|
return unless range_id
|
101
121
|
|
102
122
|
declaration = @global_state.declarations[reference[:name]]
|
103
|
-
|
104
|
-
emit_edge('next', range_id, declaration[:result_set_id])
|
105
|
-
end
|
106
|
-
|
107
|
-
def finalize_cross_references
|
108
|
-
cross_ref_data = []
|
109
|
-
|
110
|
-
@global_state.references.each do |qualified_name, references|
|
111
|
-
declaration = @global_state.declarations[qualified_name]
|
112
|
-
next unless declaration
|
113
|
-
|
114
|
-
result_set_id = declaration[:result_set_id]
|
115
|
-
next unless result_set_id
|
116
|
-
|
117
|
-
ref_result_id = emit_vertex('referenceResult')
|
118
|
-
emit_edge('textDocument/references', result_set_id, ref_result_id)
|
119
|
-
|
120
|
-
# Collect all reference range IDs
|
121
|
-
all_refs = references.map { |ref| ref[:range_id] }
|
122
|
-
all_refs << declaration[:range_id] if declaration[:range_id]
|
123
|
-
|
124
|
-
# Group references by document
|
125
|
-
references.group_by { |ref| ref[:file] }.each do |file_path, file_refs|
|
126
|
-
document_id = @document_ids[file_path]
|
127
|
-
next unless document_id
|
128
|
-
|
129
|
-
cross_ref_data << {
|
130
|
-
id: @vertex_id,
|
131
|
-
type: 'edge',
|
132
|
-
label: 'item',
|
133
|
-
outV: ref_result_id,
|
134
|
-
inVs: all_refs,
|
135
|
-
document: document_id,
|
136
|
-
property: 'references'
|
137
|
-
}
|
138
|
-
@vertex_id += 1
|
139
|
-
end
|
123
|
+
return unless declaration[:result_set_id]
|
140
124
|
|
141
|
-
|
142
|
-
|
143
|
-
def_document_id = @document_ids[def_file]
|
144
|
-
if def_document_id && references.none? { |ref| ref[:file] == def_file }
|
145
|
-
cross_ref_data << {
|
146
|
-
id: @vertex_id,
|
147
|
-
type: 'edge',
|
148
|
-
label: 'item',
|
149
|
-
outV: ref_result_id,
|
150
|
-
inVs: all_refs,
|
151
|
-
document: def_document_id,
|
152
|
-
property: 'references'
|
153
|
-
}
|
154
|
-
@vertex_id += 1
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
cross_ref_data
|
125
|
+
@global_state.add_reference(reference[:name], @current_uri, range_id, current_doc_id)
|
126
|
+
emit_edge('next', range_id, declaration[:result_set_id])
|
159
127
|
end
|
160
128
|
|
161
129
|
private
|
@@ -171,10 +139,11 @@ module Hind
|
|
171
139
|
}
|
172
140
|
})
|
173
141
|
|
174
|
-
@global_state.project_id = emit_vertex('project', {
|
142
|
+
@global_state.project_id = emit_vertex('project', {kind: 'ruby'})
|
175
143
|
end
|
176
144
|
|
177
145
|
def setup_document
|
146
|
+
return if @document_id
|
178
147
|
return unless @current_uri
|
179
148
|
|
180
149
|
file_path = File.join(@metadata[:projectRoot], @current_uri)
|
@@ -183,17 +152,19 @@ module Hind
|
|
183
152
|
uri: path_to_uri(file_path),
|
184
153
|
languageId: 'ruby'
|
185
154
|
})
|
155
|
+
|
186
156
|
@document_ids[@current_uri] = @document_id
|
157
|
+
@current_document_id = @document_id
|
187
158
|
|
188
159
|
emit_edge('contains', @global_state.project_id, [@document_id]) if @global_state.project_id
|
189
160
|
end
|
190
161
|
|
191
|
-
def
|
192
|
-
return unless @current_uri
|
162
|
+
def finalize_document_state
|
163
|
+
return unless @current_uri && @document_id
|
193
164
|
|
194
165
|
ranges = @global_state.get_ranges_for_file(@current_uri)
|
195
166
|
if ranges&.any?
|
196
|
-
emit_edge('contains', @document_id, ranges)
|
167
|
+
emit_edge('contains', @document_id, ranges, nil, @document_id)
|
197
168
|
end
|
198
169
|
end
|
199
170
|
|
@@ -235,7 +206,7 @@ module Hind
|
|
235
206
|
@vertex_id - 1
|
236
207
|
end
|
237
208
|
|
238
|
-
def emit_edge(label, out_v, in_v, property = nil)
|
209
|
+
def emit_edge(label, out_v, in_v, property = nil, doc_id = nil)
|
239
210
|
return unless out_v && valid_in_v?(in_v)
|
240
211
|
|
241
212
|
edge = {
|
@@ -251,8 +222,10 @@ module Hind
|
|
251
222
|
edge[:inV] = in_v
|
252
223
|
end
|
253
224
|
|
254
|
-
|
255
|
-
|
225
|
+
if label == 'item'
|
226
|
+
edge[:document] = doc_id || @current_document_id
|
227
|
+
edge[:property] = property if property
|
228
|
+
end
|
256
229
|
|
257
230
|
@lsif_data << edge
|
258
231
|
@vertex_id += 1
|
@@ -261,11 +234,6 @@ module Hind
|
|
261
234
|
|
262
235
|
def generate_hover_content(declaration)
|
263
236
|
case declaration[:type]
|
264
|
-
when :method
|
265
|
-
sig = []
|
266
|
-
sig << "def #{declaration[:name]}"
|
267
|
-
sig << "(#{declaration[:params]})" if declaration[:params]
|
268
|
-
sig.join
|
269
237
|
when :class
|
270
238
|
hover = ["class #{declaration[:name]}"]
|
271
239
|
hover << " < #{declaration[:superclass]}" if declaration[:superclass]
|
@@ -273,7 +241,8 @@ module Hind
|
|
273
241
|
when :module
|
274
242
|
"module #{declaration[:name]}"
|
275
243
|
when :constant
|
276
|
-
"#{declaration[:
|
244
|
+
value_info = declaration[:node].value ? " = #{declaration[:node].value.inspect}" : ''
|
245
|
+
"#{declaration[:name]}#{value_info}"
|
277
246
|
else
|
278
247
|
declaration[:name].to_s
|
279
248
|
end
|
@@ -301,7 +270,7 @@ module Hind
|
|
301
270
|
|
302
271
|
def path_to_uri(path)
|
303
272
|
return nil unless path
|
304
|
-
normalized_path = path.
|
273
|
+
normalized_path = path.tr('\\', '/')
|
305
274
|
normalized_path = normalized_path.sub(%r{^file://}, '')
|
306
275
|
absolute_path = File.expand_path(normalized_path)
|
307
276
|
"file://#{absolute_path}"
|
@@ -13,10 +13,6 @@ module Hind
|
|
13
13
|
@result_sets = {} # {qualified_name => result_set_id}
|
14
14
|
@ranges = {} # {file_path => [range_ids]}
|
15
15
|
@project_id = nil
|
16
|
-
|
17
|
-
# Method visibility tracking
|
18
|
-
@visibility_stack = [] # Stack of method visibility states per scope
|
19
|
-
@current_visibility = :public
|
20
16
|
end
|
21
17
|
|
22
18
|
def add_declaration(qualified_name, data)
|
@@ -24,12 +20,12 @@ module Hind
|
|
24
20
|
@result_sets[qualified_name] = data[:result_set_id] if data[:result_set_id]
|
25
21
|
end
|
26
22
|
|
27
|
-
def add_reference(qualified_name, file_path, range_id,
|
23
|
+
def add_reference(qualified_name, file_path, range_id, document_id)
|
28
24
|
@references[qualified_name] ||= []
|
29
25
|
@references[qualified_name] << {
|
30
26
|
file: file_path,
|
31
27
|
range_id: range_id,
|
32
|
-
|
28
|
+
document_id: document_id
|
33
29
|
}
|
34
30
|
end
|
35
31
|
|
@@ -58,98 +54,31 @@ module Hind
|
|
58
54
|
@ranges[file_path] || []
|
59
55
|
end
|
60
56
|
|
61
|
-
def push_visibility_scope(visibility = :public)
|
62
|
-
@visibility_stack.push(@current_visibility)
|
63
|
-
@current_visibility = visibility
|
64
|
-
end
|
65
|
-
|
66
|
-
def pop_visibility_scope
|
67
|
-
@current_visibility = @visibility_stack.pop || :public
|
68
|
-
end
|
69
|
-
|
70
|
-
def current_visibility
|
71
|
-
@current_visibility
|
72
|
-
end
|
73
|
-
|
74
|
-
def get_declaration_in_scope(name, scope)
|
75
|
-
# Try exact scope first
|
76
|
-
qualified_name = scope.empty? ? name : "#{scope}::#{name}"
|
77
|
-
return qualified_name if has_declaration?(qualified_name)
|
78
|
-
|
79
|
-
# Try parent scopes
|
80
|
-
scope_parts = scope.split('::')
|
81
|
-
while scope_parts.any?
|
82
|
-
scope_parts.pop
|
83
|
-
qualified_name = scope_parts.empty? ? name : "#{scope_parts.join('::')}::#{name}"
|
84
|
-
return qualified_name if has_declaration?(qualified_name)
|
85
|
-
end
|
86
|
-
|
87
|
-
# Try top level
|
88
|
-
has_declaration?(name) ? name : nil
|
89
|
-
end
|
90
|
-
|
91
|
-
def get_method_declaration(method_name, scope, instance_method = true)
|
92
|
-
separator = instance_method ? '#' : '.'
|
93
|
-
qualified_name = scope.empty? ? method_name : "#{scope}#{separator}#{method_name}"
|
94
|
-
|
95
|
-
return qualified_name if has_declaration?(qualified_name)
|
96
|
-
|
97
|
-
# For instance methods, try to find in superclass chain
|
98
|
-
if instance_method && !scope.empty?
|
99
|
-
current_scope = scope
|
100
|
-
while (class_data = @declarations[current_scope])
|
101
|
-
break unless class_data[:type] == :class && class_data[:superclass]
|
102
|
-
|
103
|
-
superclass = class_data[:superclass]
|
104
|
-
superclass_method = "#{superclass}#{separator}#{method_name}"
|
105
|
-
return superclass_method if has_declaration?(superclass_method)
|
106
|
-
|
107
|
-
current_scope = superclass
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
nil
|
112
|
-
end
|
113
|
-
|
114
57
|
def find_constant_declaration(name, current_scope)
|
115
58
|
return name if has_declaration?(name)
|
116
59
|
|
117
|
-
# Try with current scope
|
118
60
|
if current_scope && !current_scope.empty?
|
119
61
|
qualified_name = "#{current_scope}::#{name}"
|
120
62
|
return qualified_name if has_declaration?(qualified_name)
|
121
63
|
|
122
|
-
# Try parent scopes
|
123
64
|
scope_parts = current_scope.split('::')
|
124
65
|
while scope_parts.any?
|
125
66
|
scope_parts.pop
|
126
|
-
qualified_name = scope_parts.empty? ? name : "#{scope_parts.join(
|
67
|
+
qualified_name = scope_parts.empty? ? name : "#{scope_parts.join("::")}::#{name}"
|
127
68
|
return qualified_name if has_declaration?(qualified_name)
|
128
69
|
end
|
129
70
|
end
|
130
71
|
|
131
|
-
# Try top level
|
132
72
|
has_declaration?(name) ? name : nil
|
133
73
|
end
|
134
74
|
|
135
|
-
def get_instance_variable_scope(var_name, current_scope)
|
136
|
-
return nil unless current_scope
|
137
|
-
"#{current_scope}##{var_name}"
|
138
|
-
end
|
139
|
-
|
140
|
-
def get_class_variable_scope(var_name, current_scope)
|
141
|
-
return nil unless current_scope
|
142
|
-
"#{current_scope}::#{var_name}"
|
143
|
-
end
|
144
|
-
|
145
75
|
def debug_info
|
146
76
|
{
|
147
77
|
declarations_count: @declarations.size,
|
148
78
|
references_count: @references.values.sum(&:size),
|
149
79
|
result_sets_count: @result_sets.size,
|
150
80
|
ranges_count: @ranges.values.sum(&:size),
|
151
|
-
declaration_types: declaration_types_count
|
152
|
-
reference_types: reference_types_count
|
81
|
+
declaration_types: declaration_types_count
|
153
82
|
}
|
154
83
|
end
|
155
84
|
|
@@ -160,12 +89,6 @@ module Hind
|
|
160
89
|
counts[decl[:type]] += 1
|
161
90
|
end
|
162
91
|
end
|
163
|
-
|
164
|
-
def reference_types_count
|
165
|
-
@references.values.flatten.each_with_object(Hash.new(0)) do |ref, counts|
|
166
|
-
counts[ref[:type]] += 1
|
167
|
-
end
|
168
|
-
end
|
169
92
|
end
|
170
93
|
end
|
171
94
|
end
|
@@ -10,16 +10,12 @@ module Hind
|
|
10
10
|
@generator = generator
|
11
11
|
@file_path = file_path
|
12
12
|
@current_scope = []
|
13
|
-
@current_visibility = :public
|
14
|
-
@visibility_stack = []
|
15
|
-
@in_singleton_class = false
|
16
13
|
end
|
17
14
|
|
18
15
|
def visit_class_node(node)
|
19
16
|
@current_scope.push(node.constant_path.slice)
|
20
17
|
class_name = current_scope_name
|
21
18
|
|
22
|
-
# Register class declaration
|
23
19
|
@generator.register_declaration({
|
24
20
|
type: :class,
|
25
21
|
name: class_name,
|
@@ -28,10 +24,7 @@ module Hind
|
|
28
24
|
superclass: node.superclass&.slice
|
29
25
|
})
|
30
26
|
|
31
|
-
# Process the class body with proper scope and visibility
|
32
|
-
push_visibility(:public)
|
33
27
|
super
|
34
|
-
pop_visibility
|
35
28
|
@current_scope.pop
|
36
29
|
end
|
37
30
|
|
@@ -46,32 +39,10 @@ module Hind
|
|
46
39
|
scope: @current_scope[0..-2].join('::')
|
47
40
|
})
|
48
41
|
|
49
|
-
push_visibility(:public)
|
50
42
|
super
|
51
|
-
pop_visibility
|
52
43
|
@current_scope.pop
|
53
44
|
end
|
54
45
|
|
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
46
|
def visit_constant_write_node(node)
|
76
47
|
return unless node.name
|
77
48
|
|
@@ -88,152 +59,11 @@ module Hind
|
|
88
59
|
super
|
89
60
|
end
|
90
61
|
|
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
62
|
private
|
148
63
|
|
149
64
|
def current_scope_name
|
150
65
|
@current_scope.join('::')
|
151
66
|
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
67
|
end
|
238
68
|
end
|
239
69
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# lib/hind/lsif/visitors/reference_visitor.rb
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module Hind
|
@@ -12,27 +11,6 @@ module Hind
|
|
12
11
|
@current_scope = []
|
13
12
|
end
|
14
13
|
|
15
|
-
# Method calls
|
16
|
-
def visit_call_node(node)
|
17
|
-
return unless node.name && node.location
|
18
|
-
|
19
|
-
method_name = node.name.to_s
|
20
|
-
qualified_names = generate_qualified_names_for_call(node)
|
21
|
-
|
22
|
-
qualified_names.each do |qualified_name|
|
23
|
-
@generator.register_reference({
|
24
|
-
type: :method,
|
25
|
-
name: qualified_name,
|
26
|
-
node: node,
|
27
|
-
scope: current_scope_name,
|
28
|
-
call_type: :instance_method
|
29
|
-
})
|
30
|
-
end
|
31
|
-
|
32
|
-
super
|
33
|
-
end
|
34
|
-
|
35
|
-
# Class/module references
|
36
14
|
def visit_constant_read_node(node)
|
37
15
|
return unless node.name
|
38
16
|
|
@@ -49,7 +27,6 @@ module Hind
|
|
49
27
|
super
|
50
28
|
end
|
51
29
|
|
52
|
-
# Constant path references (e.g., A::B::C)
|
53
30
|
def visit_constant_path_node(node)
|
54
31
|
qualified_name = node.slice
|
55
32
|
|
@@ -63,87 +40,6 @@ module Hind
|
|
63
40
|
super
|
64
41
|
end
|
65
42
|
|
66
|
-
# Instance variable references
|
67
|
-
def visit_instance_variable_read_node(node)
|
68
|
-
return unless node.name && current_scope_name
|
69
|
-
|
70
|
-
var_name = node.name.to_s
|
71
|
-
qualified_name = "#{current_scope_name}##{var_name}"
|
72
|
-
|
73
|
-
@generator.register_reference({
|
74
|
-
type: :instance_variable,
|
75
|
-
name: qualified_name,
|
76
|
-
node: node,
|
77
|
-
scope: current_scope_name
|
78
|
-
})
|
79
|
-
|
80
|
-
super
|
81
|
-
end
|
82
|
-
|
83
|
-
# Class variable references
|
84
|
-
def visit_class_variable_read_node(node)
|
85
|
-
return unless node.name && current_scope_name
|
86
|
-
|
87
|
-
var_name = node.name.to_s
|
88
|
-
qualified_name = "#{current_scope_name}::#{var_name}"
|
89
|
-
|
90
|
-
@generator.register_reference({
|
91
|
-
type: :class_variable,
|
92
|
-
name: qualified_name,
|
93
|
-
node: node,
|
94
|
-
scope: current_scope_name
|
95
|
-
})
|
96
|
-
|
97
|
-
super
|
98
|
-
end
|
99
|
-
|
100
|
-
# Singleton method calls (class methods)
|
101
|
-
def visit_constant_path_call_node(node)
|
102
|
-
return unless node.name
|
103
|
-
|
104
|
-
method_name = node.name.to_s
|
105
|
-
receiver_name = node.receiver.slice
|
106
|
-
qualified_name = "#{receiver_name}.#{method_name}"
|
107
|
-
|
108
|
-
@generator.register_reference({
|
109
|
-
type: :method,
|
110
|
-
name: qualified_name,
|
111
|
-
node: node,
|
112
|
-
scope: current_scope_name,
|
113
|
-
call_type: :class_method
|
114
|
-
})
|
115
|
-
|
116
|
-
super
|
117
|
-
end
|
118
|
-
|
119
|
-
# Super method calls
|
120
|
-
def visit_super_node(node)
|
121
|
-
return unless current_scope_name
|
122
|
-
|
123
|
-
# Extract current method name from scope
|
124
|
-
current_method = current_method_name
|
125
|
-
return unless current_method
|
126
|
-
|
127
|
-
# Try to find the superclass method
|
128
|
-
if in_class_scope?
|
129
|
-
superclass = find_superclass
|
130
|
-
if superclass
|
131
|
-
qualified_name = "#{superclass}##{current_method}"
|
132
|
-
|
133
|
-
@generator.register_reference({
|
134
|
-
type: :method,
|
135
|
-
name: qualified_name,
|
136
|
-
node: node,
|
137
|
-
scope: current_scope_name,
|
138
|
-
call_type: :super
|
139
|
-
})
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
super
|
144
|
-
end
|
145
|
-
|
146
|
-
# Track class/module scope
|
147
43
|
def visit_class_node(node)
|
148
44
|
@current_scope.push(node.constant_path.slice)
|
149
45
|
super
|
@@ -161,61 +57,6 @@ module Hind
|
|
161
57
|
def current_scope_name
|
162
58
|
@current_scope.join('::')
|
163
59
|
end
|
164
|
-
|
165
|
-
def generate_qualified_names_for_call(node)
|
166
|
-
qualified_names = []
|
167
|
-
method_name = node.name.to_s
|
168
|
-
|
169
|
-
# Try with current scope first
|
170
|
-
qualified_names << "#{current_scope_name}##{method_name}" unless current_scope_name.empty?
|
171
|
-
|
172
|
-
# Try with receiver's type if available
|
173
|
-
if node.receiver
|
174
|
-
case node.receiver
|
175
|
-
when Prism::ConstantReadNode
|
176
|
-
qualified_names << "#{node.receiver.name}##{method_name}"
|
177
|
-
when Prism::ConstantPathNode
|
178
|
-
qualified_names << "#{node.receiver.slice}##{method_name}"
|
179
|
-
when Prism::CallNode
|
180
|
-
# Method chaining - try both instance and class methods
|
181
|
-
if node.receiver.name
|
182
|
-
qualified_names << "#{node.receiver.name}##{method_name}"
|
183
|
-
qualified_names << "#{node.receiver.name}.#{method_name}"
|
184
|
-
end
|
185
|
-
when Prism::InstanceVariableReadNode
|
186
|
-
# Instance variable calls - try current class context
|
187
|
-
qualified_names << "#{current_scope_name}##{method_name}" if current_scope_name
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
# Try as a standalone method
|
192
|
-
qualified_names << method_name
|
193
|
-
|
194
|
-
# Add potential class method variant
|
195
|
-
qualified_names << "#{current_scope_name}.#{method_name}" unless current_scope_name.empty?
|
196
|
-
|
197
|
-
qualified_names.uniq
|
198
|
-
end
|
199
|
-
|
200
|
-
def current_method_name
|
201
|
-
# Try to find the nearest method node in the AST
|
202
|
-
# This is a simplified version - you might need to enhance this
|
203
|
-
# based on your specific needs
|
204
|
-
"current_method"
|
205
|
-
end
|
206
|
-
|
207
|
-
def in_class_scope?
|
208
|
-
# Check if we're currently in a class definition
|
209
|
-
!@current_scope.empty? && @generator.global_state.declarations[@current_scope.last]&.[](:type) == :class
|
210
|
-
end
|
211
|
-
|
212
|
-
def find_superclass
|
213
|
-
return unless in_class_scope?
|
214
|
-
|
215
|
-
current_class = @current_scope.last
|
216
|
-
class_declaration = @generator.global_state.declarations[current_class]
|
217
|
-
class_declaration&.[](:superclass)
|
218
|
-
end
|
219
60
|
end
|
220
61
|
end
|
221
62
|
end
|
data/lib/hind/lsif.rb
CHANGED
data/lib/hind/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hind
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aboobacker MK
|
@@ -111,7 +111,6 @@ files:
|
|
111
111
|
- lib/hind/lsif/generator.rb
|
112
112
|
- lib/hind/lsif/global_state.rb
|
113
113
|
- lib/hind/lsif/vertex.rb
|
114
|
-
- lib/hind/lsif/visitor.rb
|
115
114
|
- lib/hind/lsif/visitors/declaration_visitor.rb
|
116
115
|
- lib/hind/lsif/visitors/reference_visitor.rb
|
117
116
|
- lib/hind/parser.rb
|
data/lib/hind/lsif/visitor.rb
DELETED
@@ -1,255 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Hind
|
4
|
-
module LSIF
|
5
|
-
class Visitor < Prism::Visitor
|
6
|
-
def initialize(generator)
|
7
|
-
@generator = generator
|
8
|
-
@current_scope = []
|
9
|
-
end
|
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
|
27
|
-
|
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}"
|
60
|
-
else
|
61
|
-
class_def
|
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
|
79
|
-
end
|
80
|
-
|
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
|
105
|
-
end
|
106
|
-
|
107
|
-
def visit_call_node(node)
|
108
|
-
return unless node.name && node.location
|
109
|
-
|
110
|
-
method_name = node.name.to_s
|
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}"
|
166
|
-
|
167
|
-
range_id = @generator.create_range(node.location, node.location)
|
168
|
-
result_set_id = @generator.emit_vertex('resultSet')
|
169
|
-
@generator.emit_edge('next', range_id, result_set_id)
|
170
|
-
|
171
|
-
def_result_id = @generator.emit_vertex('definitionResult')
|
172
|
-
@generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
|
173
|
-
@generator.emit_edge('item', def_result_id, [range_id], 'definitions')
|
174
|
-
|
175
|
-
hover_id = @generator.emit_vertex('hoverResult', {
|
176
|
-
contents: [{
|
177
|
-
language: 'ruby',
|
178
|
-
value: "#{qualified_name} = ..."
|
179
|
-
}]
|
180
|
-
})
|
181
|
-
@generator.emit_edge('textDocument/hover', result_set_id, hover_id)
|
182
|
-
|
183
|
-
@generator.add_to_global_state(qualified_name, result_set_id, range_id)
|
184
|
-
|
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
|
201
|
-
end
|
202
|
-
|
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
|
229
|
-
|
230
|
-
private
|
231
|
-
|
232
|
-
def current_scope_name
|
233
|
-
@current_scope.join('::')
|
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
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|