hind 0.1.17 → 0.1.18

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: ba0a23c04400d06ebf294a87558c5893fb195b0e477acf49772598827f1afdba
4
- data.tar.gz: a213c39aa228dd18612d45b85a1087aaec375ba3ba2477dc5dd7ff14dd0a094a
3
+ metadata.gz: df06e6747a2824041eec9a746d2ba16d6bbabf3cb927da9800157dbaeb2095a9
4
+ data.tar.gz: 93e7b0e378c3d242af3658a0d7b83687d2e454628e57f91d5c09a9927169b024
5
5
  SHA512:
6
- metadata.gz: d806daccb7faa4378c8bd644ff094b6fb8dda612ecaae3536cdf5f2e6e6551ebbdd0d5de35612daa4a7e9727c2e51fee44c6a0fe029882397f19dbbda9a15cae
7
- data.tar.gz: a0586d0ec3f980ae369138fd712fe670162be4ac6444f495fb8c414253fcfd251df4c4ccec80988305178ff20ddbb93034bbe1b812058b752b8a9fe14e31d66e
6
+ metadata.gz: 378c72668b6c0a39ef27fbaafd5053ea51fccdc80fed0d0ca721ace54d0eb1fb980030827503b84d3cc082649c4f627f1ca1f6e3e93bc9ebe44886efa2f0f6ac
7
+ data.tar.gz: 7fe5b6f0614d81bf0ae2181c63e9b11a06be8f02da670dc48e58e900e368c6c63557a8c456afa6e0f2bbc441e5688d50358298e856dfc156b685490b71c4a340
@@ -223,6 +223,44 @@ module Hind
223
223
  result_set_id
224
224
  end
225
225
 
226
+ def register_method_declaration(declaration)
227
+ return unless @current_uri && declaration[:node]
228
+
229
+ qualified_name = declaration[:name]
230
+
231
+ setup_document if @document_id.nil?
232
+ current_doc_id = @document_id
233
+
234
+ loc = declaration[:node].respond_to?(:name_loc) ? declaration[:node].name_loc : declaration[:node].location
235
+ range_id = create_range(loc)
236
+ return unless range_id
237
+
238
+ result_set_id = emit_vertex('resultSet')
239
+ emit_edge('next', range_id, result_set_id)
240
+
241
+ def_result_id = emit_vertex('definitionResult')
242
+ emit_edge('textDocument/definition', result_set_id, def_result_id)
243
+
244
+ emit_edge('item', def_result_id, [range_id], 'definitions', current_doc_id)
245
+
246
+ hover_id = emit_vertex('hoverResult', {
247
+ contents: [{
248
+ language: 'ruby',
249
+ value: "def #{declaration[:name].split('#').last}"
250
+ }]
251
+ })
252
+ emit_edge('textDocument/hover', result_set_id, hover_id)
253
+
254
+ declaration[:range_id] = range_id
255
+ declaration[:result_set_id] = result_set_id
256
+ declaration[:document_id] = current_doc_id
257
+ declaration[:file] = @current_uri
258
+
259
+ GlobalState.instance.add_method(qualified_name, declaration)
260
+
261
+ result_set_id
262
+ end
263
+
226
264
  def register_reference(reference)
227
265
  return unless @current_uri && reference[:node]
228
266
  return unless GlobalState.instance.has_declaration?(reference[:name])
@@ -18,10 +18,12 @@ module Hind
18
18
  @classes = {} # {qualified_name => {definitions: [{node:, scope:, file:, range_id:, result_set_id:, superclass:}]}}
19
19
  @modules = {} # {qualified_name => {definitions: [{node:, scope:, file:, range_id:, result_set_id:}]}}
20
20
  @constants = {} # {qualified_name => {node:, scope:, file:, range_id:, result_set_id:, value:}}
21
+ @methods = {} # {qualified_name => {node:, scope:, file:, range_id:, result_set_id:}}
21
22
  @references = {} # {qualified_name => [{file:, range_id:, document_id:}, ...]}
22
23
  @ranges = {} # {file_path => [range_ids]}
23
24
  @range_cache = {} # {file_path => {[start_line, start_col, end_line, end_col] => range_id}}
24
25
  @result_sets = {} # {qualified_name => result_set_id}
26
+ @ancestors = {} # {qualified_name => [{name:, type:}]}
25
27
  @project_id = nil
