rigortype 0.1.7 → 0.1.8

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.
data/lib/rigor/scope.rb CHANGED
@@ -21,6 +21,7 @@ module Rigor
21
21
  :class_ivars, :class_cvars, :program_globals,
22
22
  :discovered_classes, :in_source_constants, :discovered_methods,
23
23
  :discovered_def_nodes, :discovered_method_visibilities,
24
+ :discovered_superclasses, :discovered_includes,
24
25
  :source_path
25
26
 
26
27
  EMPTY_DECLARED_TYPES = {}.compare_by_identity.freeze
@@ -51,6 +52,8 @@ module Rigor
51
52
  discovered_methods: EMPTY_CLASS_BINDINGS,
52
53
  discovered_def_nodes: EMPTY_CLASS_BINDINGS,
53
54
  discovered_method_visibilities: EMPTY_CLASS_BINDINGS,
55
+ discovered_superclasses: EMPTY_CLASS_BINDINGS,
56
+ discovered_includes: EMPTY_CLASS_BINDINGS,
54
57
  source_path: nil
55
58
  )
56
59
  @environment = environment
@@ -69,6 +72,8 @@ module Rigor
69
72
  @discovered_methods = discovered_methods
70
73
  @discovered_def_nodes = discovered_def_nodes
71
74
  @discovered_method_visibilities = discovered_method_visibilities
75
+ @discovered_superclasses = discovered_superclasses
76
+ @discovered_includes = discovered_includes
72
77
  @source_path = source_path
73
78
  freeze
74
79
  end
@@ -284,6 +289,41 @@ module Rigor
284
289
  rebuild(discovered_def_nodes: table)
285
290
  end
286
291
 
292
+ # ADR-24 slice 2 — per-class table mapping a fully
293
+ # qualified user-class name to its superclass name AS
294
+ # WRITTEN at the `class Foo < Bar` declaration (`"Bar"`,
295
+ # possibly a qualified `"A::B"`). Populated by `ScopeIndexer`
296
+ # — per-file plus the cross-file project pre-pass — and
297
+ # consumed by `ExpressionTyper#try_user_method_inference`
298
+ # to walk the superclass chain when an implicit-self call
299
+ # does not resolve against the enclosing class's own defs.
300
+ # The as-written name is resolved to a qualified class at
301
+ # walk time against the call's lexical nesting.
302
+ def superclass_of(class_name)
303
+ @discovered_superclasses[class_name.to_s]
304
+ end
305
+
306
+ def with_discovered_superclasses(table)
307
+ rebuild(discovered_superclasses: table)
308
+ end
309
+
310
+ # ADR-24 slice 2 — per-class/module table mapping a fully
311
+ # qualified user class or module to the list of module
312
+ # names it `include`s / `prepend`s, AS WRITTEN at the
313
+ # mixin call. Populated by `ScopeIndexer` (per-file plus
314
+ # the cross-file pre-pass) and consumed by
315
+ # `ExpressionTyper#resolve_user_def_through_ancestors` so an
316
+ # implicit-self call resolves against an included module's
317
+ # `def`s, not just the superclass chain. As-written names
318
+ # are resolved to qualified classes at walk time.
319
+ def includes_of(class_name)
320
+ @discovered_includes[class_name.to_s] || []
321
+ end
322
+
323
+ def with_discovered_includes(table)
324
+ rebuild(discovered_includes: table)
325
+ end
326
+
287
327
  # v0.1.2 — per-class table mapping `method_name (Symbol) →
288
328
  # :public | :private | :protected`. Populated by
289
329
  # `ScopeIndexer` for every `def` it sees inside a class
@@ -372,6 +412,8 @@ module Rigor
372
412
  discovered_classes: @discovered_classes, in_source_constants: @in_source_constants,
373
413
  discovered_methods: @discovered_methods, discovered_def_nodes: @discovered_def_nodes,
374
414
  discovered_method_visibilities: @discovered_method_visibilities,
415
+ discovered_superclasses: @discovered_superclasses,
416
+ discovered_includes: @discovered_includes,
375
417
  source_path: @source_path
