hind 0.1.14 → 0.1.17
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/README.md +33 -0
- data/lib/hind/cli.rb +87 -67
- data/lib/hind/lsif/generator.rb +181 -84
- data/lib/hind/lsif/global_state.rb +184 -25
- data/lib/hind/lsif/visitors/declaration_visitor.rb +9 -8
- data/lib/hind/lsif/visitors/reference_visitor.rb +21 -16
- data/lib/hind/scip/generator.rb +322 -5
- data/lib/hind/scip/global_state.rb +61 -0
- data/lib/hind/scip/scip_pb.rb +134 -0
- data/lib/hind/version.rb +1 -1
- metadata +19 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba0a23c04400d06ebf294a87558c5893fb195b0e477acf49772598827f1afdba
|
|
4
|
+
data.tar.gz: a213c39aa228dd18612d45b85a1087aaec375ba3ba2477dc5dd7ff14dd0a094a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d806daccb7faa4378c8bd644ff094b6fb8dda612ecaae3536cdf5f2e6e6551ebbdd0d5de35612daa4a7e9727c2e51fee44c6a0fe029882397f19dbbda9a15cae
|
|
7
|
+
data.tar.gz: a0586d0ec3f980ae369138fd712fe670162be4ac6444f495fb8c414253fcfd251df4c4ccec80988305178ff20ddbb93034bbe1b812058b752b8a9fe14e31d66e
|
data/README.md
CHANGED
|
@@ -18,6 +18,10 @@ gem 'hind'
|
|
|
18
18
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
|
+
### GitLab CI Integration
|
|
22
|
+
|
|
23
|
+
See [GitLab CI Integration Guide](docs/gitlab_ci.md) for detailed instructions.
|
|
24
|
+
|
|
21
25
|
### Generating LSIF Data
|
|
22
26
|
|
|
23
27
|
To generate LSIF data for your Ruby project:
|
|
@@ -39,6 +43,16 @@ hind lsif -e "test/**/*" -e "spec/**/*"
|
|
|
39
43
|
hind lsif -v
|
|
40
44
|
```
|
|
41
45
|
|
|
46
|
+
### Generating SCIP Data
|
|
47
|
+
|
|
48
|
+
To generate SCIP data for your Ruby project:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
hind scip
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This will produce an `index.scip` file which is a JSON serialization of the SCIP index.
|
|
55
|
+
|
|
42
56
|
Options:
|
|
43
57
|
- `-d, --directory DIR` - Root directory to process (default: current directory)
|
|
44
58
|
- `-o, --output FILE` - Output file path (default: dump.lsif)
|
|
@@ -100,6 +114,25 @@ bundle exec rspec
|
|
|
100
114
|
bundle exec bin/hind
|
|
101
115
|
```
|
|
102
116
|
|
|
117
|
+
## Roadmap / TODO
|
|
118
|
+
|
|
119
|
+
The following features are planned for future releases to improve indexing fidelity and performance:
|
|
120
|
+
|
|
121
|
+
- **Ruby Semantic Fidelity**
|
|
122
|
+
- [ ] Ancestor Chain Resolution (`include`, `extend`, `prepend`)
|
|
123
|
+
- [ ] Local Variables & Method Parameters
|
|
124
|
+
- [ ] Dynamic Method Support (`attr_accessor`, `delegate`, etc.)
|
|
125
|
+
- [ ] Block & Lambda Scopes
|
|
126
|
+
- **External Dependencies**
|
|
127
|
+
- [ ] External Gem Resolution (linking to symbols in dependencies)
|
|
128
|
+
- [ ] Core & Standard Library Linking
|
|
129
|
+
- **Advanced SCIP/LSIF Features**
|
|
130
|
+
- [ ] SCIP Relationships (implementation/inheritance edges)
|
|
131
|
+
- [ ] LSIF Monikers for cross-repo resolution
|
|
132
|
+
- **Performance**
|
|
133
|
+
- [ ] Parallel Processing for large codebases
|
|
134
|
+
- [ ] Incremental Indexing
|
|
135
|
+
|
|
103
136
|
## Contributing
|
|
104
137
|
|
|
105
138
|
Bug reports and pull requests are welcome on GitHub at https://github.com/yourusername/hind.
|
data/lib/hind/cli.rb
CHANGED
|
@@ -5,33 +5,98 @@ require 'json'
|
|
|
5
5
|
require 'pathname'
|
|
6
6
|
require 'fileutils'
|
|
7
7
|
|
|
8
|
+
require_relative 'lsif/global_state'
|
|
9
|
+
require_relative 'lsif/generator'
|
|
10
|
+
require_relative 'scip/generator'
|
|
11
|
+
|
|
8
12
|
module Hind
|
|
9
13
|
class CLI < Thor
|
|
10
14
|
class_option :verbose, type: :boolean, aliases: '-v', desc: 'Enable verbose output'
|
|
11
15
|
|
|
12
|
-
desc 'lsif', 'Generate LSIF index for Ruby classes, modules, and constants'
|
|
13
|
-
method_option :directory, type: :string, aliases: '-d',
|
|
16
|
+
desc 'lsif [DIRECTORY]', 'Generate LSIF index for Ruby classes, modules, and constants'
|
|
17
|
+
method_option :directory, type: :string, aliases: '-d', desc: 'Root directory to process (deprecated, use positional argument)'
|
|
14
18
|
method_option :output, type: :string, aliases: '-o', default: 'dump.lsif', desc: 'Output file path'
|
|
15
19
|
method_option :glob, type: :string, aliases: '-g', default: '**/*.rb', desc: 'File pattern to match'
|
|
16
20
|
method_option :force, type: :boolean, aliases: '-f', desc: 'Overwrite output file if it exists'
|
|
17
21
|
method_option :exclude, type: :array, aliases: '-e', desc: 'Patterns to exclude'
|
|
18
|
-
def lsif
|
|
19
|
-
validate_directory(
|
|
22
|
+
def lsif(dir = options[:directory] || '.')
|
|
23
|
+
validate_directory(dir)
|
|
20
24
|
validate_output_file(options[:output], options[:force])
|
|
21
25
|
|
|
22
|
-
files = find_files(
|
|
23
|
-
abort "No files found matching pattern '#{options[:glob]}'" if files.empty?
|
|
26
|
+
files = find_files(dir, options[:glob], options[:exclude])
|
|
27
|
+
abort "No files found matching pattern '#{options[:glob]}' in #{dir}" if files.empty?
|
|
24
28
|
|
|
25
|
-
say "Found #{files.length} files to process", :green if options[:verbose]
|
|
29
|
+
say "Found #{files.length} files to process in #{dir}", :green if options[:verbose]
|
|
26
30
|
|
|
27
31
|
begin
|
|
28
|
-
generate_lsif(files, options)
|
|
32
|
+
generate_lsif(files, dir, options)
|
|
33
|
+
|
|
34
|
+
# Add debug info from the global state
|
|
35
|
+
if options[:verbose]
|
|
36
|
+
debug_info = LSIF::GlobalState.instance.debug_info
|
|
37
|
+
say "\nGlobal State Summary:", :cyan
|
|
38
|
+
say " Classes: #{debug_info[:classes_count]} (#{debug_info[:open_classes_count]} open classes)", :cyan
|
|
39
|
+
say " Modules: #{debug_info[:modules_count]} (#{debug_info[:open_modules_count]} open modules)", :cyan
|
|
40
|
+
say " Constants: #{debug_info[:constants_count]}", :cyan
|
|
41
|
+
say " References: #{debug_info[:references_count]}", :cyan
|
|
42
|
+
say " Result Sets: #{debug_info[:result_sets_count]}", :cyan
|
|
43
|
+
|
|
44
|
+
# Report on the most frequently reopened classes/modules
|
|
45
|
+
if debug_info[:open_classes_count] > 0
|
|
46
|
+
most_opened_classes = LSIF::GlobalState.instance.classes
|
|
47
|
+
.map { |name, data| [name, data[:definitions].size] }
|
|
48
|
+
.select { |_, count| count > 1 }
|
|
49
|
+
.sort_by { |_, count| -count }
|
|
50
|
+
.take(5)
|
|
51
|
+
|
|
52
|
+
say "\nMost frequently reopened classes:", :cyan
|
|
53
|
+
most_opened_classes.each do |name, count|
|
|
54
|
+
say " #{name}: #{count} definitions", :cyan
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if debug_info[:open_modules_count] > 0
|
|
59
|
+
most_opened_modules = LSIF::GlobalState.instance.modules
|
|
60
|
+
.map { |name, data| [name, data[:definitions].size] }
|
|
61
|
+
.select { |_, count| count > 1 }
|
|
62
|
+
.sort_by { |_, count| -count }
|
|
63
|
+
.take(5)
|
|
64
|
+
|
|
65
|
+
say "\nMost frequently reopened modules:", :cyan
|
|
66
|
+
most_opened_modules.each do |name, count|
|
|
67
|
+
say " #{name}: #{count} definitions", :cyan
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
29
72
|
say "\nLSIF data has been written to: #{options[:output]}", :green if options[:verbose]
|
|
30
73
|
rescue => e
|
|
31
74
|
handle_error(e, options[:verbose])
|
|
32
75
|
end
|
|
33
76
|
end
|
|
34
77
|
|
|
78
|
+
desc 'scip [DIRECTORY]', 'Generate SCIP index'
|
|
79
|
+
method_option :directory, type: :string, aliases: '-d', desc: 'Root directory to process (deprecated, use positional argument)'
|
|
80
|
+
method_option :output, type: :string, aliases: '-o', default: 'index.scip', desc: 'Output file path'
|
|
81
|
+
method_option :glob, type: :string, aliases: '-g', default: '**/*.rb', desc: 'File pattern to match'
|
|
82
|
+
method_option :force, type: :boolean, aliases: '-f', desc: 'Overwrite output file if it exists'
|
|
83
|
+
method_option :exclude, type: :array, aliases: '-e', desc: 'Patterns to exclude'
|
|
84
|
+
def scip(dir = options[:directory] || '.')
|
|
85
|
+
validate_directory(dir)
|
|
86
|
+
validate_output_file(options[:output], options[:force])
|
|
87
|
+
|
|
88
|
+
files = find_files(dir, options[:glob], options[:exclude])
|
|
89
|
+
abort "No files found matching pattern '#{options[:glob]}' in #{dir}" if files.empty?
|
|
90
|
+
|
|
91
|
+
say "Found #{files.length} files to process in #{dir}", :green if options[:verbose]
|
|
92
|
+
|
|
93
|
+
generator = Hind::SCIP::Generator.new(File.expand_path(dir))
|
|
94
|
+
index = generator.execute(files)
|
|
95
|
+
|
|
96
|
+
File.write(options[:output], index.to_proto, mode: 'wb')
|
|
97
|
+
say "SCIP index written to #{options[:output]}", :green
|
|
98
|
+
end
|
|
99
|
+
|
|
35
100
|
desc 'version', 'Show version'
|
|
36
101
|
def version
|
|
37
102
|
say "Hind version #{Hind::VERSION}"
|
|
@@ -39,76 +104,26 @@ module Hind
|
|
|
39
104
|
|
|
40
105
|
private
|
|
41
106
|
|
|
42
|
-
def generate_lsif(files, options)
|
|
107
|
+
def generate_lsif(files, directory, options)
|
|
43
108
|
# Initialize generator with absolute project root
|
|
44
109
|
generator = Hind::LSIF::Generator.new(
|
|
45
110
|
{
|
|
46
111
|
vertex_id: 1,
|
|
47
|
-
|
|
48
|
-
projectRoot: File.expand_path(options[:directory])
|
|
112
|
+
projectRoot: File.expand_path(directory)
|
|
49
113
|
}
|
|
50
114
|
)
|
|
51
115
|
|
|
52
116
|
File.open(options[:output], 'w') do |output_file|
|
|
53
|
-
say '
|
|
117
|
+
say 'Processing files...', :cyan if options[:verbose]
|
|
54
118
|
|
|
55
|
-
|
|
56
|
-
initial_data = generator.get_initial_data
|
|
57
|
-
if initial_data&.any?
|
|
58
|
-
say 'Writing initial LSIF data...', :cyan if options[:verbose]
|
|
59
|
-
output_file.puts(initial_data.map(&:to_json).join("\n"))
|
|
60
|
-
end
|
|
119
|
+
lsif_data = generator.execute(files, options)
|
|
61
120
|
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
absolute_path = File.expand_path(file)
|
|
66
|
-
relative_path = Pathname.new(absolute_path)
|
|
67
|
-
.relative_path_from(Pathname.new(generator.metadata[:projectRoot]))
|
|
68
|
-
.to_s
|
|
69
|
-
|
|
70
|
-
begin
|
|
71
|
-
content = File.read(absolute_path)
|
|
72
|
-
file_declaration_data = generator.collect_file_declarations(content, relative_path)
|
|
73
|
-
declaration_data.merge!(file_declaration_data)
|
|
74
|
-
rescue => e
|
|
75
|
-
warn "Warning: Failed to read file '#{file}': #{e.message}"
|
|
76
|
-
next
|
|
77
|
-
end
|
|
121
|
+
# Get counts from global state
|
|
122
|
+
if options[:verbose]
|
|
123
|
+
say "Found #{LSIF::GlobalState.instance.classes.size} classes, #{LSIF::GlobalState.instance.modules.size} modules, and #{LSIF::GlobalState.instance.constants.size} constants", :cyan
|
|
78
124
|
end
|
|
79
125
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# Write declaration LSIF data next
|
|
83
|
-
if declaration_data[:lsif_data].any?
|
|
84
|
-
output_file.puts(declaration_data[:lsif_data].map(&:to_json).join("\n"))
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
say 'Processing files for references...', :cyan if options[:verbose]
|
|
88
|
-
|
|
89
|
-
# Second pass: Process each file for references
|
|
90
|
-
files.each do |file|
|
|
91
|
-
absolute_path = File.expand_path(file)
|
|
92
|
-
relative_path = Pathname.new(absolute_path)
|
|
93
|
-
.relative_path_from(Pathname.new(generator.metadata[:projectRoot]))
|
|
94
|
-
.to_s
|
|
95
|
-
|
|
96
|
-
if options[:verbose]
|
|
97
|
-
say "Processing file: #{relative_path}", :cyan
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
begin
|
|
101
|
-
content = File.read(absolute_path)
|
|
102
|
-
reference_lsif_data = generator.process_file(
|
|
103
|
-
content: content,
|
|
104
|
-
uri: relative_path
|
|
105
|
-
)
|
|
106
|
-
output_file.puts(reference_lsif_data.map(&:to_json).join("\n"))
|
|
107
|
-
rescue => e
|
|
108
|
-
warn "Warning: Failed to read file '#{file}': #{e.message}"
|
|
109
|
-
next
|
|
110
|
-
end
|
|
111
|
-
end
|
|
126
|
+
output_file.puts(lsif_data.map(&:to_json).join("\n"))
|
|
112
127
|
end
|
|
113
128
|
end
|
|
114
129
|
|
|
@@ -127,7 +142,12 @@ module Hind
|
|
|
127
142
|
|
|
128
143
|
def find_files(directory, glob, exclude_patterns)
|
|
129
144
|
pattern = File.join(directory, glob)
|
|
130
|
-
|
|
145
|
+
absolute_directory = File.expand_path(directory)
|
|
146
|
+
|
|
147
|
+
files = Dir.glob(pattern).map do |file|
|
|
148
|
+
# Return relative path to the directory for indexing
|
|
149
|
+
Pathname.new(File.expand_path(file)).relative_path_from(Pathname.new(absolute_directory)).to_s
|
|
150
|
+
end
|
|
131
151
|
|
|
132
152
|
exclude_patterns&.each do |exclude|
|
|
133
153
|
files.reject! { |f| File.fnmatch?(exclude, f) }
|
data/lib/hind/lsif/generator.rb
CHANGED
|
@@ -7,13 +7,14 @@ require 'pathname'
|
|
|
7
7
|
|
|
8
8
|
require_relative 'visitors/declaration_visitor'
|
|
9
9
|
require_relative 'visitors/reference_visitor'
|
|
10
|
+
require_relative 'global_state'
|
|
10
11
|
|
|
11
12
|
module Hind
|
|
12
13
|
module LSIF
|
|
13
14
|
class Generator
|
|
14
15
|
LSIF_VERSION = '0.4.3'
|
|
15
16
|
|
|
16
|
-
attr_reader :metadata, :
|
|
17
|
+
attr_reader :metadata, :document_id, :current_uri
|
|
17
18
|
|
|
18
19
|
def initialize(metadata = {})
|
|
19
20
|
@vertex_id = metadata[:vertex_id] || 1
|
|
@@ -22,73 +23,91 @@ module Hind
|
|
|
22
23
|
projectRoot: File.expand_path(metadata[:projectRoot] || Dir.pwd)
|
|
23
24
|
}.merge(metadata)
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
# Reset the global state when initializing a new generator
|
|
27
|
+
GlobalState.instance.reset
|
|
26
28
|
@document_ids = {}
|
|
27
29
|
@current_document_id = nil
|
|
28
30
|
@lsif_data = []
|
|
29
31
|
@current_uri = nil
|
|
30
32
|
@last_vertex_id = @vertex_id
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
@last_reference_index = 0
|
|
34
|
+
@initial_data = []
|
|
35
|
+
@initial_data_emitted = false
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
def execute(files, options)
|
|
39
|
+
# We need to ensure metadata/project vertices are the very first ones.
|
|
40
|
+
# If we collect declarations first, we might emit vertices.
|
|
41
|
+
# So we should initialize project first.
|
|
42
|
+
|
|
43
|
+
unless @initial_data_emitted
|
|
44
|
+
initialize_project
|
|
45
|
+
@initial_data_emitted = true
|
|
46
|
+
# Ensure these are at the start of @lsif_data
|
|
47
|
+
# specific logic: if we just added them, they are at the end (idx 0 if empty)
|
|
48
|
+
# but if we called collect_declarations separately?
|
|
49
|
+
# Actually, execute is the main entry point. @lsif_data is empty approx.
|
|
50
|
+
end
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
# First pass: Declarations
|
|
53
|
+
files.each do |file|
|
|
54
|
+
@document_id = nil # Reset for each file
|
|
55
|
+
absolute_path = File.join(@metadata[:projectRoot], file)
|
|
56
|
+
next unless File.exist?(absolute_path)
|
|
57
|
+
|
|
58
|
+
source = File.read(absolute_path)
|
|
59
|
+
collect_file_declarations(source, file)
|
|
48
60
|
end
|
|
49
61
|
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
# Second pass: References
|
|
63
|
+
files.each do |file|
|
|
64
|
+
@document_id = nil # Reset for each file
|
|
65
|
+
absolute_path = File.join(@metadata[:projectRoot], file)
|
|
66
|
+
next unless File.exist?(absolute_path)
|
|
53
67
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
end
|
|
68
|
+
source = File.read(absolute_path)
|
|
69
|
+
process_file(content: source, uri: file)
|
|
70
|
+
end
|
|
59
71
|
|
|
60
|
-
|
|
61
|
-
@current_uri = params[:uri]
|
|
62
|
-
content = params[:content]
|
|
72
|
+
finalize_document_state
|
|
63
73
|
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
@lsif_data
|
|
75
|
+
end
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
@
|
|
77
|
+
def collect_file_declarations(content, uri)
|
|
78
|
+
@current_uri = uri
|
|
79
|
+
result = Prism.parse(content)
|
|
69
80
|
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
declaration_visitor = DeclarationVisitor.new(self, uri)
|
|
82
|
+
result.value.accept(declaration_visitor)
|
|
72
83
|
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
{lsif_data: @lsif_data - @initial_data}
|
|
85
|
+
ensure
|
|
86
|
+
@current_uri = nil
|
|
87
|
+
end
|
|
75
88
|
|
|
76
|
-
|
|
89
|
+
def process_file(content:, uri:)
|
|
90
|
+
@current_uri = uri
|
|
91
|
+
setup_document if @document_id.nil? || @document_ids[uri].nil?
|
|
92
|
+
@document_id = @document_ids[uri]
|
|
93
|
+
@current_document_id = @document_id
|
|
77
94
|
|
|
78
|
-
|
|
79
|
-
|
|
95
|
+
result = Prism.parse(content)
|
|
96
|
+
|
|
97
|
+
reference_visitor = ReferenceVisitor.new(self, uri)
|
|
98
|
+
result.value.accept(reference_visitor)
|
|
80
99
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
finalize_document_state
|
|
101
|
+
@lsif_data
|
|
102
|
+
ensure
|
|
103
|
+
@current_uri = nil
|
|
85
104
|
end
|
|
86
105
|
|
|
87
106
|
def get_initial_data
|
|
88
107
|
@initial_data
|
|
89
108
|
end
|
|
90
109
|
|
|
91
|
-
def
|
|
110
|
+
def register_class_declaration(declaration)
|
|
92
111
|
return unless @current_uri && declaration[:node]
|
|
93
112
|
|
|
94
113
|
qualified_name = declaration[:name]
|
|
@@ -96,15 +115,8 @@ module Hind
|
|
|
96
115
|
setup_document if @document_id.nil?
|
|
97
116
|
current_doc_id = @document_id
|
|
98
117
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
elsif declaration[:type] == :module
|
|
102
|
-
create_range(declaration[:node].constant_path.location)
|
|
103
|
-
elsif declaration[:type] == :class
|
|
104
|
-
create_range(declaration[:node].constant_path.location)
|
|
105
|
-
else
|
|
106
|
-
create_range(declaration[:node].location)
|
|
107
|
-
end
|
|
118
|
+
range_location = declaration[:range_location] || declaration[:node].constant_path.location
|
|
119
|
+
range_id = create_range(range_location)
|
|
108
120
|
return unless range_id
|
|
109
121
|
|
|
110
122
|
result_set_id = emit_vertex('resultSet')
|
|
@@ -115,7 +127,7 @@ module Hind
|
|
|
115
127
|
|
|
116
128
|
emit_edge('item', def_result_id, [range_id], 'definitions', current_doc_id)
|
|
117
129
|
|
|
118
|
-
hover_content =
|
|
130
|
+
hover_content = generate_class_hover_content(declaration)
|
|
119
131
|
hover_id = emit_vertex('hoverResult', {
|
|
120
132
|
contents: [{
|
|
121
133
|
language: 'ruby',
|
|
@@ -124,21 +136,96 @@ module Hind
|
|
|
124
136
|
})
|
|
125
137
|
emit_edge('textDocument/hover', result_set_id, hover_id)
|
|
126
138
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
declaration[:range_id] = range_id
|
|
140
|
+
declaration[:result_set_id] = result_set_id
|
|
141
|
+
declaration[:document_id] = current_doc_id
|
|
142
|
+
declaration[:file] = @current_uri
|
|
143
|
+
|
|
144
|
+
GlobalState.instance.add_class(qualified_name, declaration)
|
|
145
|
+
|
|
146
|
+
result_set_id
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def register_module_declaration(declaration)
|
|
150
|
+
return unless @current_uri && declaration[:node]
|
|
151
|
+
|
|
152
|
+
qualified_name = declaration[:name]
|
|
153
|
+
|
|
154
|
+
setup_document if @document_id.nil?
|
|
155
|
+
current_doc_id = @document_id
|
|
156
|
+
|
|
157
|
+
range_location = declaration[:range_location] || declaration[:node].constant_path.location
|
|
158
|
+
range_id = create_range(range_location)
|
|
159
|
+
return unless range_id
|
|
160
|
+
|
|
161
|
+
result_set_id = emit_vertex('resultSet')
|
|
162
|
+
emit_edge('next', range_id, result_set_id)
|
|
163
|
+
|
|
164
|
+
def_result_id = emit_vertex('definitionResult')
|
|
165
|
+
emit_edge('textDocument/definition', result_set_id, def_result_id)
|
|
166
|
+
|
|
167
|
+
emit_edge('item', def_result_id, [range_id], 'definitions', current_doc_id)
|
|
168
|
+
|
|
169
|
+
hover_content = generate_module_hover_content(declaration)
|
|
170
|
+
hover_id = emit_vertex('hoverResult', {
|
|
171
|
+
contents: [{
|
|
172
|
+
language: 'ruby',
|
|
173
|
+
value: hover_content
|
|
174
|
+
}]
|
|
175
|
+
})
|
|
176
|
+
emit_edge('textDocument/hover', result_set_id, hover_id)
|
|
177
|
+
|
|
178
|
+
declaration[:range_id] = range_id
|
|
179
|
+
declaration[:result_set_id] = result_set_id
|
|
180
|
+
declaration[:document_id] = current_doc_id
|
|
181
|
+
declaration[:file] = @current_uri
|
|
182
|
+
|
|
183
|
+
GlobalState.instance.add_module(qualified_name, declaration)
|
|
184
|
+
|
|
185
|
+
result_set_id
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def register_constant_declaration(declaration)
|
|
189
|
+
return unless @current_uri && declaration[:node]
|
|
190
|
+
|
|
191
|
+
qualified_name = declaration[:name]
|
|
192
|
+
|
|
193
|
+
setup_document if @document_id.nil?
|
|
194
|
+
current_doc_id = @document_id
|
|
195
|
+
|
|
196
|
+
range_id = create_range(declaration[:node].name_loc)
|
|
197
|
+
return unless range_id
|
|
198
|
+
|
|
199
|
+
result_set_id = emit_vertex('resultSet')
|
|
200
|
+
emit_edge('next', range_id, result_set_id)
|
|
201
|
+
|
|
202
|
+
def_result_id = emit_vertex('definitionResult')
|
|
203
|
+
emit_edge('textDocument/definition', result_set_id, def_result_id)
|
|
204
|
+
|
|
205
|
+
emit_edge('item', def_result_id, [range_id], 'definitions', current_doc_id)
|
|
206
|
+
|
|
207
|
+
hover_content = generate_constant_hover_content(declaration)
|
|
208
|
+
hover_id = emit_vertex('hoverResult', {
|
|
209
|
+
contents: [{
|
|
210
|
+
language: 'ruby',
|
|
211
|
+
value: hover_content
|
|
212
|
+
}]
|
|
213
|
+
})
|
|
214
|
+
emit_edge('textDocument/hover', result_set_id, hover_id)
|
|
215
|
+
|
|
216
|
+
declaration[:range_id] = range_id
|
|
217
|
+
declaration[:result_set_id] = result_set_id
|
|
218
|
+
declaration[:document_id] = current_doc_id
|
|
219
|
+
declaration[:file] = @current_uri
|
|
220
|
+
|
|
221
|
+
GlobalState.instance.add_constant(qualified_name, declaration)
|
|
135
222
|
|
|
136
223
|
result_set_id
|
|
137
224
|
end
|
|
138
225
|
|
|
139
226
|
def register_reference(reference)
|
|
140
227
|
return unless @current_uri && reference[:node]
|
|
141
|
-
return unless
|
|
228
|
+
return unless GlobalState.instance.has_declaration?(reference[:name])
|
|
142
229
|
|
|
143
230
|
setup_document if @document_id.nil?
|
|
144
231
|
current_doc_id = @document_id
|
|
@@ -146,10 +233,11 @@ module Hind
|
|
|
146
233
|
range_id = create_range(reference[:node].location)
|
|
147
234
|
return unless range_id
|
|
148
235
|
|
|
149
|
-
declaration
|
|
150
|
-
|
|
236
|
+
# Get the primary declaration for this reference
|
|
237
|
+
declaration = GlobalState.instance.get_declaration(reference[:name])
|
|
238
|
+
return unless declaration && declaration[:result_set_id]
|
|
151
239
|
|
|
152
|
-
|
|
240
|
+
GlobalState.instance.add_reference(reference[:name], @current_uri, range_id, current_doc_id)
|
|
153
241
|
emit_edge('next', range_id, declaration[:result_set_id])
|
|
154
242
|
|
|
155
243
|
reference_result = emit_vertex('referenceResult')
|
|
@@ -170,11 +258,18 @@ module Hind
|
|
|
170
258
|
}
|
|
171
259
|
})
|
|
172
260
|
|
|
173
|
-
|
|
261
|
+
project_id = emit_vertex('project', {kind: 'ruby'})
|
|
262
|
+
GlobalState.instance.project_id = project_id
|
|
263
|
+
|
|
264
|
+
# Store initial data separately
|
|
265
|
+
@initial_data = @lsif_data.dup
|
|
174
266
|
end
|
|
175
267
|
|
|
176
268
|
def setup_document
|
|
177
|
-
|
|
269
|
+
if @document_ids[@current_uri]
|
|
270
|
+
@document_id = @document_ids[@current_uri]
|
|
271
|
+
return
|
|
272
|
+
end
|
|
178
273
|
return unless @current_uri
|
|
179
274
|
|
|
180
275
|
file_path = File.join(@metadata[:projectRoot], @current_uri)
|
|
@@ -187,13 +282,13 @@ module Hind
|
|
|
187
282
|
@document_ids[@current_uri] = @document_id
|
|
188
283
|
@current_document_id = @document_id
|
|
189
284
|
|
|
190
|
-
emit_edge('contains',
|
|
285
|
+
emit_edge('contains', GlobalState.instance.project_id, [@document_id]) if GlobalState.instance.project_id
|
|
191
286
|
end
|
|
192
287
|
|
|
193
288
|
def finalize_document_state
|
|
194
289
|
return unless @current_uri && @document_id
|
|
195
290
|
|
|
196
|
-
ranges =
|
|
291
|
+
ranges = GlobalState.instance.get_ranges_for_file(@current_uri)
|
|
197
292
|
if ranges&.any?
|
|
198
293
|
emit_edge('contains', @document_id, ranges, nil, @document_id)
|
|
199
294
|
end
|
|
@@ -202,6 +297,9 @@ module Hind
|
|
|
202
297
|
def create_range(location)
|
|
203
298
|
return nil unless @current_uri && location
|
|
204
299
|
|
|
300
|
+
cached_id = GlobalState.instance.find_range_id(@current_uri, location)
|
|
301
|
+
return cached_id if cached_id
|
|
302
|
+
|
|
205
303
|
range_id = emit_vertex('range', {
|
|
206
304
|
start: {
|
|
207
305
|
line: location.start_line - 1, # Convert from 1-based to 0-based numbering
|
|
@@ -213,7 +311,7 @@ module Hind
|
|
|
213
311
|
}
|
|
214
312
|
})
|
|
215
313
|
|
|
216
|
-
|
|
314
|
+
GlobalState.instance.add_range(@current_uri, range_id, location)
|
|
217
315
|
range_id
|
|
218
316
|
end
|
|
219
317
|
|
|
@@ -263,20 +361,19 @@ module Hind
|
|
|
263
361
|
@vertex_id - 1
|
|
264
362
|
end
|
|
265
363
|
|
|
266
|
-
def
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
end
|
|
364
|
+
def generate_class_hover_content(declaration)
|
|
365
|
+
hover = ["class #{declaration[:name]}"]
|
|
366
|
+
hover << " < #{declaration[:superclass]}" if declaration[:superclass]
|
|
367
|
+
hover.join
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def generate_module_hover_content(declaration)
|
|
371
|
+
"module #{declaration[:name]}"
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def generate_constant_hover_content(declaration)
|
|
375
|
+
value_info = declaration[:node].value.respond_to?(:content) ? " = #{declaration[:node].value.content}" : ''
|
|
376
|
+
"#{declaration[:name]}#{value_info}"
|
|
280
377
|
end
|
|
281
378
|
|
|
282
379
|
def format_hover_data(data)
|