26
28
  end
27
29
 
@@ -58,6 +60,16 @@ module Hind
58
60
  @result_sets[qualified_name] = data[:result_set_id] if data[:result_set_id]
59
61
  end
60
62
 
63
+ def add_method(qualified_name, data)
64
+ @methods[qualified_name] = data
65
+ @result_sets[qualified_name] = data[:result_set_id] if data[:result_set_id]
66
+ end
67
+
68
+ def add_ancestor(qualified_name, mixed_in_module, type)
69
+ @ancestors[qualified_name] ||= []
70
+ @ancestors[qualified_name] << { name: mixed_in_module, type: type }
71
+ end
72
+
61
73
  def add_reference(qualified_name, file_path, range_id, document_id)
62
74
  @references[qualified_name] ||= []
63
75
  @references[qualified_name] << {
@@ -87,7 +99,8 @@ module Hind
87
99
  def has_declaration?(qualified_name)
88
100
  @classes.key?(qualified_name) ||
89
101
  @modules.key?(qualified_name) ||
90
- @constants.key?(qualified_name)
102
+ @constants.key?(qualified_name) ||
103
+ @methods.key?(qualified_name)
91
104
  end
92
105
 
93
106
  def get_declaration(qualified_name)
@@ -95,8 +108,10 @@ module Hind
95
108
  determine_primary_class_definition(qualified_name)
96
109
  elsif @modules.key?(qualified_name)
97
110
  determine_primary_module_definition(qualified_name)
98
- else
111
+ elsif @constants.key?(qualified_name)
99
112
  @constants[qualified_name]
113
+ else
114
+ @methods[qualified_name]
100
115
  end
101
116
  end
102
117
 
@@ -112,31 +127,48 @@ module Hind
112
127
  @ranges[file_path] || []
113
128
  end
114
129
 
115
- def find_constant_declaration(name, current_scope)
116
- # First check if the name exists exactly as provided
117
- return name if has_declaration?(name)
118
-
119
- return nil unless current_scope && !current_scope.empty?
130
+ def find_declaration(name, current_scope)
131
+ # Handle both constants (::) and methods (#)
132
+ is_method = name.start_with?('#')
133
+ sep = is_method ? '' : '::'
120
134
 
121
- # Try with the full current scope
122
- qualified_name = "#{current_scope}::#{name}"
135
+ # 1. Lexical Scope search
136
+ # Try full current scope
137
+ qualified_name = current_scope.empty? ? name : "#{current_scope}#{sep}#{name}"
138
+ # Strip leading # if simple name
139
+ qualified_name = name if current_scope.empty? && is_method
123
140
  return qualified_name if has_declaration?(qualified_name)
124
141
 
125
- # Try with parent scopes by progressively removing the innermost scope
142
+ # 2. Ancestor chain search for current scope
143
+ if !current_scope.empty?
144
+ found = resolve_in_ancestors(name, current_scope, seen: Set.new)
145
+ return found if found
146
+ end
147
+
148
+ # 3. Parent Lexical Scopes
126
149
  scope_parts = current_scope.split('::')
127
150
  while scope_parts.size > 0
128
151
  scope_parts.pop
129
- current_scope = scope_parts.join('::')
152
+ prefix = scope_parts.join('::')
130
153
 
131
- # For empty scope, just check the name directly
132
- qualified_name = current_scope.empty? ? name : "#{current_scope}::#{name}"
154
+ qualified_name = prefix.empty? ? name : "#{prefix}#{sep}#{name}"
155
+ qualified_name = name if prefix.empty? && is_method
133
156
  return qualified_name if has_declaration?(qualified_name)
157
+
158
+ # Search ancestors of the parent scope too
159
+ if !prefix.empty?
160
+ found = resolve_in_ancestors(name, prefix, seen: Set.new)
161
+ return found if found
162
+ end
134
163
  end
135
164
 
136
- # Not found in any scope
137
165
  nil
138
166
  end
139
167
 
168
+ def find_constant_declaration(name, current_scope)
169
+ find_declaration(name, current_scope)
170
+ end
171
+
140
172
  def debug_info
141
173
  {
142
174
  classes_count: @classes.size,
@@ -247,6 +279,33 @@ module Hind
247
279
  # Rule 3: Fall back to the first definition
248
280
  definitions.first
249
281
  end
282
+
283
+ private
284
+
285
+ def resolve_in_ancestors(name, scope, seen:)
286
+ return nil if seen.include?(scope)
287
+ seen.add(scope)
288
+
289
+ is_method = name.start_with?('#')
290
+ sep = is_method ? '' : '::'
291
+
292
+ # Check this scope's ancestors
293
+ ancestors = @ancestors[scope] || []
294
+ ancestors.each do |ancestor|
295
+ mixed_in = ancestor[:name]
296
+
297
+ # Try absolute name first (if it's already qualified)
298
+ qualified = "#{mixed_in}#{sep}#{name}"
299
+ qualified = name if mixed_in.empty? && is_method
300
+ return qualified if has_declaration?(qualified)
301
+
302
+ # Recurse into mixed_in's ancestors
303
+ found = resolve_in_ancestors(name, mixed_in, seen: seen)
304
+ return found if found
305
+ end
306
+
307
+ nil
308
+ end
250
309
  end
251
310
  end
252
311
  end
@@ -59,6 +59,66 @@ module Hind
59
59
  super
60
60
  end
61
61
 
62
+ def visit_def_node(node)
63
+ method_name = node.name.to_s
64
+ qualified_name = @current_scope.empty? ? "##{method_name}" : "#{current_scope_name}##{method_name}"
65
+
66
+ @generator.register_method_declaration({
67
+ type: :method,
68
+ name: qualified_name,
69
+ node: node,
70
+ scope: current_scope_name
71
+ })
72
+ super
73
+ end
74
+
75
+ def visit_call_node(node)
76
+ # Handle attr_accessor, attr_reader, attr_writer
77
+ if node.receiver.nil? && %w[attr_reader attr_writer attr_accessor].include?(node.name.to_s)
78
+ helpers = node.arguments&.arguments || []
79
+ helpers.each do |arg|
80
+ next unless arg.is_a?(Prism::SymbolNode)
81
+
82
+ name = arg.value
83
+ if %w[attr_reader attr_accessor].include?(node.name.to_s)
84
+ # Getter
85
+ qualified_name = @current_scope.empty? ? "##{name}" : "#{current_scope_name}##{name}"
86
+ @generator.register_method_declaration({
87
+ type: :method,
88
+ name: qualified_name,
89
+ node: arg, # Use symbol node as the 'def' site
90
+ scope: current_scope_name
91
+ })
92
+ end
93
+
94
+ if %w[attr_writer attr_accessor].include?(node.name.to_s)
95
+ # Setter
96
+ setter_name = "#{name}="
97
+ qualified_name = @current_scope.empty? ? "##{setter_name}" : "#{current_scope_name}##{setter_name}"
98
+ @generator.register_method_declaration({
99
+ type: :method,
100
+ name: qualified_name,
101
+ node: arg,
102
+ scope: current_scope_name
103
+ })
104
+ end
105
+ end
106
+ end
107
+
108
+ # Handle include, extend, prepend
109
+ if node.receiver.nil? && %w[include extend prepend].include?(node.name.to_s)
110
+ modules = node.arguments&.arguments || []
111
+ modules.each do |mod_node|
112
+ # Simplified: only handle simple constant names or paths
113
+ next unless mod_node.is_a?(Prism::ConstantReadNode) || mod_node.is_a?(Prism::ConstantPathNode)
114
+
115
+ mixed_in = mod_node.slice
116
+ GlobalState.instance.add_ancestor(current_scope_name, mixed_in, node.name.to_sym)
117
+ end
118
+ end
119
+ super
120
+ end
121
+
62
122
  private
63
123
 
64
124
  def current_scope_name
@@ -46,19 +46,96 @@ module Hind
46
46
  end
47
47
 
48
48
  def visit_class_node(node)
49
- @current_scope.push(node.constant_path.slice)
49
+ @current_scope.push(scip_name(node.constant_path.slice))
50
50
  super
51
51
  @current_scope.pop
52
52
  end
53
53
 
54
54
  def visit_module_node(node)
55
- @current_scope.push(node.constant_path.slice)
55
+ @current_scope.push(scip_name(node.constant_path.slice))
56
56
  super
57
57
  @current_scope.pop
58
58
  end
59
59
 
60
+ def visit_def_node(node)
61
+ # We don't push def to current_scope because methods are usually inside classes/modules
62
+ # and we use #method notation. Pushing 'method' to scope would result in A::B#method#submethod
63
+ # which is not standard for Ruby normally unless we handle nested blocks.
64
+ # But for now, let's just visit.
65
+ super
66
+ end
67
+
68
+ def visit_call_node(node)
69
+ # Speculative resolution for method calls
70
+ register_method_call_reference(node)
71
+ super
72
+ end
73
+
74
+ def visit_call_operator_write_node(node)
75
+ # Handle self.age += 1
76
+ # This involves both a getter (age) and a setter (age=)
77
+ register_method_call_reference(node, name_override: node.read_name.to_s)
78
+ register_method_call_reference(node, name_override: node.write_name.to_s)
79
+ super
80
+ end
81
+
82
+ def visit_call_and_write_node(node)
83
+ register_method_call_reference(node, name_override: node.read_name.to_s)
84
+ register_method_call_reference(node, name_override: node.write_name.to_s)
85
+ super
86
+ end
87
+
88
+ def visit_call_or_write_node(node)
89
+ register_method_call_reference(node, name_override: node.read_name.to_s)
90
+ register_method_call_reference(node, name_override: node.write_name.to_s)
91
+ super
92
+ end
93
+
94
+ def visit_index_operator_write_node(node)
95
+ # Handle obj[key] += val
96
+ register_method_call_reference(node, name_override: "[]")
97
+ register_method_call_reference(node, name_override: "[]=")
98
+ super
99
+ end
100
+
101
+ def visit_index_and_write_node(node)
102
+ register_method_call_reference(node, name_override: "[]")
103
+ register_method_call_reference(node, name_override: "[]=")
104
+ super
105
+ end
106
+
107
+ def visit_index_or_write_node(node)
108
+ register_method_call_reference(node, name_override: "[]")
109
+ register_method_call_reference(node, name_override: "[]=")
110
+ super
111
+ end
112
+
60
113
  private
61
114
 
115
+ def register_method_call_reference(node, name_override: nil)
116
+ method_name = name_override || node.name.to_s
117
+
118
+ # Try finding #method_name in current scope
119
+ found_name = GlobalState.instance.find_declaration("##{method_name}", current_scope_name)
120
+
121
+ if found_name
122
+ loc = node.respond_to?(:message_loc) ? (node.message_loc || node.location) : node.location
123
+ @generator.register_reference({
124
+ type: :method,
125
+ name: found_name,
126
+ node: node,
127
+ location: loc
128
+ })
129
+ end
130
+ end
131
+
132
+ def scip_name(name)
133
+ # Basic unescaping to match what we store in GlobalState keys if needed
134
+ # We store them unescaped in scope normally or with backticks if scip_name was used.
135
+ # Let's be consistent.
136
+ name.delete('`')
137
+ end
138
+
62
139
  def current_scope_name
63
140
  @current_scope.join('::')
64
141
  end
@@ -280,17 +280,135 @@ module Hind
280
280
  end
281
281
 
282
282
  def visit_call_node(node)
283
- return if @mode == :index
283
+ # Handle attr_accessor, attr_reader, attr_writer
284
+ index_attribute_helpers(node)
285
+ index_ancestor_helpers(node)
286
+
287
+ return super if @mode == :index
284
288
 
285
289
  # Skip common methods
286
290
  return super if %w[new puts p print].include?(node.name.to_s)
287
291
 
292
+ register_scip_call_reference(node)
293
+ super
294
+ end
295
+
296
+ def visit_call_operator_write_node(node)
297
+ return super if @mode == :index
298
+ register_scip_call_reference(node, name_override: node.read_name.to_s)
299
+ register_scip_call_reference(node, name_override: node.write_name.to_s)
300
+ super
301
+ end
302
+
303
+ def visit_call_and_write_node(node)
304
+ return super if @mode == :index
305
+ register_scip_call_reference(node, name_override: node.read_name.to_s)
306
+ register_scip_call_reference(node, name_override: node.write_name.to_s)
307
+ super
308
+ end
309
+
310
+ def visit_call_or_write_node(node)
311
+ return super if @mode == :index
312
+ register_scip_call_reference(node, name_override: node.read_name.to_s)
313
+ register_scip_call_reference(node, name_override: node.write_name.to_s)
314
+ super
315
+ end
316
+
317
+ def visit_index_operator_write_node(node)
318
+ return super if @mode == :index
319
+ register_scip_call_reference(node, name_override: "[]")
320
+ register_scip_call_reference(node, name_override: "[]=")
321
+ super
322
+ end
323
+
324
+ def visit_index_and_write_node(node)
325
+ return super if @mode == :index
326
+ register_scip_call_reference(node, name_override: "[]")
327
+ register_scip_call_reference(node, name_override: "[]=")
328
+ super
329
+ end
330
+
331
+ def visit_index_or_write_node(node)
332
+ return super if @mode == :index
333
+ register_scip_call_reference(node, name_override: "[]")
334
+ register_scip_call_reference(node, name_override: "[]=")
335
+ super
336
+ end
337
+
338
+ private
339
+
340
+ def index_attribute_helpers(node)
341
+ return unless node.receiver.nil? && %w[attr_reader attr_writer attr_accessor].include?(node.name.to_s)
342
+
343
+ helpers = node.arguments&.arguments || []
344
+ helpers.each do |arg|
345
+ next unless arg.is_a?(Prism::SymbolNode)
346
+
347
+ name = arg.value
348
+ if %w[attr_reader attr_accessor].include?(node.name.to_s)
349
+ # Getter
350
+ getter_name = scip_name(name)
351
+ qualified_name = "#{current_scope_name}##{name}"
352
+ suffix = @current_scope.empty? ? '' : '#'
353
+ symbol = "#{@package_prefix}#{@current_scope.join("#")}#{suffix}#{getter_name}."
354
+
355
+ if @mode == :index
356
+ GlobalState.instance.add_symbol(qualified_name, symbol)
357
+ GlobalState.instance.add_symbol("##{name}", symbol)
358
+ else
359
+ range = [arg.location.start_line - 1, arg.location.start_column, arg.location.end_column]
360
+ @occurrences << Occurrence.new(range: range, symbol: symbol, symbol_roles: 1, syntax_kind: SyntaxKind::Identifier)
361
+ unless GlobalState.instance.emitted?(symbol)
362
+ @symbols << SymbolInformation.new(symbol: symbol, documentation: ["attr_reader #{name}"], kind: SymbolInformation::Kind::Method)
363
+ GlobalState.instance.mark_emitted(symbol)
364
+ end
365
+ end
366
+ end
367
+
368
+ if %w[attr_writer attr_accessor].include?(node.name.to_s)
369
+ # Setter
370
+ setter_name = "#{name}="
371
+ escaped_setter = scip_name(setter_name)
372
+ qualified_name = "#{current_scope_name}##{setter_name}"
373
+ suffix = @current_scope.empty? ? '' : '#'
374
+ symbol = "#{@package_prefix}#{@current_scope.join("#")}#{suffix}#{escaped_setter}."
375
+
376
+ if @mode == :index
377
+ GlobalState.instance.add_symbol(qualified_name, symbol)
378
+ GlobalState.instance.add_symbol("##{setter_name}", symbol)
379
+ else
380
+ range = [arg.location.start_line - 1, arg.location.start_column, arg.location.end_column]
381
+ @occurrences << Occurrence.new(range: range, symbol: symbol, symbol_roles: 1, syntax_kind: SyntaxKind::Identifier)
382
+ unless GlobalState.instance.emitted?(symbol)
383
+ @symbols << SymbolInformation.new(symbol: symbol, documentation: ["attr_writer #{name}"], kind: SymbolInformation::Kind::Method)
384
+ GlobalState.instance.mark_emitted(symbol)
385
+ end
386
+ end
387
+ end
388
+ end
389
+ end
390
+
391
+ def index_ancestor_helpers(node)
392
+ return unless node.receiver.nil? && %w[include extend prepend].include?(node.name.to_s)
393
+
394
+ modules = node.arguments&.arguments || []
395
+ modules.each do |mod_node|
396
+ next unless mod_node.is_a?(Prism::ConstantReadNode) || mod_node.is_a?(Prism::ConstantPathNode)
397
+
398
+ mixed_in = mod_node.slice
399
+ GlobalState.instance.add_ancestor(current_scope_name, mixed_in)
400
+ end
401
+ end
402
+
403
+ def register_scip_call_reference(node, name_override: nil)
404
+ method_name = name_override || node.name.to_s
405
+
288
406
  # Speculative resolution
289
- symbol = GlobalState.instance.find_symbol("#{current_scope_name}##{node.name}", '') ||
290
- GlobalState.instance.find_symbol("##{node.name}", '')
407
+ symbol = GlobalState.instance.find_symbol("#{current_scope_name}##{method_name}", '') ||
408
+ GlobalState.instance.find_symbol("##{method_name}", '')
291
409
 
292
410
  if symbol
293
- loc = node.message_loc || node.location
411
+ loc = node.respond_to?(:message_loc) ? (node.message_loc || node.location) : node.location
294
412
  range = [
295
413
  loc.start_line - 1,
296
414
  loc.start_column,
@@ -304,7 +422,6 @@ module Hind
304
422
  syntax_kind: SyntaxKind::Identifier
305
423
  )
306
424
  end
307
- super
308
425
  end
309
426
 
310
427
  private
@@ -14,9 +14,15 @@ module Hind
14
14
 
15
15
  def reset
16
16
  @symbols = {} # {qualified_name => symbol_string}
17
+ @ancestors = {} # {qualified_name => [symbol_strings]}
17
18
  @emitted_symbols = Set.new
18
19
  end
19
20
 
21
+ def add_ancestor(qualified_name, mixed_in_symbol)
22
+ @ancestors[qualified_name] ||= []
23
+ @ancestors[qualified_name] << mixed_in_symbol
24
+ end
25
+
20
26
  def mark_emitted(symbol)
21
27
  @emitted_symbols.add(symbol)
22
28
  end
@@ -38,20 +44,59 @@ module Hind
38
44
  end
39
45
 
40
46
  def find_symbol(name, current_scope)
41
- # Reuse LSIF-like scope resolution logic
42
- return @symbols[name] if @symbols.key?(name)
47
+ # Handle both constants (::) and methods (#)
48
+ is_method = name.start_with?('#')
49
+ sep = is_method ? '' : '::'
43
50
 
44
- return nil unless current_scope && !current_scope.empty?
45
-
46
- qualified_name = "#{current_scope}::#{name}"
51
+ # 1. Lexical Scope search
52
+ qualified_name = current_scope.empty? ? name : "#{current_scope}#{sep}#{name}"
53
+ qualified_name = name if current_scope.empty? && is_method
47
54
  return @symbols[qualified_name] if @symbols.key?(qualified_name)
48
55
 
56
+ # 2. Ancestor chain search for current scope
57
+ if !current_scope.empty?
58
+ found = resolve_in_ancestors(name, current_scope, seen: Set.new)
59
+ return found if found
60
+ end
61
+
62
+ # 3. Parent Lexical Scopes
49
63
  scope_parts = current_scope.split('::')
50
64
  while scope_parts.size > 0
51
65
  scope_parts.pop
52
66
  prefix = scope_parts.join('::')
53
- qualified_name = prefix.empty? ? name : "#{prefix}::#{name}"
67
+
68
+ qualified_name = prefix.empty? ? name : "#{prefix}#{sep}#{name}"
69
+ qualified_name = name if prefix.empty? && is_method
54
70
  return @symbols[qualified_name] if @symbols.key?(qualified_name)
71
+
72
+ # Search ancestors of the parent scope too
73
+ if !prefix.empty?
74
+ found = resolve_in_ancestors(name, prefix, seen: Set.new)
75
+ return found if found
76
+ end
77
+ end
78
+
79
+ nil
80
+ end
81
+
82
+ private
83
+
84
+ def resolve_in_ancestors(name, scope, seen:)
85
+ return nil if seen.include?(scope)
86
+ seen.add(scope)
87
+
88
+ is_method = name.start_with?('#')
89
+ sep = is_method ? '' : '::'
90
+
91
+ ancestors = @ancestors[scope] || []
92
+ ancestors.each do |mixed_in|
93
+ # 1. Try to see if mixed_in has the declaration
94
+ qualified = "#{mixed_in}#{sep}#{name}"
95
+ return @symbols[qualified] if @symbols.key?(qualified)
96
+
97
+ # 2. Recurse into mixed_in's ancestors
98
+ found = resolve_in_ancestors(name, mixed_in, seen: seen)
99
+ return found if found
55
100
  end
56
101
 
57
102
  nil
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.17'
4
+ VERSION = '0.1.18'
5
5
  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.17
4
+ version: 0.1.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aboobacker MK