376
418
  )
377
419
  self.class.new(
@@ -386,6 +428,8 @@ module Rigor
386
428
  discovered_methods: discovered_methods,
387
429
  discovered_def_nodes: discovered_def_nodes,
388
430
  discovered_method_visibilities: discovered_method_visibilities,
431
+ discovered_superclasses: discovered_superclasses,
432
+ discovered_includes: discovered_includes,
389
433
  source_path: source_path
390
434
  )
391
435
  end
@@ -413,6 +457,8 @@ module Rigor
413
457
  discovered_methods: discovered_methods,
414
458
  discovered_def_nodes: discovered_def_nodes,
415
459
  discovered_method_visibilities: discovered_method_visibilities,
460
+ discovered_superclasses: discovered_superclasses,
461
+ discovered_includes: discovered_includes,
416
462
  source_path: source_path
417
463
  )
418
464
  end
@@ -0,0 +1,296 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "hint"
4
+
5
+ module Rigor
6
+ module Triage
7
+ # ADR-23 § "Heuristic catalogue" — the six v1 recognisers.
8
+ #
9
+ # {.recognise} runs them in order over the diagnostic stream.
10
+ # Each recogniser sees only the diagnostics not yet claimed by
11
+ # an earlier one, so a `5.minutes` diagnostic counted by H1
12
+ # (ActiveSupport) is not re-counted by H2 (monkey-patch).
13
+ #
14
+ # WD3 / slice 4: recognisers key on the structured
15
+ # `qualified_rule` first; where they additionally need the
16
+ # receiver type or method name they read the structured
17
+ # `Diagnostic#receiver_type` / `#method_name` fields, falling
18
+ # back to parsing the diagnostic message only when those are
19
+ # absent. A parse failure degrades to "skip this diagnostic" —
20
+ # never a crash.
21
+ module Catalogue # rubocop:disable Metrics/ModuleLength
22
+ module_function
23
+
24
+ UNDEFINED_METHOD_RULE = "call.undefined-method"
25
+
26
+ # `undefined method `foo' for <receiver>`
27
+ UNDEF_METHOD = /\Aundefined method [`'"]([^`'"]+)['"`] for (.+)\z/
28
+
29
+ # ActiveSupport `core_ext` selectors, grouped by the core
30
+ # class they extend. Survey-grounded (the dominant clusters
31
+ # from the five-project survey + the Mastodon measurement).
32
+ AS_NUMERIC = %w[
33
+ day days hour hours minute minutes second seconds week weeks
34
+ fortnight fortnights month months year years
35
+ byte bytes kilobyte kilobytes megabyte megabytes gigabyte gigabytes
36
+ terabyte terabytes petabyte petabytes exabyte exabytes
37
+ ago since from_now in_milliseconds
38
+ ].freeze
39
+ AS_STRING = %w[
40
+ squish squish! strip_heredoc html_safe underscore camelize camelcase
41
+ pluralize singularize titleize titlecase humanize dasherize
42
+ parameterize tableize classify constantize safe_constantize
43
+ demodulize deconstantize foreign_key indent indent! truncate
44
+ truncate_words to_datetime to_date to_time exclude? at from
45
+ remove remove! mb_chars upcase_first downcase_first
46
+ ].freeze
47
+ AS_HASH = %w[
48
+ deep_dup deep_merge deep_merge! symbolize_keys symbolize_keys!
49
+ stringify_keys stringify_keys! deep_symbolize_keys deep_stringify_keys
50
+ deep_transform_keys deep_transform_keys! deep_transform_values
51
+ except! with_indifferent_access assert_valid_keys
52
+ reverse_merge reverse_merge! extract!
53
+ ].freeze
54
+ AS_ARRAY = %w[
55
+ to_sentence in_groups_of in_groups second third fourth fifth
56
+ forty_two extract_options! wrap deep_dup
57
+ ].freeze
58
+ AS_TIMEDATE = %w[
59
+ zone current beginning_of_day end_of_day beginning_of_week
60
+ end_of_week beginning_of_month end_of_month beginning_of_year
61
+ end_of_year next_week prev_week next_month prev_month
62
+ tomorrow yesterday all_day all_week all_month advance
63
+ ago since change to_fs
64
+ ].freeze
65
+ AS_BY_CLASS = {
66
+ "Integer" => AS_NUMERIC, "Float" => AS_NUMERIC, "Numeric" => AS_NUMERIC,
67
+ "String" => AS_STRING, "Symbol" => AS_STRING,
68
+ "Hash" => AS_HASH, "Array" => AS_ARRAY,
69
+ "Time" => AS_TIMEDATE, "Date" => AS_TIMEDATE,
70
+ "DateTime" => AS_TIMEDATE, "ActiveSupport::TimeWithZone" => AS_TIMEDATE
71
+ }.freeze
72
+ private_constant :AS_NUMERIC, :AS_STRING, :AS_HASH, :AS_ARRAY, :AS_TIMEDATE
73
+
74
+ # ActiveRecord query-builder methods. When flagged on an
75
+ # `Array[...]` receiver they signal a relation misinference.
76
+ AR_QUERY_METHODS = %w[
77
+ where joins includes preload eager_load references select
78
+ order reorder distinct group having limit offset pluck
79
+ find_by find_each find_in_batches in_batches none rewhere
80
+ unscope merge except_query extending
81
+ ].freeze
82
+ private_constant :AR_QUERY_METHODS
83
+
84
+ SYSTEMIC_THRESHOLD = 8 # (file, rule) count → "systemic"
85
+ MONKEY_PATCH_MIN_FILES = 3 # same (method, receiver) across N files
86
+ GENUINE_BUG_MAX_COUNT = 5 # rule total ≤ N → "likely genuine bug"
87
+ private_constant :SYSTEMIC_THRESHOLD, :MONKEY_PATCH_MIN_FILES, :GENUINE_BUG_MAX_COUNT
88
+
89
+ # @param diagnostics [Array<Analysis::Diagnostic>]
90
+ # @return [Array<Hint>]
91
+ def recognise(diagnostics)
92
+ claimed = {}.compare_by_identity
93
+ recognisers.filter_map do |recogniser|
94
+ pool = diagnostics.reject { |d| claimed[d] }
95
+ hint, matched = send(recogniser, pool)
96
+ next unless hint
97
+
98
+ matched.each { |d| claimed[d] = true }
99
+ hint
100
+ end
101
+ end
102
+
103
+ # H4 (ActiveRecord query methods) runs before H2 (generic
104
+ # monkey-patch): a known AR method on `Array[...]` deserves
105
+ # the precise relation-misinference hint, not the generic
106
+ # "project core-ext" guess H2 would otherwise claim it for.
107
+ def recognisers
108
+ %i[h1_activesupport h4_ar_relation h3_gem_without_rbs
109
+ h2_monkey_patch h5_systemic_cluster h6_genuine_bugs]
110
+ end
111
+
112
+ # --- H1 — likely ActiveSupport core_ext --------------------
113
+ def h1_activesupport(pool)
114
+ matched = pool.select do |d|
115
+ parsed = parse_undefined_method(d)
116
+ parsed && activesupport?(parsed[:receiver], parsed[:method])
117
+ end
118
+ return nil if matched.empty?
119
+
120
+ [Hint.new(
121
+ id: "activesupport-core-ext", confidence: :likely,
122
+ diagnostic_count: matched.size,
123
+ summary: "undefined-method on core classes (#{top_methods(matched)}) — " \
124
+ "ActiveSupport monkey-patches these",
125
+ action: "Wire the rigor-activesupport-core-ext RBS bundle via " \
126
+ "`signature_paths:` in .rigor.yml."
127
+ ), matched]
128
+ end
129
+
130
+ # --- H2 — likely a project monkey-patch / refinement -------
131
+ def h2_monkey_patch(pool)
132
+ groups = undefined_method_groups(pool).select do |(_method, _recv), diags|
133
+ diags.map(&:path).uniq.size >= MONKEY_PATCH_MIN_FILES
134
+ end
135
+ return nil if groups.empty?
136
+
137
+ matched = groups.values.flatten(1)
138
+ [Hint.new(
139
+ id: "project-monkey-patch", confidence: :possible,
140
+ diagnostic_count: matched.size,
141
+ summary: "same method undefined across many files " \
142
+ "(#{describe_groups(groups)}) — likely a project core-ext / refinement",
143
+ action: "Register the defining file via `pre_eval:` (ADR-17), " \
144
+ "or add an RBS overlay for the method."
145
+ ), matched]
146
+ end
147
+
148
+ # --- H3 — gem ships no RBS ---------------------------------
149
+ def h3_gem_without_rbs(pool)
150
+ notice = pool.find { |d| d.message.match?(/gem\(s\).*have no RBS available/) }
151
+ return nil unless notice
152
+
153
+ count = notice.message[/\A(\d+) gem/, 1] || "some"
154
+ [Hint.new(
155
+ id: "gem-without-rbs", confidence: :likely, diagnostic_count: 1,
156
+ summary: "#{count} Gemfile.lock gem(s) ship no RBS — undefined-method " \
157
+ "diagnostics on their classes are expected, not bugs",
158
+ action: "`rbs collection install`, ship `sig/` in the gem, or opt the " \
159
+ "gem into `dependencies.source_inference:` (ADR-10)."
160
+ ), [notice]]
161
+ end
162
+
163
+ # --- H4 — possible ActiveRecord relation misinference ------
164
+ def h4_ar_relation(pool)
165
+ matched = pool.select do |d|
166
+ parsed = parse_undefined_method(d)
167
+ parsed && AR_QUERY_METHODS.include?(parsed[:method]) &&
168
+ parsed[:receiver].start_with?("Array[")
169
+ end
170
+ return nil if matched.empty?
171
+
172
+ [Hint.new(
173
+ id: "activerecord-relation-misinference", confidence: :possible,
174
+ diagnostic_count: matched.size,
175
+ summary: "ActiveRecord query methods (#{top_methods(matched)}) flagged " \
176
+ "on an `Array[...]` receiver",
177
+ action: "Enable rigor-activerecord; if it persists the receiver is an " \
178
+ "engine misinference (an AR relation read as Array) — worth a Rigor issue."
179
+ ), matched]
180
+ end
181
+
182
+ # --- H5 — systemic single-file cluster ---------------------
183
+ def h5_systemic_cluster(pool)
184
+ bucket = pool.group_by { |d| [d.path, rule_of(d)] }
185
+ .select { |_key, diags| diags.size >= SYSTEMIC_THRESHOLD }
186
+ .max_by { |_key, diags| diags.size }
187
+ return nil unless bucket
188
+
189
+ (path, rule), matched = bucket
190
+ [Hint.new(
191
+ id: "systemic-file-cluster", confidence: :likely,
192
+ diagnostic_count: matched.size,
193
+ summary: "#{matched.size}× `#{rule}` concentrated in #{path}",
194
+ action: "Likely systemic in this file — one fix may clear many; " \
195
+ "or a strong baseline candidate (ADR-22)."
196
+ ), matched]
197
+ end
198
+
199
+ # --- H6 — low-count scattered rules = likely genuine bugs --
200
+ def h6_genuine_bugs(pool)
201
+ small = pool.group_by { |d| rule_of(d) }
202
+ .select { |rule, diags| rule && diags.size.between?(1, GENUINE_BUG_MAX_COUNT) }
203
+ return nil if small.empty?
204
+
205
+ matched = small.values.flatten(1)
206
+ rules = small.map { |rule, diags| "#{rule}×#{diags.size}" }.sort.join(", ")
207
+ [Hint.new(
208
+ id: "genuine-bugs", confidence: :likely,
209
+ diagnostic_count: matched.size,
210
+ summary: "low-count, scattered rules (#{rules})",
211
+ action: "Review these first — low-count diagnostics are usually the " \
212
+ "localised bugs Rigor caught, not systemic noise."
213
+ ), matched]
214
+ end
215
+
216
+ # --- shared helpers ----------------------------------------
217
+
218
+ # WD3 / slice 4: prefer the structured `receiver_type` /
219
+ # `method_name` fields the `call.undefined-method` rule now
220
+ # populates; fall back to parsing the message only when they
221
+ # are absent (older diagnostics, plugin-emitted rules). Either
222
+ # way the receiver token is normalised through `receiver_class`.
223
+ def parse_undefined_method(diag)
224
+ return nil unless rule_of(diag) == UNDEFINED_METHOD_RULE
225
+
226
+ method, receiver_token = structured_undefined_method(diag) ||
227
+ message_undefined_method(diag)
228
+ return nil unless method
229
+
230
+ receiver = receiver_class(receiver_token)
231
+ return nil unless receiver
232
+
233
+ { method: method, receiver: receiver }
234
+ end
235
+
236
+ def structured_undefined_method(diag)
237
+ return nil unless diag.method_name && diag.receiver_type
238
+
239
+ [diag.method_name, diag.receiver_type]
240
+ end
241
+
242
+ def message_undefined_method(diag)
243
+ m = UNDEF_METHOD.match(diag.message)
244
+ m && [m[1], m[2]]
245
+ end
246
+
247
+ # Normalises a message receiver token to a class name.
248
+ # Integer / string / symbol literals fold to their class;
249
+ # `Foo[...]` keeps the `Array[...]` form (H4 needs it);
250
+ # `singleton(Foo)` and bare `Foo` fold to `Foo`.
251
+ def receiver_class(token)
252
+ t = token.strip
253
+ return "Integer" if t.match?(/\A-?\d+\z/)
254
+ return "Float" if t.match?(/\A-?\d+\.\d+\z/)
255
+ return "String" if t.start_with?('"', "'")
256
+ return "Symbol" if t.start_with?(":")
257
+
258
+ singleton = t[/\Asingleton\(([\w:]+)\)\z/, 1]
259
+ return singleton if singleton
260
+ return t if t.start_with?("Array[")
261
+
262
+ nominal = t[/\A([\w:]+)\[/, 1]
263
+ return nominal if nominal
264
+ return t if t.match?(/\A[\w:]+\z/)
265
+
266
+ nil
267
+ end
268
+
269
+ def activesupport?(receiver, method)
270
+ AS_BY_CLASS[receiver]&.include?(method) || false
271
+ end
272
+
273
+ def undefined_method_groups(pool)
274
+ pairs = pool.filter_map do |d|
275
+ parsed = parse_undefined_method(d)
276
+ parsed ? [[parsed[:method], parsed[:receiver]], d] : nil
277
+ end
278
+ pairs.group_by(&:first).transform_values { |group| group.map(&:last) }
279
+ end
280
+
281
+ def describe_groups(groups)
282
+ groups.keys.first(3).map { |method, recv| "`#{method}` on #{recv}" }.join(", ")
283
+ end
284
+
285
+ def top_methods(diagnostics, limit: 5)
286
+ diagnostics.filter_map { |d| parse_undefined_method(d)&.fetch(:method) }
287
+ .tally.sort_by { |method, count| [-count, method] }
288
+ .first(limit).map { |method, count| "#{method}×#{count}" }.join(" ")
289
+ end
290
+
291
+ def rule_of(diag)
292
+ diag.qualified_rule
293
+ end
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Triage
5
+ # ADR-23 — one heuristic finding produced by the {Catalogue}.
6
+ #
7
+ # - `id` — stable kebab-case identifier (`activesupport-core-ext`, …).
8
+ # - `confidence` — `:likely` or `:possible`. Surfaced in the
9
+ # `[likely …]` / `[possible …]` report framing; a hint is
10
+ # signal, never a verdict.
11
+ # - `diagnostic_count` — size of the matched cluster.
12
+ # - `summary` — one-line evidence string (what was matched).
13
+ # - `action` — the suggested next step, phrased imperatively
14
+ # for a human / agent (ADR-23 WD4: triage never acts itself).
15
+ Hint = Data.define(:id, :confidence, :diagnostic_count, :summary, :action) do
16
+ def to_h
17
+ {
18
+ "id" => id,
19
+ "confidence" => confidence.to_s,
20
+ "diagnostic_count" => diagnostic_count,
21
+ "summary" => summary,
22
+ "action" => action
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "triage/hint"
4
+ require_relative "triage/catalogue"
5
+
6
+ module Rigor
7
+ # ADR-23 — diagnostic triage. Aggregates a `rigor check`
8
+ # diagnostic stream into the data behind the `rigor triage`
9
+ # report: a rule-ID distribution, per-file hotspots, and the
10
+ # heuristic hint catalogue ({Triage::Catalogue}).
11
+ #
12
+ # Pure over the diagnostic stream — no second analysis pass, no
13
+ # analyzer internals. `Triage.analyze` is the single entry point;
14
+ # rendering is {CLI::TriageRenderer}'s job.
15
+ module Triage
16
+ UNCATEGORISED = "(uncategorised)"
17
+
18
+ Summary = Data.define(:total, :error, :warning, :info)
19
+ RuleCount = Data.define(:rule, :count)
20
+ Hotspot = Data.define(:file, :count, :by_rule)
21
+ Report = Data.define(:summary, :distribution, :hotspots, :hints)
22
+
23
+ module_function
24
+
25
+ # @param diagnostics [Array<Analysis::Diagnostic>]
26
+ # @param top [Integer] hotspot-file cap
27
+ # @param hints [Boolean] run the heuristic catalogue
28
+ # @return [Report]
29
+ def analyze(diagnostics, top: 10, hints: true)
30
+ Report.new(
31
+ summary: build_summary(diagnostics),
32
+ distribution: build_distribution(diagnostics),
33
+ hotspots: build_hotspots(diagnostics, top),
34
+ hints: hints ? Catalogue.recognise(diagnostics) : []
35
+ )
36
+ end
37
+
38
+ # Diagnostics without a `rule` (parse errors, internal-analyzer
39
+ # errors) bucket under a single sentinel rather than vanishing.
40
+ def rule_key(diagnostic)
41
+ diagnostic.qualified_rule || UNCATEGORISED
42
+ end
43
+
44
+ def build_summary(diagnostics)
45
+ by_severity = diagnostics.group_by(&:severity).transform_values(&:size)
46
+ Summary.new(
47
+ total: diagnostics.size,
48
+ error: by_severity.fetch(:error, 0),
49
+ warning: by_severity.fetch(:warning, 0),
50
+ info: by_severity.fetch(:info, 0)
51
+ )
52
+ end
53
+
54
+ def build_distribution(diagnostics)
55
+ diagnostics.group_by { |d| rule_key(d) }
56
+ .map { |rule, group| RuleCount.new(rule: rule, count: group.size) }
57
+ .sort_by { |row| [-row.count, row.rule] }
58
+ end
59
+
60
+ def build_hotspots(diagnostics, top)
61
+ diagnostics.group_by(&:path)
62
+ .map { |path, group| hotspot_for(path, group) }
63
+ .sort_by { |spot| [-spot.count, spot.file] }
64
+ .first(top)
65
+ end
66
+
67
+ def hotspot_for(path, group)
68
+ by_rule = group.group_by { |d| rule_key(d) }
69
+ .transform_values(&:size)
70
+ .sort_by { |rule, count| [-count, rule] }
71
+ .to_h
72
+ Hotspot.new(file: path, count: group.size, by_rule: by_rule)
73
+ end
74
+
75
+ def report_to_h(report)
76
+ {
77
+ "summary" => {
78
+ "total" => report.summary.total, "error" => report.summary.error,
79
+ "warning" => report.summary.warning, "info" => report.summary.info
80
+ },
81
+ "distribution" => report.distribution.map { |r| { "rule" => r.rule, "count" => r.count } },
82
+ "hotspots" => report.hotspots.map do |h|
83
+ { "file" => h.file, "count" => h.count, "by_rule" => h.by_rule }
84
+ end,
85
+ "hints" => report.hints.map(&:to_h)
86
+ }
87
+ end
88
+ end
89
+ end
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.1.7"
4
+ VERSION = "0.1.8"
5
5
  end
@@ -135,6 +135,7 @@ module Rigor
135
135
  def self?.build_declaration_overrides: (untyped root) -> Hash[untyped, Type::t]
136
136
  def self?.record_declarations: (untyped node, Array[String] qualified_prefix, Hash[untyped, Type::t] identity_table, Hash[String, Type::t] discovered) -> void
137
137
  def self?.discovered_classes_for_paths: (Array[String] paths, ?buffer: untyped) -> Hash[String, Type::t]
138
+ def self?.discovered_def_index_for_paths: (Array[String] paths, ?buffer: untyped) -> Hash[Symbol, untyped]
138
139
  def self?.collect_class_decls: (untyped node, Array[String] qualified_prefix, Hash[String, Type::t] accumulator) -> void
139
140
  def self?.qualified_name_for: (untyped constant_path_node) -> String?
140
141
  def self?.render_constant_path: (untyped node) -> String
data/sig/rigor/scope.rbs CHANGED
@@ -16,6 +16,8 @@ module Rigor
16
16
  attr_reader discovered_methods: Hash[String, Hash[Symbol, Symbol]]
17
17
  attr_reader discovered_def_nodes: Hash[String, Hash[Symbol, untyped]]
18
18
  attr_reader discovered_method_visibilities: Hash[String, Hash[Symbol, Symbol]]
19
+ attr_reader discovered_superclasses: Hash[String, String]
20
+ attr_reader discovered_includes: Hash[String, Array[String]]
19
21
  attr_reader source_path: String?
20
22
 
21
23
  def self.empty: (?environment: Environment, ?source_path: String?) -> Scope
@@ -44,6 +46,10 @@ module Rigor
44
46
  def top_level_def_for: (String | Symbol method_name) -> untyped?
45
47
  def with_discovered_method_visibilities: (Hash[String, Hash[Symbol, Symbol]] table) -> Scope
46
48
  def discovered_method_visibility: (String | Symbol class_name, String | Symbol method_name) -> Symbol?
49
+ def superclass_of: (String | Symbol class_name) -> String?
50
+ def with_discovered_superclasses: (Hash[String, String] table) -> Scope
51
+ def includes_of: (String | Symbol class_name) -> Array[String]
52
+ def with_discovered_includes: (Hash[String, Array[String]] table) -> Scope
47
53
  def with_fact: (Analysis::FactStore::Fact fact) -> Scope
48
54
  def with_self_type: (Type::t? type) -> Scope
49
55
  def with_declared_types: (Hash[untyped, Type::t] table) -> Scope
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rigortype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
@@ -266,6 +266,8 @@ files:
266
266
  - lib/rigor/cli/explain_command.rb
267
267
  - lib/rigor/cli/lsp_command.rb
268
268
  - lib/rigor/cli/sig_gen_command.rb
269
+ - lib/rigor/cli/triage_command.rb
270
+ - lib/rigor/cli/triage_renderer.rb
269
271
  - lib/rigor/cli/type_of_command.rb
270
272
  - lib/rigor/cli/type_of_renderer.rb
271
273
  - lib/rigor/cli/type_scan_command.rb
@@ -401,6 +403,9 @@ files:
401
403
  - lib/rigor/source/node_locator.rb
402
404
  - lib/rigor/source/node_walker.rb
403
405
  - lib/rigor/testing.rb
406
+ - lib/rigor/triage.rb
407
+ - lib/rigor/triage/catalogue.rb
408
+ - lib/rigor/triage/hint.rb
404
409
  - lib/rigor/trinary.rb
405
410
  - lib/rigor/type.rb
406
411
  - lib/rigor/type/accepts_result.rb