hind 0.1.14 → 0.1.15
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 +177 -86
- data/lib/hind/lsif/global_state.rb +169 -24
- data/lib/hind/lsif/visitors/declaration_visitor.rb +3 -11
- data/lib/hind/lsif/visitors/reference_visitor.rb +21 -17
- data/lib/hind/scip/generator.rb +264 -5
- data/lib/hind/scip/global_state.rb +50 -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: 67b64fd17d7735528e37b9098db56f71a5332e01e58e9dfd958052da1b68e220
|
|
4
|
+
data.tar.gz: 7d5ab4b8b54fd439be53eba3f40df928ea35852d321945f972b09bfad5a4de24
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 462b167b1073fcdbe74dc9d1731e07b068a5b49c9f13e1204f7a8c58925a972dfef541f28388ed163efa59b8057df2f535f46ced8fdeb0e76308a505e58231fb
|
|
7
|
+
data.tar.gz: '035070842f07d55edc194ec60b8210858e01a68b8261f444c4e1c6af5d8418e5436ebc8a8aca071dbf608ba0baabdce3ae6b7d08adbf12e05317eeb0bb4387d7'
|
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,166 @@ 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)
|
|
67
|
+
|
|
68
|
+
source = File.read(absolute_path)
|
|
69
|
+
process_file(content: source, uri: file)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
finalize_document_state
|
|
73
|
+
|
|
74
|
+
@lsif_data
|
|
75
|
+
end
|
|
53
76
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
77
|
+
def collect_file_declarations(content, uri)
|
|
78
|
+
@current_uri = uri
|
|
79
|
+
result = Prism.parse(content)
|
|
80
|
+
|
|
81
|
+
declaration_visitor = DeclarationVisitor.new(self, uri)
|
|
82
|
+
result.value.accept(declaration_visitor)
|
|
83
|
+
|
|
84
|
+
{ lsif_data: @lsif_data - @initial_data }
|
|
85
|
+
ensure
|
|
86
|
+
@current_uri = nil
|
|
58
87
|
end
|
|
59
88
|
|
|
60
|
-
def process_file(
|
|
61
|
-
@current_uri =
|
|
62
|
-
|
|
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
|
|
94
|
+
|
|
95
|
+
result = Prism.parse(content)
|
|
96
|
+
|
|
97
|
+
reference_visitor = ReferenceVisitor.new(self, uri)
|
|
98
|
+
result.value.accept(reference_visitor)
|
|
99
|
+
|
|
100
|
+
finalize_document_state
|
|
101
|
+
@lsif_data
|
|
102
|
+
ensure
|
|
103
|
+
@current_uri = nil
|
|
104
|
+
end
|
|
105
|
+
def get_initial_data
|
|
106
|
+
@initial_data
|
|
107
|
+
end
|
|
63
108
|
|
|
64
|
-
|
|
65
|
-
@
|
|
109
|
+
def register_class_declaration(declaration)
|
|
110
|
+
return unless @current_uri && declaration[:node]
|
|
66
111
|
|
|
67
|
-
|
|
68
|
-
@current_document_id = nil
|
|
112
|
+
qualified_name = declaration[:name]
|
|
69
113
|
|
|
70
|
-
setup_document
|
|
71
|
-
|
|
114
|
+
setup_document if @document_id.nil?
|
|
115
|
+
current_doc_id = @document_id
|
|
72
116
|
|
|
73
|
-
|
|
74
|
-
|
|
117
|
+
range_id = create_range(declaration[:node].constant_path.location)
|
|
118
|
+
return unless range_id
|
|
75
119
|
|
|
76
|
-
|
|
120
|
+
result_set_id = emit_vertex('resultSet')
|
|
121
|
+
emit_edge('next', range_id, result_set_id)
|
|
77
122
|
|
|
78
|
-
|
|
79
|
-
|
|
123
|
+
def_result_id = emit_vertex('definitionResult')
|
|
124
|
+
emit_edge('textDocument/definition', result_set_id, def_result_id)
|
|
125
|
+
|
|
126
|
+
emit_edge('item', def_result_id, [range_id], 'definitions', current_doc_id)
|
|
127
|
+
|
|
128
|
+
hover_content = generate_class_hover_content(declaration)
|
|
129
|
+
hover_id = emit_vertex('hoverResult', {
|
|
130
|
+
contents: [{
|
|
131
|
+
language: 'ruby',
|
|
132
|
+
value: hover_content
|
|
133
|
+
}]
|
|
134
|
+
})
|
|
135
|
+
emit_edge('textDocument/hover', result_set_id, hover_id)
|
|
136
|
+
|
|
137
|
+
declaration[:range_id] = range_id
|
|
138
|
+
declaration[:result_set_id] = result_set_id
|
|
139
|
+
declaration[:document_id] = current_doc_id
|
|
140
|
+
declaration[:file] = @current_uri
|
|
80
141
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
result
|
|
142
|
+
GlobalState.instance.add_class(qualified_name, declaration)
|
|
143
|
+
|
|
144
|
+
result_set_id
|
|
85
145
|
end
|
|
86
146
|
|
|
87
|
-
def
|
|
88
|
-
@
|
|
147
|
+
def register_module_declaration(declaration)
|
|
148
|
+
return unless @current_uri && declaration[:node]
|
|
149
|
+
|
|
150
|
+
qualified_name = declaration[:name]
|
|
151
|
+
|
|
152
|
+
setup_document if @document_id.nil?
|
|
153
|
+
current_doc_id = @document_id
|
|
154
|
+
|
|
155
|
+
range_id = create_range(declaration[:node].constant_path.location)
|
|
156
|
+
return unless range_id
|
|
157
|
+
|
|
158
|
+
result_set_id = emit_vertex('resultSet')
|
|
159
|
+
emit_edge('next', range_id, result_set_id)
|
|
160
|
+
|
|
161
|
+
def_result_id = emit_vertex('definitionResult')
|
|
162
|
+
emit_edge('textDocument/definition', result_set_id, def_result_id)
|
|
163
|
+
|
|
164
|
+
emit_edge('item', def_result_id, [range_id], 'definitions', current_doc_id)
|
|
165
|
+
|
|
166
|
+
hover_content = generate_module_hover_content(declaration)
|
|
167
|
+
hover_id = emit_vertex('hoverResult', {
|
|
168
|
+
contents: [{
|
|
169
|
+
language: 'ruby',
|
|
170
|
+
value: hover_content
|
|
171
|
+
}]
|
|
172
|
+
})
|
|
173
|
+
emit_edge('textDocument/hover', result_set_id, hover_id)
|
|
174
|
+
|
|
175
|
+
declaration[:range_id] = range_id
|
|
176
|
+
declaration[:result_set_id] = result_set_id
|
|
177
|
+
declaration[:document_id] = current_doc_id
|
|
178
|
+
declaration[:file] = @current_uri
|
|
179
|
+
|
|
180
|
+
GlobalState.instance.add_module(qualified_name, declaration)
|
|
181
|
+
|
|
182
|
+
result_set_id
|
|
89
183
|
end
|
|
90
184
|
|
|
91
|
-
def
|
|
185
|
+
def register_constant_declaration(declaration)
|
|
92
186
|
return unless @current_uri && declaration[:node]
|
|
93
187
|
|
|
94
188
|
qualified_name = declaration[:name]
|
|
@@ -96,15 +190,7 @@ module Hind
|
|
|
96
190
|
setup_document if @document_id.nil?
|
|
97
191
|
current_doc_id = @document_id
|
|
98
192
|
|
|
99
|
-
range_id =
|
|
100
|
-
create_range(declaration[:node].name_loc)
|
|
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
|
|
193
|
+
range_id = create_range(declaration[:node].name_loc)
|
|
108
194
|
return unless range_id
|
|
109
195
|
|
|
110
196
|
result_set_id = emit_vertex('resultSet')
|
|
@@ -115,7 +201,7 @@ module Hind
|
|
|
115
201
|
|
|
116
202
|
emit_edge('item', def_result_id, [range_id], 'definitions', current_doc_id)
|
|
117
203
|
|
|
118
|
-
hover_content =
|
|
204
|
+
hover_content = generate_constant_hover_content(declaration)
|
|
119
205
|
hover_id = emit_vertex('hoverResult', {
|
|
120
206
|
contents: [{
|
|
121
207
|
language: 'ruby',
|
|
@@ -124,21 +210,19 @@ module Hind
|
|
|
124
210
|
})
|
|
125
211
|
emit_edge('textDocument/hover', result_set_id, hover_id)
|
|
126
212
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
document_id: current_doc_id
|
|
134
|
-
}.merge(declaration))
|
|
213
|
+
declaration[:range_id] = range_id
|
|
214
|
+
declaration[:result_set_id] = result_set_id
|
|
215
|
+
declaration[:document_id] = current_doc_id
|
|
216
|
+
declaration[:file] = @current_uri
|
|
217
|
+
|
|
218
|
+
GlobalState.instance.add_constant(qualified_name, declaration)
|
|
135
219
|
|
|
136
220
|
result_set_id
|
|
137
221
|
end
|
|
138
222
|
|
|
139
223
|
def register_reference(reference)
|
|
140
224
|
return unless @current_uri && reference[:node]
|
|
141
|
-
return unless
|
|
225
|
+
return unless GlobalState.instance.has_declaration?(reference[:name])
|
|
142
226
|
|
|
143
227
|
setup_document if @document_id.nil?
|
|
144
228
|
current_doc_id = @document_id
|
|
@@ -146,10 +230,11 @@ module Hind
|
|
|
146
230
|
range_id = create_range(reference[:node].location)
|
|
147
231
|
return unless range_id
|
|
148
232
|
|
|
149
|
-
declaration
|
|
150
|
-
|
|
233
|
+
# Get the primary declaration for this reference
|
|
234
|
+
declaration = GlobalState.instance.get_declaration(reference[:name])
|
|
235
|
+
return unless declaration && declaration[:result_set_id]
|
|
151
236
|
|
|
152
|
-
|
|
237
|
+
GlobalState.instance.add_reference(reference[:name], @current_uri, range_id, current_doc_id)
|
|
153
238
|
emit_edge('next', range_id, declaration[:result_set_id])
|
|
154
239
|
|
|
155
240
|
reference_result = emit_vertex('referenceResult')
|
|
@@ -160,7 +245,7 @@ module Hind
|
|
|
160
245
|
private
|
|
161
246
|
|
|
162
247
|
def initialize_project
|
|
163
|
-
emit_vertex('metaData', {
|
|
248
|
+
metadata_vertex = emit_vertex('metaData', {
|
|
164
249
|
version: LSIF_VERSION,
|
|
165
250
|
projectRoot: path_to_uri(@metadata[:projectRoot]),
|
|
166
251
|
positionEncoding: 'utf-16',
|
|
@@ -170,11 +255,18 @@ module Hind
|
|
|
170
255
|
}
|
|
171
256
|
})
|
|
172
257
|
|
|
173
|
-
|
|
258
|
+
project_id = emit_vertex('project', {kind: 'ruby'})
|
|
259
|
+
GlobalState.instance.project_id = project_id
|
|
260
|
+
|
|
261
|
+
# Store initial data separately
|
|
262
|
+
@initial_data = @lsif_data.dup
|
|
174
263
|
end
|
|
175
264
|
|
|
176
265
|
def setup_document
|
|
177
|
-
|
|
266
|
+
if @document_ids[@current_uri]
|
|
267
|
+
@document_id = @document_ids[@current_uri]
|
|
268
|
+
return
|
|
269
|
+
end
|
|
178
270
|
return unless @current_uri
|
|
179
271
|
|
|
180
272
|
file_path = File.join(@metadata[:projectRoot], @current_uri)
|
|
@@ -187,13 +279,13 @@ module Hind
|
|
|
187
279
|
@document_ids[@current_uri] = @document_id
|
|
188
280
|
@current_document_id = @document_id
|
|
189
281
|
|
|
190
|
-
emit_edge('contains',
|
|
282
|
+
emit_edge('contains', GlobalState.instance.project_id, [@document_id]) if GlobalState.instance.project_id
|
|
191
283
|
end
|
|
192
284
|
|
|
193
285
|
def finalize_document_state
|
|
194
286
|
return unless @current_uri && @document_id
|
|
195
287
|
|
|
196
|
-
ranges =
|
|
288
|
+
ranges = GlobalState.instance.get_ranges_for_file(@current_uri)
|
|
197
289
|
if ranges&.any?
|
|
198
290
|
emit_edge('contains', @document_id, ranges, nil, @document_id)
|
|
199
291
|
end
|
|
@@ -213,7 +305,7 @@ module Hind
|
|
|
213
305
|
}
|
|
214
306
|
})
|
|
215
307
|
|
|
216
|
-
|
|
308
|
+
GlobalState.instance.add_range(@current_uri, range_id)
|
|
217
309
|
range_id
|
|
218
310
|
end
|
|
219
311
|
|
|
@@ -263,20 +355,19 @@ module Hind
|
|
|
263
355
|
@vertex_id - 1
|
|
264
356
|
end
|
|
265
357
|
|
|
266
|
-
def
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
end
|
|
358
|
+
def generate_class_hover_content(declaration)
|
|
359
|
+
hover = ["class #{declaration[:name]}"]
|
|
360
|
+
hover << " < #{declaration[:superclass]}" if declaration[:superclass]
|
|
361
|
+
hover.join
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def generate_module_hover_content(declaration)
|
|
365
|
+
"module #{declaration[:name]}"
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def generate_constant_hover_content(declaration)
|
|
369
|
+
value_info = declaration[:node].value.respond_to?(:content) ? " = #{declaration[:node].value.content}" : ''
|
|
370
|
+
"#{declaration[:name]}#{value_info}"
|
|
280
371
|
end
|
|
281
372
|
|
|
282
373
|
def format_hover_data(data)
|