Class: PinkSpoon::RbiIndex

Inherits:
Object
  • Object
show all
Defined in:
lib/pink_spoon/rbi_index.rb

Overview

Parses every sorbet/rbi/*/.rbi file in the project and builds:

@sigs[type][method]   → sig string
@types[type][method]  → return type string
@mixins[type]         → [mixin_type, ...] from extend/include calls

Type keys are fully qualified with “::” prefix normalised away. Lookups walk the mixin chain so Hesiod.register_gauge resolves even though the method is defined on Hesiod::Gauge (which Hesiod extends).

Defined Under Namespace

Classes: RbiVisitor

Instance Method Summary collapse

Constructor Details

#initialize(root_path) ⇒ RbiIndex

Returns a new instance of RbiIndex.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/pink_spoon/rbi_index.rb', line 16

def initialize(root_path)
  @root_path       = root_path
  @sigs            = Hash.new { |h, k| h[k] = {} }
  @types           = Hash.new { |h, k| h[k] = {} }
  @defs            = Hash.new { |h, k| h[k] = {} }
  @sources         = Hash.new { |h, k| h[k] = {} }
  @locations       = Hash.new { |h, k| h[k] = {} }
  @mixins          = Hash.new { |h, k| h[k] = [] }
  @params          = Hash.new { |h, k| h[k] = {} }
  @yields              = Hash.new { |h, k| h[k] = {} }
  @const_locations     = {}
  @const_resolve_cache = {}
  @method_type_cache   = {}
  build
end

Instance Method Details

#const_source_for(type) ⇒ Object

Returns { file:, line: } pointing at the real gem source for a constant, resolved from the “# source://” comment in the RBI file, or nil. Resolution is deferred to call time and nil results are not cached so transient Bundler state changes don’t permanently break lookups.



120
121
122
123
124
125
126
127
128
# File 'lib/pink_spoon/rbi_index.rb', line 120

def const_source_for(type)
  raw = @const_locations[normalise(type)]
  return nil unless raw
  cached = @const_resolve_cache[raw]
  return cached if cached
  loc = resolve_source_uri(raw)
  @const_resolve_cache[raw] = loc if loc
  loc
end

#find_type_for_method(method_name) ⇒ Object

Returns the first RBI type that defines method_name, scanning all indexed types. Used as a last resort when mixin-chain lookups fail (e.g. WebMock methods not wired into RSpec::Core::ExampleGroup).



186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/pink_spoon/rbi_index.rb', line 186

def find_type_for_method(method_name)
  name = method_name.to_s
  return @method_type_cache[name] if @method_type_cache.key?(name)

  result = nil
  @sigs.each { |type, methods| result = type and break if methods.key?(name) }
  result ||= begin
    found = nil
    @defs.each { |type, methods| found = type and break if methods.key?(name) }
    found
  end
  @method_type_cache[name] = result
  result
end

#hover_content_for(type, method_name, _seen = []) ⇒ Object

Returns markdown hover content for type#method, or nil if we know nothing about the method at all. Prefers the Sorbet sig; falls back to the plain def line so callers always get something useful.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/pink_spoon/rbi_index.rb', line 69

def hover_content_for(type, method_name, _seen = [])
  t = normalise(type)
  return nil if _seen.include?(t)

  sig      = @sigs[t][method_name.to_s]
  def_line = @defs[t][method_name.to_s]
  source   = @sources[t][method_name.to_s]

  if sig || def_line
    parts = []
    parts << source   if source
    parts << sig      if sig
    parts << def_line if def_line
    body = parts.join("\n")
    return "**`#{t}##{method_name}`** _(via RBI)_\n\n```ruby\n#{body}\n```"
  end

  @mixins[t].each do |mixin|
    result = hover_content_for(mixin, method_name, _seen + [t])
    return result if result
  end

  nil
end

#methods_for(type, _seen = []) ⇒ Object

Returns { method_name => sig_or_def_line } for a type including all mixins. Used by the completion listener to enumerate available methods.



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/pink_spoon/rbi_index.rb', line 149

def methods_for(type, _seen = [])
  t = normalise(type)
  return {} if _seen.include?(t)

  result = {}
  @mixins[t].each do |mixin|
    result.merge!(methods_for(mixin, _seen + [t]))
  end

  @defs[t].each do |method, def_line|
    result[method] = @sigs[t][method] || def_line
  end

  result
end

#mixins_for(type) ⇒ Object

Returns the direct parent/mixin types for the given type (one level, no chain walk).



112
113
114
# File 'lib/pink_spoon/rbi_index.rb', line 112

def mixins_for(type)
  @mixins[normalise(type)].dup
end

#params_for(type, method_name, _seen = []) ⇒ Object

Returns { param_name => type_string } for type#method, or nil. Follows the mixin chain.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/pink_spoon/rbi_index.rb', line 132

def params_for(type, method_name, _seen = [])
  t = normalise(type)
  return nil if _seen.include?(t)

  direct = @params[t][method_name.to_s]
  return direct if direct && !direct.empty?

  @mixins[t].each do |mixin|
    result = params_for(mixin, method_name, _seen + [t])
    return result if result && !result.empty?
  end

  nil
end

#rbi_location_for(type, method_name, _seen = []) ⇒ Object

Returns { file:, line: } for the .rbi definition of type#method, or nil. Follows extend/include/superclass chain when not found directly.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/pink_spoon/rbi_index.rb', line 96

def rbi_location_for(type, method_name, _seen = [])
  t = normalise(type)
  return nil if _seen.include?(t)

  loc = @locations[t][method_name.to_s]
  return loc if loc

  @mixins[t].each do |mixin|
    result = rbi_location_for(mixin, method_name, _seen + [t])
    return result if result
  end

  nil
end

#return_type_for(type, method_name, _seen = []) ⇒ Object

Returns the return-type string for type#method, or nil. Follows extend/include chain when not found directly.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/pink_spoon/rbi_index.rb', line 51

def return_type_for(type, method_name, _seen = [])
  t = normalise(type)
  return nil if _seen.include?(t)

  direct = @types[t][method_name.to_s]
  return direct if direct

  @mixins[t].each do |mixin|
    result = return_type_for(mixin, method_name, _seen + [t])
    return result if result
  end

  nil
end

#signature_for(type, method_name, _seen = []) ⇒ Object

Returns the sig string for type#method, or nil. Follows extend/include chain when not found directly.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/pink_spoon/rbi_index.rb', line 34

def signature_for(type, method_name, _seen = [])
  t = normalise(type)
  return nil if _seen.include?(t)

  direct = @sigs[t][method_name.to_s]
  return direct if direct

  @mixins[t].each do |mixin|
    result = signature_for(mixin, method_name, _seen + [t])
    return result if result
  end

  nil
end

#typesObject

All known types (for debugging / testing).



202
# File 'lib/pink_spoon/rbi_index.rb', line 202

def types = @sigs.keys

#yield_type_for(type, method_name, _seen = []) ⇒ Object

Returns the yield type declared in the sig (yields(Type)), or nil. e.g. for ‘sig { yields(WebMock::RequestSignature).returns(…) }` returns “WebMock::RequestSignature”.



168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/pink_spoon/rbi_index.rb', line 168

def yield_type_for(type, method_name, _seen = [])
  t = normalise(type)
  return nil if _seen.include?(t)

  direct = @yields[t][method_name.to_s]
  return direct if direct

  @mixins[t].each do |mixin|
    result = yield_type_for(mixin, method_name, _seen + [t])
    return result if result
  end

  nil
end