rigortype 0.1.7 → 0.1.9

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +186 -513
  3. data/lib/rigor/analysis/check_rules.rb +23 -1
  4. data/lib/rigor/analysis/diagnostic.rb +17 -3
  5. data/lib/rigor/analysis/runner.rb +178 -3
  6. data/lib/rigor/analysis/worker_session.rb +14 -3
  7. data/lib/rigor/cli/annotate_command.rb +224 -0
  8. data/lib/rigor/cli/baseline_command.rb +36 -16
  9. data/lib/rigor/cli/prism_colorizer.rb +111 -0
  10. data/lib/rigor/cli/triage_command.rb +83 -0
  11. data/lib/rigor/cli/triage_renderer.rb +77 -0
  12. data/lib/rigor/cli.rb +71 -5
  13. data/lib/rigor/environment.rb +9 -1
  14. data/lib/rigor/inference/builtins/method_catalog.rb +17 -1
  15. data/lib/rigor/inference/builtins/time_catalog.rb +10 -1
  16. data/lib/rigor/inference/expression_typer.rb +300 -18
  17. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +109 -0
  18. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +173 -10
  19. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +53 -1
  20. data/lib/rigor/inference/method_dispatcher/math_folding.rb +149 -0
  21. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +20 -1
  22. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +33 -8
  23. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +81 -0
  24. data/lib/rigor/inference/method_dispatcher/set_folding.rb +81 -0
  25. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +316 -2
  26. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +126 -0
  27. data/lib/rigor/inference/method_dispatcher/time_folding.rb +56 -0
  28. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +67 -0
  29. data/lib/rigor/inference/method_dispatcher.rb +179 -4
  30. data/lib/rigor/inference/method_parameter_binder.rb +67 -10
  31. data/lib/rigor/inference/narrowing.rb +29 -10
  32. data/lib/rigor/inference/scope_indexer.rb +156 -6
  33. data/lib/rigor/inference/statement_evaluator.rb +43 -21
  34. data/lib/rigor/plugin/base.rb +39 -0
  35. data/lib/rigor/plugin/loader.rb +22 -1
  36. data/lib/rigor/plugin/manifest.rb +73 -10
  37. data/lib/rigor/plugin/protocol_contract.rb +185 -0
  38. data/lib/rigor/plugin/registry.rb +66 -0
  39. data/lib/rigor/scope.rb +46 -0
  40. data/lib/rigor/triage/catalogue.rb +296 -0
  41. data/lib/rigor/triage/hint.rb +27 -0
  42. data/lib/rigor/triage.rb +89 -0
  43. data/lib/rigor/type/constant.rb +29 -2
  44. data/lib/rigor/version.rb +1 -1
  45. data/sig/rigor/inference.rbs +1 -0
  46. data/sig/rigor/scope.rbs +6 -0
  47. metadata +16 -1
@@ -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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "date"
3
4
  require_relative "../trinary"
4
5
 
5
6
  module Rigor
@@ -24,6 +25,13 @@ module Rigor
24
25
  Complex,
25
26
  Regexp,
26
27
  Pathname,
28
+ ::Set,
29
+ # `Date` covers `DateTime` (a subclass). `Time` is core.
30
+ # Both arise only from deterministic constructor folding
31
+ # (`Date.new` / `Time.utc`) — there is no Date / Time
32
+ # literal node — so a `Constant` carrier is always sound.
33
+ Date,
34
+ Time,
27
35
  TrueClass,
28
36
  FalseClass,
29
37
  NilClass
@@ -42,12 +50,31 @@ module Rigor
42
50
  raise ArgumentError, "Rigor::Type::Constant only carries scalar literals; got #{value.class}"
43
51
  end
44
52
 
45
- @value = value.is_a?(String) ? value.dup.freeze : value
53
+ @value = freezable_carrier?(value) ? value.dup.freeze : value
46
54
  freeze
47
55
  end
48
56
 
57
+ # Mutable-ish carriers are stored as a frozen copy so a later
58
+ # in-place mutation cannot rewrite the literal under us. `Time`
59
+ # joins `String` / `Set` here: `Time#localtime` mutates the
60
+ # receiver's zone in place, so the carrier holds a frozen copy
61
+ # (the catalog also blocklists the mutators). `Date` is already
62
+ # immutable, but is duped-and-frozen for symmetry.
63
+ def freezable_carrier?(value)
64
+ value.is_a?(String) || value.is_a?(::Set) ||
65
+ value.is_a?(Date) || value.is_a?(Time)
66
+ end
67
+
68
+ # `Date#inspect` / `DateTime#inspect` spell out the internal
69
+ # astronomical-Julian-day representation, which is unreadable
70
+ # in a diagnostic. ISO-8601 is the compact, deterministic
71
+ # form. `Time#inspect` is already compact (`2026-01-01
72
+ # 00:00:00 UTC`), so it keeps the default.
49
73
  def describe(_verbosity = :short)
