hind 0.1.4 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89ca540ebc5d2ae79689ed9eb4c4ad9a89cea5b66991adecb4de8262f8d50867
4
- data.tar.gz: c1d5b39a3a772b1c4079376491361f67cb0dc2853a484e43ceca18b370798f1b
3
+ metadata.gz: 5fa041a7ffe7e916e1f3715500d21a9dea2a3a6ce60990be8cf4e7578f704702
4
+ data.tar.gz: '0779f87c520ddc1edddf0448c96d596194982d5b87fa5c380bb3a1132500bd9e'
5
5
  SHA512:
6
- metadata.gz: cbb72f614d4355c2aa7f4d96b6c5f9babbd61565c7aa6e571c1f33096b5ed8154bda34cb20c9a55523bf5b9a628ec79f7901d0a04d71ceef992cd9930bd62f19
7
- data.tar.gz: 3e2288b97ef3f4028060ff754b069bf5aa457a8429edce1e9177c75307d605a872b87eced63184cbda4578e17ffc5b39c2529ac0317d968e0a75d83e364a7b43
6
+ metadata.gz: 397847759b6c959511602d276eee22f7e89959c5858d840cf8398b874a39ef6ccfeec8d876e9c0539d5cc0bc0cce77cb1e297b7fabc30c9a4562bfb7e85ddb7b
7
+ data.tar.gz: 5c823067473c8b784a4868bf696b4374735b548eb8eb9341fb887dc78028e9223aeaac7172c621c624d0e8a3de221b50e101e908177626f9c316797e8ce2de70
data/exe/hind CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
- require "hind"
3
- require "hind/cli"
2
+ # frozen_string_literal: true
3
+
4
+ require 'hind'
5
+ require 'hind/cli'
4
6
 
5
7
  Hind::CLI.start(ARGV)
data/lib/hind/cli.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'json'
3
5
  require 'pathname'
@@ -26,7 +28,7 @@ module Hind
26
28
  begin
27
29
  generate_lsif(files, options)
28
30
  say "\nLSIF data has been written to: #{options[:output]}", :green if options[:verbose]
29
- rescue StandardError => e
31
+ rescue => e
30
32
  abort "Error generating LSIF: #{e.message}"
31
33
  end
32
34
  end
@@ -49,7 +51,7 @@ module Hind
49
51
  begin
50
52
  generate_scip(files, options)
51
53
  say "\nSCIP data has been written to: #{options[:output]}", :green if options[:verbose]
52
- rescue StandardError => e
54
+ rescue => e
53
55
  abort "Error generating SCIP: #{e.message}"
54
56
  end
55
57
  end
@@ -66,9 +68,7 @@ module Hind
66
68
  end
67
69
 
68
70
  def validate_output_file(output, force)
69
- if File.exist?(output) && !force
70
- abort "Error: Output file '#{output}' already exists. Use --force to overwrite."
71
- end
71
+ abort "Error: Output file '#{output}' already exists. Use --force to overwrite." if File.exist?(output) && !force
72
72
 
73
73
  # Ensure output directory exists
74
74
  FileUtils.mkdir_p(File.dirname(output))
@@ -78,10 +78,8 @@ module Hind
78
78
  pattern = File.join(directory, glob)
79
79
  files = Dir.glob(pattern)
80
80
 
81
- if exclude_patterns
82
- exclude_patterns.each do |exclude|
83
- files.reject! { |f| File.fnmatch?(exclude, f) }
84
- end
81
+ exclude_patterns&.each do |exclude|
82
+ files.reject! { |f| File.fnmatch?(exclude, f) }
85
83
  end
86
84
 
87
85
  files
@@ -94,9 +92,7 @@ module Hind
94
92
 
95
93
  File.open(options[:output], 'w') do |output_file|
96
94
  files.each do |file|
97
- if options[:verbose]
98
- say "Processing file: #{file}", :cyan
99
- end
95
+ say "Processing file: #{file}", :cyan if options[:verbose]
100
96
 
101
97
  relative_path = Pathname.new(file).relative_path_from(Pathname.new(options[:directory])).to_s
102
98
 
@@ -115,7 +111,7 @@ module Hind
115
111
  vertex_id = output.last[:id].to_i + 1
116
112
  output_file.puts(output.map(&:to_json).join("\n"))
117
113
  initial = false
118
- rescue StandardError => e
114
+ rescue => e
119
115
  warn "Warning: Failed to process file '#{file}': #{e.message}"
120
116
  next
121
117
  end
@@ -124,7 +120,7 @@ module Hind
124
120
  end
125
121
 
126
122
  def generate_scip(files, options)
127
- raise NotImplementedError, "SCIP generation not yet implemented"
123
+ raise NotImplementedError, 'SCIP generation not yet implemented'
128
124
  # Similar to generate_lsif but using SCIP generator
