hind 0.1.15 → 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/lib/hind/cli.rb +3 -3
- data/lib/hind/lsif/generator.rb +27 -21
- data/lib/hind/lsif/global_state.rb +21 -7
- data/lib/hind/lsif/visitors/declaration_visitor.rb +9 -0
- data/lib/hind/lsif/visitors/reference_visitor.rb +1 -0
- data/lib/hind/scip/generator.rb +122 -64
- data/lib/hind/scip/global_state.rb +16 -5
- data/lib/hind/scip/scip_pb.rb +45 -45
- data/lib/hind/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 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/lib/hind/cli.rb
CHANGED
|
@@ -84,7 +84,7 @@ module Hind
|
|
|
84
84
|
def scip(dir = options[:directory] || '.')
|
|
85
85
|
validate_directory(dir)
|
|
86
86
|
validate_output_file(options[:output], options[:force])
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
files = find_files(dir, options[:glob], options[:exclude])
|
|
89
89
|
abort "No files found matching pattern '#{options[:glob]}' in #{dir}" if files.empty?
|
|
90
90
|
|
|
@@ -92,7 +92,7 @@ module Hind
|
|
|
92
92
|
|
|
93
93
|
generator = Hind::SCIP::Generator.new(File.expand_path(dir))
|
|
94
94
|
index = generator.execute(files)
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
File.write(options[:output], index.to_proto, mode: 'wb')
|
|
97
97
|
say "SCIP index written to #{options[:output]}", :green
|
|
98
98
|
end
|
|
@@ -143,7 +143,7 @@ module Hind
|
|
|
143
143
|
def find_files(directory, glob, exclude_patterns)
|
|
144
144
|
pattern = File.join(directory, glob)
|
|
145
145
|
absolute_directory = File.expand_path(directory)
|
|
146
|
-
|
|
146
|
+
|
|
147
147
|
files = Dir.glob(pattern).map do |file|
|
|
148
148
|
# Return relative path to the directory for indexing
|
|
149
149
|
Pathname.new(File.expand_path(file)).relative_path_from(Pathname.new(absolute_directory)).to_s
|
data/lib/hind/lsif/generator.rb
CHANGED
|
@@ -39,14 +39,14 @@ module Hind
|
|
|
39
39
|
# We need to ensure metadata/project vertices are the very first ones.
|
|
40
40
|
# If we collect declarations first, we might emit vertices.
|
|
41
41
|
# So we should initialize project first.
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
unless @initial_data_emitted
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
50
|
end
|
|
51
51
|
|
|
52
52
|
# First pass: Declarations
|
|
@@ -54,7 +54,7 @@ module Hind
|
|
|
54
54
|
@document_id = nil # Reset for each file
|
|
55
55
|
absolute_path = File.join(@metadata[:projectRoot], file)
|
|
56
56
|
next unless File.exist?(absolute_path)
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
source = File.read(absolute_path)
|
|
59
59
|
collect_file_declarations(source, file)
|
|
60
60
|
end
|
|
@@ -64,24 +64,24 @@ module Hind
|
|
|
64
64
|
@document_id = nil # Reset for each file
|
|
65
65
|
absolute_path = File.join(@metadata[:projectRoot], file)
|
|
66
66
|
next unless File.exist?(absolute_path)
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
source = File.read(absolute_path)
|
|
69
69
|
process_file(content: source, uri: file)
|
|
70
70
|
end
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
finalize_document_state
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
@lsif_data
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def collect_file_declarations(content, uri)
|
|
78
78
|
@current_uri = uri
|
|
79
79
|
result = Prism.parse(content)
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
declaration_visitor = DeclarationVisitor.new(self, uri)
|
|
82
82
|
result.value.accept(declaration_visitor)
|
|
83
|
-
|
|
84
|
-
{
|
|
83
|
+
|
|
84
|
+
{lsif_data: @lsif_data - @initial_data}
|
|
85
85
|
ensure
|
|
86
86
|
@current_uri = nil
|
|
87
87
|
end
|
|
@@ -91,17 +91,18 @@ module Hind
|
|
|
91
91
|
setup_document if @document_id.nil? || @document_ids[uri].nil?
|
|
92
92
|
@document_id = @document_ids[uri]
|
|
93
93
|
@current_document_id = @document_id
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
result = Prism.parse(content)
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
reference_visitor = ReferenceVisitor.new(self, uri)
|
|
98
98
|
result.value.accept(reference_visitor)
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
finalize_document_state
|
|
101
101
|
@lsif_data
|
|
102
102
|
ensure
|
|
103
103
|
@current_uri = nil
|
|
104
104
|
end
|
|
105
|
+
|
|
105
106
|
def get_initial_data
|
|
106
107
|
@initial_data
|
|
107
108
|
end
|
|
@@ -114,7 +115,8 @@ module Hind
|
|
|
114
115
|
setup_document if @document_id.nil?
|
|
115
116
|
current_doc_id = @document_id
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
range_location = declaration[:range_location] || declaration[:node].constant_path.location
|
|
119
|
+
range_id = create_range(range_location)
|
|
118
120
|
return unless range_id
|
|
119
121
|
|
|
120
122
|
result_set_id = emit_vertex('resultSet')
|
|
@@ -152,7 +154,8 @@ module Hind
|
|
|
152
154
|
setup_document if @document_id.nil?
|
|
153
155
|
current_doc_id = @document_id
|
|
154
156
|
|
|
155
|
-
|
|
157
|
+
range_location = declaration[:range_location] || declaration[:node].constant_path.location
|
|
158
|
+
range_id = create_range(range_location)
|
|
156
159
|
return unless range_id
|
|
157
160
|
|
|
158
161
|
result_set_id = emit_vertex('resultSet')
|
|
@@ -245,7 +248,7 @@ module Hind
|
|
|
245
248
|
private
|
|
246
249
|
|
|
247
250
|
def initialize_project
|
|
248
|
-
|
|
251
|
+
emit_vertex('metaData', {
|
|
249
252
|
version: LSIF_VERSION,
|
|
250
253
|
projectRoot: path_to_uri(@metadata[:projectRoot]),
|
|
251
254
|
positionEncoding: 'utf-16',
|
|
@@ -294,6 +297,9 @@ module Hind
|
|
|
294
297
|
def create_range(location)
|
|
295
298
|
return nil unless @current_uri && location
|
|
296
299
|
|
|
300
|
+
cached_id = GlobalState.instance.find_range_id(@current_uri, location)
|
|
301
|
+
return cached_id if cached_id
|
|
302
|
+
|
|
297
303
|
range_id = emit_vertex('range', {
|
|
298
304
|
start: {
|
|
299
305
|
line: location.start_line - 1, # Convert from 1-based to 0-based numbering
|
|
@@ -305,7 +311,7 @@ module Hind
|
|
|
305
311
|
}
|
|
306
312
|
})
|
|
307
313
|
|
|
308
|
-
GlobalState.instance.add_range(@current_uri, range_id)
|
|
314
|
+
GlobalState.instance.add_range(@current_uri, range_id, location)
|
|
309
315
|
range_id
|
|
310
316
|
end
|
|
311
317
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'singleton'
|
|
3
4
|
|
|
4
5
|
module Hind
|
|
@@ -19,6 +20,7 @@ module Hind
|
|
|
19
20
|
@constants = {} # {qualified_name => {node:, scope:, file:, range_id:, result_set_id:, value:}}
|
|
20
21
|
@references = {} # {qualified_name => [{file:, range_id:, document_id:}, ...]}
|
|
21
22
|
@ranges = {} # {file_path => [range_ids]}
|
|
23
|
+
@range_cache = {} # {file_path => {[start_line, start_col, end_line, end_col] => range_id}}
|
|
22
24
|
@result_sets = {} # {qualified_name => result_set_id}
|
|
23
25
|
@project_id = nil
|
|
24
26
|
end
|
|
@@ -26,7 +28,7 @@ module Hind
|
|
|
26
28
|
def add_class(qualified_name, data)
|
|
27
29
|
# Initialize if this is the first time we're seeing this class
|
|
28
30
|
if !@classes.key?(qualified_name)
|
|
29
|
-
@classes[qualified_name] = {
|
|
31
|
+
@classes[qualified_name] = {definitions: []}
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
# Add this definition to the list
|
|
@@ -40,7 +42,7 @@ module Hind
|
|
|
40
42
|
def add_module(qualified_name, data)
|
|
41
43
|
# Initialize if this is the first time we're seeing this module
|
|
42
44
|
if !@modules.key?(qualified_name)
|
|
43
|
-
@modules[qualified_name] = {
|
|
45
|
+
@modules[qualified_name] = {definitions: []}
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
# Add this definition to the list
|
|
@@ -65,15 +67,27 @@ module Hind
|
|
|
65
67
|
}
|
|
66
68
|
end
|
|
67
69
|
|
|
68
|
-
def add_range(file_path, range_id)
|
|
70
|
+
def add_range(file_path, range_id, location = nil)
|
|
69
71
|
@ranges[file_path] ||= []
|
|
70
72
|
@ranges[file_path] << range_id
|
|
73
|
+
|
|
74
|
+
if location
|
|
75
|
+
@range_cache[file_path] ||= {}
|
|
76
|
+
coords = [location.start_line, location.start_column, location.end_line, location.end_column]
|
|
77
|
+
@range_cache[file_path][coords] = range_id
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def find_range_id(file_path, location)
|
|
82
|
+
return nil unless @range_cache[file_path] && location
|
|
83
|
+
coords = [location.start_line, location.start_column, location.end_line, location.end_column]
|
|
84
|
+
@range_cache[file_path][coords]
|
|
71
85
|
end
|
|
72
86
|
|
|
73
87
|
def has_declaration?(qualified_name)
|
|
74
88
|
@classes.key?(qualified_name) ||
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
@modules.key?(qualified_name) ||
|
|
90
|
+
@constants.key?(qualified_name)
|
|
77
91
|
end
|
|
78
92
|
|
|
79
93
|
def get_declaration(qualified_name)
|
|
@@ -205,7 +219,7 @@ module Hind
|
|
|
205
219
|
# 3. Fall back to the first definition
|
|
206
220
|
|
|
207
221
|
module_name = qualified_name.split('::').last
|
|
208
|
-
|
|
222
|
+
qualified_name.split('::')[0..-2].join('/')
|
|
209
223
|
|
|
210
224
|
# Rule 1: Check for a file named directly after the module
|
|
211
225
|
direct_match = definitions.find do |d|
|
|
@@ -225,7 +239,7 @@ module Hind
|
|
|
225
239
|
|
|
226
240
|
# Check if the file is at the module's path root
|
|
227
241
|
parts.size == qualified_name.split('::').size &&
|
|
228
|
-
|
|
242
|
+
parts.map(&:downcase).join('/') == qualified_name.gsub('::', '/').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
|
229
243
|
end
|
|
230
244
|
|
|
231
245
|
return root_match if root_match
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Hind
|
|
3
4
|
module LSIF
|
|
4
5
|
class DeclarationVisitor < Prism::Visitor
|
|
@@ -13,10 +14,14 @@ module Hind
|
|
|
13
14
|
def visit_class_node(node)
|
|
14
15
|
@current_scope.push(node.constant_path.slice)
|
|
15
16
|
class_name = current_scope_name
|
|
17
|
+
target_node = node.constant_path
|
|
18
|
+
target_node = target_node.child while target_node.is_a?(Prism::ConstantPathNode)
|
|
19
|
+
|
|
16
20
|
@generator.register_class_declaration({
|
|
17
21
|
type: :class,
|
|
18
22
|
name: class_name,
|
|
19
23
|
node: node,
|
|
24
|
+
range_location: target_node.location,
|
|
20
25
|
scope: @current_scope[0..-2].join('::'),
|
|
21
26
|
superclass: node.superclass&.slice
|
|
22
27
|
})
|
|
@@ -27,10 +32,14 @@ module Hind
|
|
|
27
32
|
def visit_module_node(node)
|
|
28
33
|
@current_scope.push(node.constant_path.slice)
|
|
29
34
|
module_name = current_scope_name
|
|
35
|
+
target_node = node.constant_path
|
|
36
|
+
target_node = target_node.child while target_node.is_a?(Prism::ConstantPathNode)
|
|
37
|
+
|
|
30
38
|
@generator.register_module_declaration({
|
|
31
39
|
type: :module,
|
|
32
40
|
name: module_name,
|
|
33
41
|
node: node,
|
|
42
|
+
range_location: target_node.location,
|
|
34
43
|
scope: @current_scope[0..-2].join('::')
|
|
35
44
|
})
|
|
36
45
|
super
|
data/lib/hind/scip/generator.rb
CHANGED
|
@@ -9,11 +9,12 @@ module Hind
|
|
|
9
9
|
def initialize(project_root)
|
|
10
10
|
@project_root = project_root
|
|
11
11
|
@documents = []
|
|
12
|
+
@package_info = detect_package_info
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def execute(files)
|
|
15
16
|
GlobalState.instance.reset
|
|
16
|
-
|
|
17
|
+
|
|
17
18
|
# Pass 1: Collect all definitions
|
|
18
19
|
files.each do |file|
|
|
19
20
|
collect_definitions(file)
|
|
@@ -41,6 +42,31 @@ module Hind
|
|
|
41
42
|
|
|
42
43
|
private
|
|
43
44
|
|
|
45
|
+
def detect_package_info
|
|
46
|
+
gemspec = Dir.glob(File.join(@project_root, '*.gemspec')).first
|
|
47
|
+
if gemspec
|
|
48
|
+
begin
|
|
49
|
+
content = File.read(gemspec)
|
|
50
|
+
name = content[/spec\.name\s*=\s*['"]([^'"]+)['"]/, 1]
|
|
51
|
+
version = content[/spec\.version\s*=\s*['"]([^'"]+)['"]/, 1]
|
|
52
|
+
|
|
53
|
+
# If version is a constant like Hind::VERSION, we might not find it easily via regex
|
|
54
|
+
# Let's try to find it via VERSION = '...'
|
|
55
|
+
if version.nil?
|
|
56
|
+
version_file = Dir.glob(File.join(@project_root, 'lib/**/version.rb')).first
|
|
57
|
+
if version_file
|
|
58
|
+
version = File.read(version_file)[/VERSION\s*=\s*['"]([^'"]+)['"]/, 1]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
return {name: name, version: version || '0.1.0'} if name
|
|
63
|
+
rescue
|
|
64
|
+
# Fallback
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
{name: 'hind', version: Hind::VERSION}
|
|
68
|
+
end
|
|
69
|
+
|
|
44
70
|
def collect_definitions(relative_path)
|
|
45
71
|
absolute_path = File.join(@project_root, relative_path)
|
|
46
72
|
return unless File.exist?(absolute_path)
|
|
@@ -48,7 +74,7 @@ module Hind
|
|
|
48
74
|
source = File.read(absolute_path)
|
|
49
75
|
result = Prism.parse(source)
|
|
50
76
|
|
|
51
|
-
visitor = ScipVisitor.new(relative_path, {
|
|
77
|
+
visitor = ScipVisitor.new(relative_path, {mode: :index, package_info: @package_info})
|
|
52
78
|
result.value.accept(visitor)
|
|
53
79
|
end
|
|
54
80
|
|
|
@@ -61,8 +87,8 @@ module Hind
|
|
|
61
87
|
|
|
62
88
|
occurrences = []
|
|
63
89
|
symbols = []
|
|
64
|
-
|
|
65
|
-
visitor = ScipVisitor.new(relative_path, {
|
|
90
|
+
|
|
91
|
+
visitor = ScipVisitor.new(relative_path, {mode: :emit, package_info: @package_info})
|
|
66
92
|
result.value.accept(visitor)
|
|
67
93
|
occurrences.concat(visitor.occurrences)
|
|
68
94
|
symbols.concat(visitor.symbols)
|
|
@@ -84,13 +110,14 @@ module Hind
|
|
|
84
110
|
@occurrences = []
|
|
85
111
|
@symbols = []
|
|
86
112
|
@current_scope = []
|
|
87
|
-
@package_prefix = "scip-ruby rubygems #{package_info[:name] ||
|
|
113
|
+
@package_prefix = "scip-ruby rubygems #{package_info[:name] || "hind"} #{package_info[:version] || "0.1.0"} "
|
|
88
114
|
end
|
|
89
115
|
|
|
90
116
|
def visit_class_node(node)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
117
|
+
name = scip_name(node.constant_path.slice)
|
|
118
|
+
@current_scope.push(name)
|
|
119
|
+
symbol = "#{@package_prefix}#{@current_scope.join("#")}#"
|
|
120
|
+
|
|
94
121
|
if @mode == :index
|
|
95
122
|
GlobalState.instance.add_symbol(current_scope_name, symbol)
|
|
96
123
|
else
|
|
@@ -108,21 +135,25 @@ module Hind
|
|
|
108
135
|
syntax_kind: SyntaxKind::Identifier
|
|
109
136
|
)
|
|
110
137
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
138
|
+
unless GlobalState.instance.emitted?(symbol)
|
|
139
|
+
@symbols << SymbolInformation.new(
|
|
140
|
+
symbol: symbol,
|
|
141
|
+
documentation: ["class #{node.constant_path.slice}"],
|
|
142
|
+
kind: SymbolInformation::Kind::Class
|
|
143
|
+
)
|
|
144
|
+
GlobalState.instance.mark_emitted(symbol)
|
|
145
|
+
end
|
|
116
146
|
end
|
|
117
147
|
|
|
118
148
|
super
|
|
119
149
|
@current_scope.pop
|
|
120
150
|
end
|
|
121
|
-
|
|
151
|
+
|
|
122
152
|
def visit_module_node(node)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
153
|
+
name = scip_name(node.constant_path.slice)
|
|
154
|
+
@current_scope.push(name)
|
|
155
|
+
symbol = "#{@package_prefix}#{@current_scope.join("#")}#"
|
|
156
|
+
|
|
126
157
|
if @mode == :index
|
|
127
158
|
GlobalState.instance.add_symbol(current_scope_name, symbol)
|
|
128
159
|
else
|
|
@@ -139,20 +170,25 @@ module Hind
|
|
|
139
170
|
syntax_kind: SyntaxKind::Identifier
|
|
140
171
|
)
|
|
141
172
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
173
|
+
unless GlobalState.instance.emitted?(symbol)
|
|
174
|
+
@symbols << SymbolInformation.new(
|
|
175
|
+
symbol: symbol,
|
|
176
|
+
documentation: ["module #{node.constant_path.slice}"],
|
|
177
|
+
kind: SymbolInformation::Kind::Module
|
|
178
|
+
)
|
|
179
|
+
GlobalState.instance.mark_emitted(symbol)
|
|
180
|
+
end
|
|
147
181
|
end
|
|
148
|
-
|
|
182
|
+
|
|
149
183
|
super
|
|
150
184
|
@current_scope.pop
|
|
151
185
|
end
|
|
152
|
-
|
|
186
|
+
|
|
153
187
|
def visit_constant_write_node(node)
|
|
154
|
-
|
|
155
|
-
|
|
188
|
+
name = scip_name(node.name.to_s)
|
|
189
|
+
suffix = @current_scope.empty? ? '' : '#'
|
|
190
|
+
symbol = "#{@package_prefix}#{@current_scope.join("#")}#{suffix}#{name}."
|
|
191
|
+
|
|
156
192
|
if @mode == :index
|
|
157
193
|
GlobalState.instance.add_symbol("#{current_scope_name}::#{node.name}", symbol)
|
|
158
194
|
else
|
|
@@ -161,56 +197,64 @@ module Hind
|
|
|
161
197
|
node.name_loc.start_column,
|
|
162
198
|
node.name_loc.end_column
|
|
163
199
|
]
|
|
164
|
-
|
|
200
|
+
|
|
165
201
|
@occurrences << Occurrence.new(
|
|
166
202
|
range: range,
|
|
167
203
|
symbol: symbol,
|
|
168
204
|
symbol_roles: 1,
|
|
169
205
|
syntax_kind: SyntaxKind::Identifier
|
|
170
206
|
)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
207
|
+
|
|
208
|
+
unless GlobalState.instance.emitted?(symbol)
|
|
209
|
+
@symbols << SymbolInformation.new(
|
|
210
|
+
symbol: symbol,
|
|
211
|
+
documentation: ["constant #{node.name}"],
|
|
212
|
+
kind: SymbolInformation::Kind::Package # fallback
|
|
213
|
+
)
|
|
214
|
+
GlobalState.instance.mark_emitted(symbol)
|
|
215
|
+
end
|
|
177
216
|
end
|
|
178
217
|
super
|
|
179
218
|
end
|
|
180
219
|
|
|
181
220
|
def visit_def_node(node)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
221
|
+
name = scip_name(node.name.to_s)
|
|
222
|
+
suffix = @current_scope.empty? ? '' : '#'
|
|
223
|
+
symbol = "#{@package_prefix}#{@current_scope.join("#")}#{suffix}#{name}."
|
|
224
|
+
|
|
225
|
+
if @mode == :index
|
|
226
|
+
# Register both qualified and simple name for speculative resolution
|
|
227
|
+
GlobalState.instance.add_symbol("#{current_scope_name}##{node.name}", symbol)
|
|
228
|
+
GlobalState.instance.add_symbol("##{node.name}", symbol)
|
|
229
|
+
else
|
|
230
|
+
range = [
|
|
190
231
|
node.name_loc.start_line - 1,
|
|
191
232
|
node.name_loc.start_column,
|
|
192
233
|
node.name_loc.end_column
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
@occurrences << Occurrence.new(
|
|
237
|
+
range: range,
|
|
238
|
+
symbol: symbol,
|
|
239
|
+
symbol_roles: 1,
|
|
240
|
+
syntax_kind: SyntaxKind::Identifier
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
unless GlobalState.instance.emitted?(symbol)
|
|
244
|
+
@symbols << SymbolInformation.new(
|
|
245
|
+
symbol: symbol,
|
|
246
|
+
documentation: ["def #{node.name}"],
|
|
247
|
+
kind: SymbolInformation::Kind::Method
|
|
248
|
+
)
|
|
249
|
+
GlobalState.instance.mark_emitted(symbol)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
super
|
|
209
253
|
end
|
|
210
254
|
|
|
211
255
|
def visit_constant_read_node(node)
|
|
212
256
|
return if @mode == :index
|
|
213
|
-
|
|
257
|
+
|
|
214
258
|
# Try to resolve constant
|
|
215
259
|
symbol = GlobalState.instance.find_symbol(node.name.to_s, current_scope_name)
|
|
216
260
|
if symbol
|
|
@@ -219,7 +263,7 @@ module Hind
|
|
|
219
263
|
node.location.start_column,
|
|
220
264
|
node.location.end_column
|
|
221
265
|
]
|
|
222
|
-
|
|
266
|
+
|
|
223
267
|
@occurrences << Occurrence.new(
|
|
224
268
|
range: range,
|
|
225
269
|
symbol: symbol,
|
|
@@ -237,13 +281,13 @@ module Hind
|
|
|
237
281
|
|
|
238
282
|
def visit_call_node(node)
|
|
239
283
|
return if @mode == :index
|
|
240
|
-
|
|
284
|
+
|
|
241
285
|
# Skip common methods
|
|
242
286
|
return super if %w[new puts p print].include?(node.name.to_s)
|
|
243
287
|
|
|
244
288
|
# Speculative resolution
|
|
245
|
-
symbol = GlobalState.instance.find_symbol("#{current_scope_name}##{node.name}",
|
|
246
|
-
|
|
289
|
+
symbol = GlobalState.instance.find_symbol("#{current_scope_name}##{node.name}", '') ||
|
|
290
|
+
GlobalState.instance.find_symbol("##{node.name}", '')
|
|
247
291
|
|
|
248
292
|
if symbol
|
|
249
293
|
loc = node.message_loc || node.location
|
|
@@ -252,7 +296,7 @@ module Hind
|
|
|
252
296
|
loc.start_column,
|
|
253
297
|
loc.end_column
|
|
254
298
|
]
|
|
255
|
-
|
|
299
|
+
|
|
256
300
|
@occurrences << Occurrence.new(
|
|
257
301
|
range: range,
|
|
258
302
|
symbol: symbol,
|
|
@@ -265,8 +309,22 @@ module Hind
|
|
|
265
309
|
|
|
266
310
|
private
|
|
267
311
|
|
|
312
|
+
def scip_name(name)
|
|
313
|
+
# Escape names with special characters using backticks
|
|
314
|
+
# Allowed: alphanumeric, -, +, $, _
|
|
315
|
+
if /^[a-zA-Z0-9\-\+\$_]+$/.match?(name)
|
|
316
|
+
name
|
|
317
|
+
else
|
|
318
|
+
"`#{name}`"
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
268
322
|
def current_scope_name
|
|
269
|
-
|
|
323
|
+
# This returns Ruby-style name for GlobalState mapping
|
|
324
|
+
# We don't want escaped names here to keep mapping simple
|
|
325
|
+
# Wait, I should probably store unescaped names in scope
|
|
326
|
+
# Let's adjust visit_* to store unescaped and scip_name for symbol construction
|
|
327
|
+
@current_scope.join('::').delete('`')
|
|
270
328
|
end
|
|
271
329
|
end
|
|
272
330
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'singleton'
|
|
4
|
+
require 'set'
|
|
3
5
|
|
|
4
6
|
module Hind
|
|
5
7
|
module SCIP
|
|
@@ -12,6 +14,15 @@ module Hind
|
|
|
12
14
|
|
|
13
15
|
def reset
|
|
14
16
|
@symbols = {} # {qualified_name => symbol_string}
|
|
17
|
+
@emitted_symbols = Set.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def mark_emitted(symbol)
|
|
21
|
+
@emitted_symbols.add(symbol)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def emitted?(symbol)
|
|
25
|
+
@emitted_symbols.include?(symbol)
|
|
15
26
|
end
|
|
16
27
|
|
|
17
28
|
def add_symbol(name, symbol)
|
|
@@ -25,16 +36,16 @@ module Hind
|
|
|
25
36
|
def has_symbol?(name)
|
|
26
37
|
@symbols.key?(name)
|
|
27
38
|
end
|
|
28
|
-
|
|
39
|
+
|
|
29
40
|
def find_symbol(name, current_scope)
|
|
30
41
|
# Reuse LSIF-like scope resolution logic
|
|
31
42
|
return @symbols[name] if @symbols.key?(name)
|
|
32
|
-
|
|
43
|
+
|
|
33
44
|
return nil unless current_scope && !current_scope.empty?
|
|
34
|
-
|
|
45
|
+
|
|
35
46
|
qualified_name = "#{current_scope}::#{name}"
|
|
36
47
|
return @symbols[qualified_name] if @symbols.key?(qualified_name)
|
|
37
|
-
|
|
48
|
+
|
|
38
49
|
scope_parts = current_scope.split('::')
|
|
39
50
|
while scope_parts.size > 0
|
|
40
51
|
scope_parts.pop
|
|
@@ -42,7 +53,7 @@ module Hind
|
|
|
42
53
|
qualified_name = prefix.empty? ? name : "#{prefix}::#{name}"
|
|
43
54
|
return @symbols[qualified_name] if @symbols.key?(qualified_name)
|
|
44
55
|
end
|
|
45
|
-
|
|
56
|
+
|
|
46
57
|
nil
|
|
47
58
|
end
|
|
48
59
|
end
|
data/lib/hind/scip/scip_pb.rb
CHANGED
|
@@ -3,37 +3,37 @@
|
|
|
3
3
|
require 'google/protobuf'
|
|
4
4
|
|
|
5
5
|
Google::Protobuf::DescriptorPool.generated_pool.build do
|
|
6
|
-
add_enum
|
|
6
|
+
add_enum 'scip.ProtocolVersion' do
|
|
7
7
|
value :UnspecifiedProtocolVersion, 0
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
add_enum
|
|
10
|
+
add_enum 'scip.TextEncoding' do
|
|
11
11
|
value :UnspecifiedTextEncoding, 0
|
|
12
12
|
value :UTF8, 1
|
|
13
13
|
value :UTF16, 2
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
add_message
|
|
16
|
+
add_message 'scip.ToolInfo' do
|
|
17
17
|
optional :name, :string, 1
|
|
18
18
|
optional :version, :string, 2
|
|
19
19
|
repeated :arguments, :string, 3
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
add_message
|
|
23
|
-
optional :version, :enum, 1,
|
|
24
|
-
optional :tool_info, :message, 2,
|
|
22
|
+
add_message 'scip.Metadata' do
|
|
23
|
+
optional :version, :enum, 1, 'scip.ProtocolVersion'
|
|
24
|
+
optional :tool_info, :message, 2, 'scip.ToolInfo'
|
|
25
25
|
optional :project_root, :string, 3
|
|
26
|
-
optional :text_document_encoding, :enum, 4,
|
|
26
|
+
optional :text_document_encoding, :enum, 4, 'scip.TextEncoding'
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
add_enum
|
|
29
|
+
add_enum 'scip.PositionEncoding' do
|
|
30
30
|
value :UnspecifiedPositionEncoding, 0
|
|
31
31
|
value :UTF8CodeUnitOffsetFromLineStart, 1
|
|
32
32
|
value :UTF16CodeUnitOffsetFromLineStart, 2
|
|
33
33
|
value :UTF32CodeUnitOffsetFromLineStart, 3
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
add_enum
|
|
36
|
+
add_enum 'scip.SyntaxKind' do
|
|
37
37
|
value :UnspecifiedSyntaxKind, 0
|
|
38
38
|
value :Comment, 1
|
|
39
39
|
value :Identifier, 41
|
|
@@ -41,7 +41,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
|
|
41
41
|
value :StringLiteral, 49
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
add_enum
|
|
44
|
+
add_enum 'scip.Severity' do
|
|
45
45
|
value :UnspecifiedSeverity, 0
|
|
46
46
|
value :Error, 1
|
|
47
47
|
value :Warning, 2
|
|
@@ -49,21 +49,21 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
|
|
49
49
|
value :Hint, 4
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
add_enum
|
|
52
|
+
add_enum 'scip.DiagnosticTag' do
|
|
53
53
|
value :UnspecifiedDiagnosticTag, 0
|
|
54
54
|
value :Unnecessary, 1
|
|
55
55
|
value :Deprecated, 2
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
add_message
|
|
59
|
-
optional :severity, :enum, 1,
|
|
58
|
+
add_message 'scip.Diagnostic' do
|
|
59
|
+
optional :severity, :enum, 1, 'scip.Severity'
|
|
60
60
|
optional :code, :string, 2
|
|
61
61
|
optional :message, :string, 3
|
|
62
62
|
optional :source, :string, 4
|
|
63
|
-
repeated :tags, :enum, 5,
|
|
63
|
+
repeated :tags, :enum, 5, 'scip.DiagnosticTag'
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
add_message
|
|
66
|
+
add_message 'scip.Relationship' do
|
|
67
67
|
optional :symbol, :string, 1
|
|
68
68
|
optional :is_reference, :bool, 2
|
|
69
69
|
optional :is_implementation, :bool, 3
|
|
@@ -71,16 +71,16 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
|
|
71
71
|
optional :is_definition, :bool, 5
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
add_message
|
|
74
|
+
add_message 'scip.Occurrence' do
|
|
75
75
|
repeated :range, :int32, 1
|
|
76
76
|
optional :symbol, :string, 2
|
|
77
77
|
optional :symbol_roles, :int32, 3
|
|
78
78
|
repeated :override_documentation, :string, 4
|
|
79
|
-
optional :syntax_kind, :enum, 5,
|
|
80
|
-
repeated :diagnostics, :message, 6,
|
|
79
|
+
optional :syntax_kind, :enum, 5, 'scip.SyntaxKind'
|
|
80
|
+
repeated :diagnostics, :message, 6, 'scip.Diagnostic'
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
add_enum
|
|
83
|
+
add_enum 'scip.SymbolInformation.Kind' do
|
|
84
84
|
value :UnspecifiedKind, 0
|
|
85
85
|
value :Class, 7
|
|
86
86
|
value :Method, 26
|
|
@@ -88,47 +88,47 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
|
|
88
88
|
value :Package, 35
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
add_message
|
|
91
|
+
add_message 'scip.Document' do
|
|
92
92
|
optional :language, :string, 4
|
|
93
93
|
optional :relative_path, :string, 1
|
|
94
|
-
repeated :occurrences, :message, 2,
|
|
95
|
-
repeated :symbols, :message, 3,
|
|
94
|
+
repeated :occurrences, :message, 2, 'scip.Occurrence'
|
|
95
|
+
repeated :symbols, :message, 3, 'scip.SymbolInformation'
|
|
96
96
|
optional :text, :string, 5
|
|
97
|
-
optional :position_encoding, :enum, 6,
|
|
97
|
+
optional :position_encoding, :enum, 6, 'scip.PositionEncoding'
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
add_message
|
|
100
|
+
add_message 'scip.SymbolInformation' do
|
|
101
101
|
optional :symbol, :string, 1
|
|
102
102
|
repeated :documentation, :string, 3
|
|
103
|
-
repeated :relationships, :message, 4,
|
|
104
|
-
optional :kind, :enum, 5,
|
|
103
|
+
repeated :relationships, :message, 4, 'scip.Relationship'
|
|
104
|
+
optional :kind, :enum, 5, 'scip.SymbolInformation.Kind'
|
|
105
105
|
optional :display_name, :string, 6
|
|
106
|
-
optional :signature_documentation, :message, 7,
|
|
106
|
+
optional :signature_documentation, :message, 7, 'scip.Document'
|
|
107
107
|
optional :snippet, :string, 8
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
-
add_message
|
|
111
|
-
optional :metadata, :message, 1,
|
|
112
|
-
repeated :documents, :message, 2,
|
|
113
|
-
repeated :external_symbols, :message, 3,
|
|
110
|
+
add_message 'scip.Index' do
|
|
111
|
+
optional :metadata, :message, 1, 'scip.Metadata'
|
|
112
|
+
repeated :documents, :message, 2, 'scip.Document'
|
|
113
|
+
repeated :external_symbols, :message, 3, 'scip.SymbolInformation'
|
|
114
114
|
end
|
|
115
115
|
end
|
|
116
116
|
|
|
117
117
|
module Hind
|
|
118
118
|
module SCIP
|
|
119
|
-
Index = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
120
|
-
Metadata = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
121
|
-
ProtocolVersion = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
122
|
-
TextEncoding = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
123
|
-
ToolInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
124
|
-
Document = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
125
|
-
PositionEncoding = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
126
|
-
Occurrence = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
127
|
-
SymbolInformation = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
128
|
-
SymbolInformation::Kind = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
129
|
-
Relationship = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
130
|
-
SyntaxKind = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
131
|
-
Diagnostic = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
132
|
-
Severity = Google::Protobuf::DescriptorPool.generated_pool.lookup(
|
|
119
|
+
Index = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.Index').msgclass
|
|
120
|
+
Metadata = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.Metadata').msgclass
|
|
121
|
+
ProtocolVersion = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.ProtocolVersion').enummodule
|
|
122
|
+
TextEncoding = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.TextEncoding').enummodule
|
|
123
|
+
ToolInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.ToolInfo').msgclass
|
|
124
|
+
Document = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.Document').msgclass
|
|
125
|
+
PositionEncoding = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.PositionEncoding').enummodule
|
|
126
|
+
Occurrence = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.Occurrence').msgclass
|
|
127
|
+
SymbolInformation = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.SymbolInformation').msgclass
|
|
128
|
+
SymbolInformation::Kind = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.SymbolInformation.Kind').enummodule
|
|
129
|
+
Relationship = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.Relationship').msgclass
|
|
130
|
+
SyntaxKind = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.SyntaxKind').enummodule
|
|
131
|
+
Diagnostic = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.Diagnostic').msgclass
|
|
132
|
+
Severity = Google::Protobuf::DescriptorPool.generated_pool.lookup('scip.Severity').enummodule
|
|
133
133
|
end
|
|
134
134
|
end
|
data/lib/hind/version.rb
CHANGED