50
- value.inspect
74
+ case value
75
+ when Date then value.iso8601
76
+ else value.inspect
77
+ end
51
78
  end
52
79
 
53
80
  # RBS supports `Literal` types for booleans, nil, integer
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.9"
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.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
@@ -261,11 +261,15 @@ files:
261
261
  - lib/rigor/cache/rbs_known_class_names.rb
262
262
  - lib/rigor/cache/store.rb
263
263
  - lib/rigor/cli.rb
264
+ - lib/rigor/cli/annotate_command.rb
264
265
  - lib/rigor/cli/baseline_command.rb
265
266
  - lib/rigor/cli/diff_command.rb
266
267
  - lib/rigor/cli/explain_command.rb
267
268
  - lib/rigor/cli/lsp_command.rb
269
+ - lib/rigor/cli/prism_colorizer.rb
268
270
  - lib/rigor/cli/sig_gen_command.rb
271
+ - lib/rigor/cli/triage_command.rb
272
+ - lib/rigor/cli/triage_renderer.rb
269
273
  - lib/rigor/cli/type_of_command.rb
270
274
  - lib/rigor/cli/type_of_renderer.rb
271
275
  - lib/rigor/cli/type_scan_command.rb
@@ -325,16 +329,23 @@ files:
325
329
  - lib/rigor/inference/macro_block_self_type.rb
326
330
  - lib/rigor/inference/method_dispatcher.rb
327
331
  - lib/rigor/inference/method_dispatcher/block_folding.rb
332
+ - lib/rigor/inference/method_dispatcher/cgi_folding.rb
328
333
  - lib/rigor/inference/method_dispatcher/constant_folding.rb
329
334
  - lib/rigor/inference/method_dispatcher/file_folding.rb
330
335
  - lib/rigor/inference/method_dispatcher/iterator_dispatch.rb
331
336
  - lib/rigor/inference/method_dispatcher/kernel_dispatch.rb
332
337
  - lib/rigor/inference/method_dispatcher/literal_string_folding.rb
338
+ - lib/rigor/inference/method_dispatcher/math_folding.rb
333
339
  - lib/rigor/inference/method_dispatcher/method_folding.rb
334
340
  - lib/rigor/inference/method_dispatcher/overload_selector.rb
335
341
  - lib/rigor/inference/method_dispatcher/rbs_dispatch.rb
336
342
  - lib/rigor/inference/method_dispatcher/receiver_affinity.rb
343
+ - lib/rigor/inference/method_dispatcher/regexp_folding.rb
344
+ - lib/rigor/inference/method_dispatcher/set_folding.rb
337
345
  - lib/rigor/inference/method_dispatcher/shape_dispatch.rb
346
+ - lib/rigor/inference/method_dispatcher/shellwords_folding.rb
347
+ - lib/rigor/inference/method_dispatcher/time_folding.rb
348
+ - lib/rigor/inference/method_dispatcher/uri_folding.rb
338
349
  - lib/rigor/inference/method_parameter_binder.rb
339
350
  - lib/rigor/inference/multi_target_binder.rb
340
351
  - lib/rigor/inference/narrowing.rb
@@ -376,6 +387,7 @@ files:
376
387
  - lib/rigor/plugin/macro/heredoc_template.rb
377
388
  - lib/rigor/plugin/macro/trait_registry.rb
378
389
  - lib/rigor/plugin/manifest.rb
390
+ - lib/rigor/plugin/protocol_contract.rb
379
391
  - lib/rigor/plugin/registry.rb
380
392
  - lib/rigor/plugin/services.rb
381
393
  - lib/rigor/plugin/trust_policy.rb
@@ -401,6 +413,9 @@ files:
401
413
  - lib/rigor/source/node_locator.rb
402
414
  - lib/rigor/source/node_walker.rb
403
415
  - lib/rigor/testing.rb
416
+ - lib/rigor/triage.rb
417
+ - lib/rigor/triage/catalogue.rb
418
+ - lib/rigor/triage/hint.rb
404
419
  - lib/rigor/trinary.rb
405
420
  - lib/rigor/type.rb
406
421
  - lib/rigor/type/accepts_result.rb