129
125
  end
130
126
 
@@ -133,7 +129,7 @@ module Hind
133
129
 
134
130
  begin
135
131
  YAML.load_file(config_path) || {}
136
- rescue StandardError => e
132
+ rescue => e
137
133
  abort "Error loading config file: #{e.message}"
138
134
  end
139
135
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hind
2
4
  module LSIF
3
5
  class Edge
@@ -12,7 +14,7 @@ module Hind
12
14
  @document = document
13
15
  end
14
16
 
15
- def to_json
17
+ def to_json(*_args)
16
18
  json = {
17
19
  id: @id,
18
20
  type: 'edge',
@@ -91,7 +91,7 @@ module Hind
91
91
  }
92
92
  })
93
93
 
94
- @global_state.project_id = emit_vertex('project', { kind: 'ruby' })
94
+ @global_state.project_id = emit_vertex('project', {kind: 'ruby'})
95
95
  end
96
96
 
97
97
  def setup_document
@@ -110,9 +110,9 @@ module Hind
110
110
  file_path = File.join(@metadata[:projectRoot], @metadata[:uri])
111
111
  ranges = @global_state.ranges[file_path]
112
112
 
113
- if ranges&.any?
114
- emit_edge('contains', @document_id, ranges)
115
- end
113
+ return unless ranges&.any?
114
+
115
+ emit_edge('contains', @document_id, ranges)
116
116
  end
117
117
 
118
118
  def update_cross_file_references
@@ -150,15 +150,16 @@ module Hind
150
150
  def valid_in_v?(in_v)
151
151
  return false unless in_v
152
152
  return in_v.any? if in_v.is_a?(Array)
153
+
153
154
  true
154
155
  end
155
156
 
156
157
  def edge_document(label)
157
- label == 'item' ? @document_id : nil
158
+ (label == 'item') ? @document_id : nil
158
159
  end
159
160
 
160
161
  def path_to_uri(path)
