rigortype 0.1.17 → 0.1.19

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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +159 -222
  3. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +24 -1
  4. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +25 -0
  5. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +29 -0
  6. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +54 -0
  7. data/lib/rigor/analysis/check_rules/rule_walk.rb +213 -0
  8. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +24 -1
  9. data/lib/rigor/analysis/check_rules.rb +275 -44
  10. data/lib/rigor/analysis/diagnostic.rb +8 -0
  11. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +581 -0
  12. data/lib/rigor/analysis/runner/pool_coordinator.rb +569 -0
  13. data/lib/rigor/analysis/runner/project_pre_passes.rb +321 -0
  14. data/lib/rigor/analysis/runner/run_snapshots.rb +46 -0
  15. data/lib/rigor/analysis/runner.rb +207 -1200
  16. data/lib/rigor/analysis/worker_session.rb +60 -11
  17. data/lib/rigor/bleeding_edge.rb +123 -0
  18. data/lib/rigor/cache/descriptor.rb +86 -8
  19. data/lib/rigor/cache/incremental_snapshot.rb +10 -4
  20. data/lib/rigor/cache/rbs_cache_producer.rb +5 -1
  21. data/lib/rigor/cache/rbs_descriptor.rb +2 -1
  22. data/lib/rigor/cache/store.rb +46 -13
  23. data/lib/rigor/cli/annotate_command.rb +100 -15
  24. data/lib/rigor/cli/check_command.rb +708 -0
  25. data/lib/rigor/cli/ci_detector.rb +94 -0
  26. data/lib/rigor/cli/diagnostic_formats.rb +345 -0
  27. data/lib/rigor/cli/plugins_command.rb +2 -4
  28. data/lib/rigor/cli/plugins_renderer.rb +0 -2
  29. data/lib/rigor/cli/prism_colorizer.rb +10 -3
  30. data/lib/rigor/cli/show_bleedingedge_command.rb +114 -0
  31. data/lib/rigor/cli/trace_command.rb +143 -0
  32. data/lib/rigor/cli/trace_renderer.rb +310 -0
  33. data/lib/rigor/cli/triage_command.rb +6 -3
  34. data/lib/rigor/cli/triage_renderer.rb +15 -1
  35. data/lib/rigor/cli.rb +21 -612
  36. data/lib/rigor/configuration/severity_profile.rb +13 -1
  37. data/lib/rigor/configuration.rb +66 -7
  38. data/lib/rigor/environment/rbs_loader.rb +78 -68
  39. data/lib/rigor/environment.rb +1 -1
  40. data/lib/rigor/inference/acceptance.rb +10 -0
  41. data/lib/rigor/inference/body_fixpoint.rb +89 -0
  42. data/lib/rigor/inference/budget_trace.rb +29 -2
  43. data/lib/rigor/inference/expression_typer.rb +1080 -105
  44. data/lib/rigor/inference/flow_tracer.rb +180 -0
  45. data/lib/rigor/inference/macro_block_self_type.rb +11 -12
  46. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +60 -0
  47. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +54 -14
  48. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +33 -1
  49. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +281 -0
  50. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +71 -0
  51. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +148 -10
  52. data/lib/rigor/inference/method_dispatcher.rb +187 -55
  53. data/lib/rigor/inference/method_parameter_binder.rb +56 -2
  54. data/lib/rigor/inference/multi_target_binder.rb +46 -3
  55. data/lib/rigor/inference/mutation_widening.rb +142 -0
  56. data/lib/rigor/inference/narrowing.rb +330 -37
  57. data/lib/rigor/inference/scope_indexer.rb +770 -39
  58. data/lib/rigor/inference/statement_evaluator.rb +998 -68
  59. data/lib/rigor/inference/synthetic_method_scanner.rb +1 -1
  60. data/lib/rigor/plugin/additional_initializer.rb +61 -38
  61. data/lib/rigor/plugin/base.rb +517 -120
  62. data/lib/rigor/plugin/macro/block_as_method.rb +22 -21
  63. data/lib/rigor/plugin/macro/nested_class_template.rb +9 -7
  64. data/lib/rigor/plugin/macro.rb +2 -3
  65. data/lib/rigor/plugin/manifest.rb +4 -24
  66. data/lib/rigor/plugin/node_rule_walk.rb +192 -0
  67. data/lib/rigor/plugin/registry.rb +264 -35
  68. data/lib/rigor/plugin.rb +1 -0
  69. data/lib/rigor/rbs_extended/conformance_checker.rb +86 -1
  70. data/lib/rigor/scope/discovery_index.rb +60 -0
  71. data/lib/rigor/scope.rb +199 -204
  72. data/lib/rigor/sig_gen/generator.rb +8 -0
  73. data/lib/rigor/sig_gen/observation_collector.rb +6 -6
  74. data/lib/rigor/source/literals.rb +14 -0
  75. data/lib/rigor/triage/catalogue.rb +4 -19
  76. data/lib/rigor/triage.rb +69 -1
  77. data/lib/rigor/type/combinator.rb +34 -0
  78. data/lib/rigor/version.rb +1 -1
  79. data/lib/rigor.rb +0 -1
  80. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +13 -29
  81. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -32
  82. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -2
  83. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +27 -90
  84. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +13 -30
  85. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +2 -4
  86. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +90 -51
  87. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -3
  88. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -29
  89. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +1 -1
  90. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -2
  91. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +11 -40
  92. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -2
  93. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +1 -1
  94. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +10 -21
  95. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +21 -34
  96. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +11 -18
  97. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
  98. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +12 -2
  99. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  100. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +37 -31
  101. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +3 -23
  102. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +8 -21
  103. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -1
  104. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +8 -29
  105. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +17 -1
  106. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +2 -2
  107. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +108 -36
  108. data/sig/rigor/analysis/fact_store.rbs +3 -0
  109. data/sig/rigor/environment.rbs +0 -2
  110. data/sig/rigor/inference/builtins/method_catalog.rbs +1 -1
  111. data/sig/rigor/inference.rbs +5 -0
  112. data/sig/rigor/plugin/base.rbs +6 -4
  113. data/sig/rigor/plugin/manifest.rbs +1 -2
  114. data/sig/rigor/scope.rbs +50 -29
  115. data/sig/rigor/source.rbs +1 -0
  116. data/sig/rigor/type.rbs +1 -0
  117. data/sig/rigor.rbs +1 -1
  118. data/skills/rigor-baseline-reduce/references/01-classify.md +27 -0
  119. data/skills/rigor-ci-setup/SKILL.md +319 -0
  120. data/skills/rigor-plugin-author/SKILL.md +6 -4
  121. data/skills/rigor-plugin-author/references/02-walker-and-types.md +22 -17
  122. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +18 -1
  123. metadata +21 -3
  124. data/lib/rigor/cache/rbs_instance_definitions.rb +0 -66
  125. data/lib/rigor/plugin/macro/external_file.rb +0 -143
@@ -228,7 +228,7 @@ module Rigor
228
228
  return unless call.is_a?(Prism::CallNode) && call.receiver.nil? && call.name == template.variant_method
229
229
 
230
230
  args = call.arguments&.arguments || []
231
- variant_const = const_name_string(args[template.name_arg_position])
231
+ variant_const = const_name_string(args[template.symbol_arg_position])
232
232
  return if variant_const.nil?
233
233
 
234
234
  yield variant_const, args[template.inner_arg_position]
@@ -3,32 +3,40 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  # ADR-38 declaration: "on `receiver_constraint` (and its
6
- # subclasses), every method named in `methods` also establishes
7
- # instance-variable state treat it like `initialize` for the
8
- # read-before-write nil soundness gate."
6
+ # subclasses), every method named in `methods` (def-form) or
7
+ # `block_methods` (block-form) also establishes instance-variable
8
+ # state — treat it like `initialize` for the read-before-write nil
9
+ # soundness gate."
10
+ #
11
+ # **Def-form** (`methods:`) — applies when the ivar write lives in a
12
+ # named `def` body. Example: Minitest `def setup; @conn = …; end`.
13
+ #
14
+ # **Block-form** (`block_methods:`) — applies when the ivar write
15
+ # lives in a block passed to a method call. Example: RSpec
16
+ # `before { @user = create(:user) }` / `let(:x) { @y = … }`.
17
+ # `ScopeIndexer` descends the block body of any `CallNode` whose
18
+ # method name is in `block_methods`, collecting ivar writes exactly
19
+ # as it would for a def-form initializer.
20
+ #
21
+ # At least one of `methods:` or `block_methods:` must be non-empty.
9
22
  #
10
23
  # Authored on a plugin manifest:
11
24
  #
12
- # manifest(
13
- # id: "minitest",
14
- # version: "0.1.0",
15
- # additional_initializers: [
16
- # Rigor::Plugin::AdditionalInitializer.new(
17
- # receiver_constraint: "Minitest::Test",
18
- # methods: [:setup]
19
- # )
20
- # ]
25
+ # # def-form (Minitest):
26
+ # AdditionalInitializer.new(
27
+ # receiver_constraint: "Minitest::Test",
28
+ # methods: [:setup]
29
+ # )
30
+ #
31
+ # # block-form (RSpec):
32
+ # AdditionalInitializer.new(
33
+ # receiver_constraint: "RSpec::ExampleGroup",
34
+ # block_methods: [:before, :let, :subject]
21
35
  # )
22
36
  #