161
- normalized_path = path.gsub('\\', '/')
162
+ normalized_path = path.tr('\\', '/')
162
163
  normalized_path = normalized_path.sub(%r{^file://}, '')
163
164
  absolute_path = File.expand_path(normalized_path)
164
165
  "file://#{absolute_path}"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hind
2
4
  module LSIF
3
5
  class GlobalState
@@ -18,12 +20,12 @@ module Hind
18
20
  end
19
21
 
20
22
  def add_definition(qualified_name, file_path, range_id)
21
- @definitions[qualified_name] = { file: file_path, range_id: range_id }
23
+ @definitions[qualified_name] = {file: file_path, range_id: range_id}
22
24
  end
23
25
 
24
26
  def add_reference(qualified_name, file_path, range_id)
25
27
  @references[qualified_name] ||= []
26
- @references[qualified_name] << { file: file_path, range_id: range_id }
28
+ @references[qualified_name] << {file: file_path, range_id: range_id}
27
29
  end
28
30
  end
29
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hind
2
4
  module LSIF
3
5
  class Vertex
@@ -9,7 +11,7 @@ module Hind
9
11
  @data = data
10
12
  end
11
13
 
12
- def to_json
14
+ def to_json(*_args)
13
15
  json = {
14
16
  id: @id,
15
17
  type: 'vertex',
@@ -1,3 +1,6 @@
1
+ # lib/hind/lsif/visitor.rb
2
+ # frozen_string_literal: true
3
+
1
4
  module Hind
2
5
  module LSIF
3
6
  class Visitor
@@ -9,7 +12,7 @@ module Hind
9
12
  def visit(node)
10
13
  return unless node
11
14
 
12
- method_name = "visit_#{node.class.name.split('::').last.downcase}"
15
+ method_name = "visit_#{node.class.name.split("::").last.downcase}"
13
16
  if respond_to?(method_name)
14
17
  send(method_name, node)
15
18
  else
@@ -22,6 +25,7 @@ module Hind
22
25
  end
23
26
 
24
27
  def visit_defnode(node)
28
+ # Handle method definitions
25
29
  method_name = node.name.to_s
26
30
  qualified_name = current_scope_name.empty? ? method_name : "#{current_scope_name}##{method_name}"
27
31
 
@@ -33,7 +37,7 @@ module Hind
33
37
  @generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
34
38
  @generator.emit_edge('item', def_result_id, [range_id], 'definitions')
35
39
 
36
- # Add hover information
40
+ # Generate method signature for hover
37
41
  sig = []
38
42
  sig << "def #{qualified_name}"
39
43
  sig << "(#{node.parameters.slice})" if node.parameters
@@ -51,13 +55,214 @@ module Hind
51
55
  visit_children(node)
52
56
  end
53
57
 
54
- # Additional visitor methods...
58
+ def visit_classnode(node)
59
+ @current_scope.push(node.constant_path.slice)
60
+ class_name = current_scope_name
61
+
62
+ range_id = @generator.create_range(node.location, node.location)
63
+ result_set_id = @generator.emit_vertex('resultSet')
64
+ @generator.emit_edge('next', range_id, result_set_id)
65
+
66
+ def_result_id = @generator.emit_vertex('definitionResult')
67
+ @generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
68
+ @generator.emit_edge('item', def_result_id, [range_id], 'definitions')
69
+
70
+ # Generate hover with inheritance info
71
+ hover = []
72
+ class_def = "class #{class_name}"
73
+ class_def += " < #{node.superclass.slice}" if node.superclass
74
+
75
+ hover << if node.superclass
76
+ "#{class_def}\n\nInherits from: #{node.superclass.slice}"
77
+ else
78
+ class_def
79
+ end
80
+
81
+ hover_id = @generator.emit_vertex('hoverResult', {
82
+ contents: [{
83
+ language: 'ruby',
84
+ value: hover.join("\n")
85
+ }]
86
+ })
87
+ @generator.emit_edge('textDocument/hover', result_set_id, hover_id)
88
+
89
+ @generator.add_to_global_state(class_name, result_set_id, range_id)
90
+
91
+ # Handle inheritance
92
+ visit_inheritance(node.superclass) if node.superclass
93
+
94
+ visit_children(node)
95
+ @current_scope.pop
96
+ end
97
+
98
+ def visit_modulenode(node)
99
+ @current_scope.push(node.constant_path.slice)
100
+ module_name = current_scope_name
101
+
102
+ range_id = @generator.create_range(node.location, node.location)
103
+ result_set_id = @generator.emit_vertex('resultSet')
104
+ @generator.emit_edge('next', range_id, result_set_id)
105
+
106
+ def_result_id = @generator.emit_vertex('definitionResult')
107
+ @generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
108
+ @generator.emit_edge('item', def_result_id, [range_id], 'definitions')
109
+
110
+ hover_id = @generator.emit_vertex('hoverResult', {
111
+ contents: [{
112
+ language: 'ruby',
113
+ value: "module #{module_name}"
114
+ }]
115
+ })
116
+ @generator.emit_edge('textDocument/hover', result_set_id, hover_id)
117
+
118
+ @generator.add_to_global_state(module_name, result_set_id, range_id)
119
+
120
+ visit_children(node)
121
+ @current_scope.pop
122
+ end
123
+
124
+ def visit_callnode(node)
125
+ return unless node.name && node.location
126
+
127
+ method_name = node.name.to_s
128
+ qualified_names = []
129
+
130
+ # Try with current scope first
131
+ qualified_names << "#{current_scope_name}##{method_name}" unless current_scope_name.empty?
132
+
133
+ # Try with receiver's type if available
134
+ if node.receiver
135
+ case node.receiver
136
+ when Prism::ConstantReadNode
137
+ qualified_names << "#{node.receiver.name}##{method_name}"
138
+ when Prism::CallNode
139
+ # Handle method chaining
140
+ qualified_names << "#{node.receiver.name}##{method_name}" if node.receiver.name
141
+ when Prism::InstanceVariableReadNode
142
+ # Handle instance variable calls
143
+ qualified_names << "#{current_scope_name}##{method_name}" if current_scope_name
144
+ end
145
+ end
146
+
147
+ # Try as a standalone method
148
+ qualified_names << method_name
149
+
150
+ # Add references for matching qualified names
151
+ qualified_names.each do |qualified_name|
152
+ next 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
+ break # Stop after finding first match
158
+ end
159
+
160
+ visit_children(node)
161
+ end
162
+
163
+ def visit_constantreadnode(node)
164
+ return unless node.name
165
+
166
+ constant_name = node.name.to_s
167
+ qualified_name = @current_scope.empty? ? constant_name : "#{current_scope_name}::#{constant_name}"
168
+
169
+ return unless @generator.global_state.result_sets[qualified_name]
170
+
171
+ range_id = @generator.create_range(node.location, node.location)
172
+ @generator.global_state.add_reference(qualified_name, @generator.metadata[:uri], range_id)
173
+ @generator.emit_edge('next', range_id, @generator.global_state.result_sets[qualified_name])
174
+ end
175
+
176
+ def visit_constantwritenode(node)
177
+ return unless node.name
178
+
179
+ constant_name = node.name.to_s
180
+ qualified_name = @current_scope.empty? ? constant_name : "#{current_scope_name}::#{constant_name}"
181
+
182
+ range_id = @generator.create_range(node.location, node.location)
183
+ result_set_id = @generator.emit_vertex('resultSet')
184
+ @generator.emit_edge('next', range_id, result_set_id)
185
+
186
+ def_result_id = @generator.emit_vertex('definitionResult')
187
+ @generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
188
+ @generator.emit_edge('item', def_result_id, [range_id], 'definitions')
189
+
190
+ hover_id = @generator.emit_vertex('hoverResult', {
191
+ contents: [{
192
+ language: 'ruby',
193
+ value: "#{qualified_name} = ..."
194
+ }]
195
+ })
196
+ @generator.emit_edge('textDocument/hover', result_set_id, hover_id)
197
+
198
+ @generator.add_to_global_state(qualified_name, result_set_id, range_id)
199
+
200
+ visit_children(node)
201
+ end
202
+
203
+ def visit_instancevariablereadnode(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
+ return unless @generator.global_state.result_sets[qualified_name]
210
+
211
+ range_id = @generator.create_range(node.location, node.location)
212
+ @generator.global_state.add_reference(qualified_name, @generator.metadata[:uri], range_id)
213
+ @generator.emit_edge('next', range_id, @generator.global_state.result_sets[qualified_name])
214
+ end
215
+
216
+ def visit_instancevariablewritenode(node)
217
+ return unless node.name && current_scope_name
218
+
219
+ var_name = node.name.to_s
220
+ qualified_name = "#{current_scope_name}##{var_name}"
221
+
222
+ range_id = @generator.create_range(node.location, node.location)
223
+ result_set_id = @generator.emit_vertex('resultSet')
224
+ @generator.emit_edge('next', range_id, result_set_id)
225
+
226
+ def_result_id = @generator.emit_vertex('definitionResult')
227
+ @generator.emit_edge('textDocument/definition', result_set_id, def_result_id)
228
+ @generator.emit_edge('item', def_result_id, [range_id], 'definitions')
229
+
230
+ hover_id = @generator.emit_vertex('hoverResult', {
231
+ contents: [{
232
+ language: 'ruby',
233
+ value: "Instance variable #{var_name} in #{current_scope_name}"
234
+ }]
235
+ })
236
+ @generator.emit_edge('textDocument/hover', result_set_id, hover_id)
237
+
238
+ @generator.add_to_global_state(qualified_name, result_set_id, range_id)
239
+
240
+ visit_children(node)
241
+ end
55
242
 
56
243
  private
57
244
 
58
245
  def current_scope_name
59
246
  @current_scope.join('::')
60
247
  end
248
+
249
+ def visit_inheritance(node)
250
+ case node
251
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
252
+ range_id = @generator.create_range(node.location, node.location)
253
+ qualified_name = case node
254
+ when Prism::ConstantReadNode
255
+ node.name.to_s
256
+ when Prism::ConstantPathNode
257
+ node.slice
258
+ end
259
+
260
+ return unless @generator.global_state.result_sets[qualified_name]
261
+
262
+ @generator.global_state.add_reference(qualified_name, @generator.metadata[:uri], range_id)
263
+ @generator.emit_edge('next', range_id, @generator.global_state.result_sets[qualified_name])
264
+ end
265
+ end
61
266
  end
62
267
  end
63
268
  end
data/lib/hind/lsif.rb CHANGED
@@ -1,9 +1,10 @@
1
- require_relative "lsif/global_state"
2
- require_relative "lsif/edge"
3
- require_relative "lsif/generator"
4
- require_relative "lsif/vertex"
5
- require_relative "lsif/visitor"
1
+ # frozen_string_literal: true
6
2
 
3
+ require_relative 'lsif/global_state'
4
+ require_relative 'lsif/edge'
5
+ require_relative 'lsif/generator'
6
+ require_relative 'lsif/vertex'
7
+ require_relative 'lsif/visitor'
7
8
 
8
9
  module Hind
9
10
  module LSIF
data/lib/hind/parser.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hind
2
4
  class Parser
3
5
  def initialize(code)
@@ -7,6 +9,7 @@ module Hind
7
9
  def parse
8
10
  result = Prism.parse(@code)
9
11
  raise "Parse error: #{result.errors}" unless result.success?
12
+
10
13
  result.value
11
14
  end
12
15
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hind
2
4
  module SCIP
3
5
  class Generator
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hind
2
4
  module SCIP
3
5
  class Visitor
data/lib/hind/scip.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hind
2
4
  module SCIP
3
5
  end
data/lib/hind/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hind
4
- VERSION = "0.1.4"
4
+ VERSION = '0.1.5'
5
5
  end
data/lib/hind.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "hind/version"
4
- require_relative "hind/lsif"
5
- require_relative "hind/scip"
6
- require_relative "hind/parser"
3
+ require_relative 'hind/version'
4
+ require_relative 'hind/lsif'
5
+ require_relative 'hind/scip'
6
+ require_relative 'hind/parser'
7
7
 
8
8
  module Hind
9
9
  class Error < StandardError; end
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
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aboobacker MK