23
37
  # The Ruby analogue of PHPStan's `AdditionalConstructorsExtension`.
24
38
  # `Rigor::Inference::ScopeIndexer` consults the aggregated set at
25
- # its single read-before-write gate: for a `def` whose name is in
26
- # `methods` on a class that equals or inherits from
27
- # `receiver_constraint` (matched via `Environment#class_ordering`,
28
- # the same mechanism ADR-16 Tier A uses), the method's ivar writes
29
- # are folded into the class's `init_writes` set, so a sibling
30
- # method reading those ivars no longer gets a `Constant[nil]`
31
- # widening.
39
+ # its read-before-write gate.
32
40
  #
33
41
  # The contribution can only ever *suppress* a nil widening — it
34
42
  # never makes the analyzer stricter — so a missed or over-broad
@@ -39,38 +47,47 @@ module Rigor
39
47
  #
40
48
  # - `receiver_constraint` — fully-qualified class name (String).
41
49
  # The entry applies to that class and its subclasses.
42
- # - `methods` — Array of Symbol method names treated as
43
- # initializers on a matching class.
50
+ # - `methods` — Array of Symbol `def`-form method names (may be
51
+ # empty when only block_methods is used).
52
+ # - `block_methods` — Array of Symbol call-with-block method names
53
+ # (may be empty when only methods is used).
44
54
  #
45
55
  # ## Ractor-shareability
46
56
  #
47
- # Both fields are frozen at construction (ADR-15 Phase 1);
48
- # `Ractor.shareable?` returns true after `#initialize`, so the
49
- # value object survives `Plugin::Registry.materialize` into a
50
- # worker Ractor.
57
+ # All fields are frozen at construction (ADR-15 Phase 1);
58
+ # `Ractor.shareable?` returns true after `#initialize`.
51
59
  class AdditionalInitializer
52
- attr_reader :receiver_constraint, :methods
60
+ attr_reader :receiver_constraint, :methods, :block_methods
53
61
 
54
- def initialize(receiver_constraint:, methods:)
62
+ def initialize(receiver_constraint:, methods: [], block_methods: [])
55
63
  validate_receiver_constraint!(receiver_constraint)
56
- validate_methods!(methods)
64
+ validate_method_list!(methods, :methods)
65
+ validate_method_list!(block_methods, :block_methods)
66
+ validate_at_least_one!(methods, block_methods)
57
67
 
58
68
  @receiver_constraint = receiver_constraint.dup.freeze
59
69
  @methods = methods.map(&:to_sym).freeze
70
+ @block_methods = block_methods.map(&:to_sym).freeze
60
71
  freeze
61
72
  end
62
73
 
63
- # True when `method_name` (a Symbol) is declared an initializer
64
- # by this entry. The class-constraint match is the caller's
65
- # responsibility (it needs the environment's class graph).
74
+ # True when `method_name` (a Symbol) is declared a def-form
75
+ # initializer by this entry.
66
76
  def covers_method?(method_name)
67
77
  methods.include?(method_name)
68
78
  end
69
79
 
80
+ # True when `method_name` (a Symbol) is declared a block-form
81
+ # initializer by this entry.
82
+ def covers_block_method?(method_name)
83
+ block_methods.include?(method_name)
84
+ end
85
+
70
86
  def to_h
71
87
  {
72
88
  "receiver_constraint" => receiver_constraint,
73
- "methods" => methods.map(&:to_s)
89
+ "methods" => methods.map(&:to_s),
90
+ "block_methods" => block_methods.map(&:to_s)
74
91
  }
75
92
  end
76
93
 
@@ -93,16 +110,22 @@ module Rigor
93
110
  "got #{value.inspect}"
94
111
  end
95
112
 
96
- def validate_methods!(value)
97
- if value.is_a?(Array) && !value.empty? &&
98
- value.all? { |m| m.is_a?(Symbol) || (m.is_a?(String) && !m.empty?) }
99
- return
100
- end
113
+ def validate_method_list!(value, field)
114
+ return if value.is_a?(Array) &&
115
+ value.all? { |m| m.is_a?(Symbol) || (m.is_a?(String) && !m.empty?) }
101
116
 
102
117
  raise ArgumentError,
103
- "Plugin::AdditionalInitializer#methods must be a non-empty Array of " \
118
+ "Plugin::AdditionalInitializer##{field} must be an Array of " \
104
119
  "Symbol/non-empty String, got #{value.inspect}"
105
120
  end
121
+
122
+ def validate_at_least_one!(methods, block_methods)
123
+ return unless methods.empty? && block_methods.empty?
124
+
125
+ raise ArgumentError,
126
+ "Plugin::AdditionalInitializer requires at least one of methods: or block_methods: " \
127
+ "to be non-empty"
128
+ end
106
129
  end
107
130
  end
108
131
  end