rigortype 0.2.7 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/core_overlay/csv.rbs +28 -0
- data/data/core_overlay/psych.rbs +22 -0
- data/docs/handbook/01-getting-started.md +9 -1
- data/docs/handbook/02-everyday-types.md +4 -1
- data/docs/handbook/08-understanding-errors.md +3 -3
- data/docs/manual/01-installation.md +1 -0
- data/docs/manual/02-cli-reference.md +16 -7
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/14-rails-quickstart.md +4 -2
- data/docs/manual/15-type-protection-coverage.md +27 -4
- data/docs/manual/plugins/rigor-actionpack.md +1 -1
- data/docs/manual/plugins/rigor-activerecord.md +12 -5
- data/docs/manual/plugins/rigor-rails-routes.md +11 -0
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +248 -186
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +62 -48
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +46 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_fork_scan.rb +55 -0
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +34 -18
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/missing_gem_constant_index.rb +128 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +225 -304
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +794 -1154
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/fork_map.rb +87 -0
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +627 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +154 -109
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -821
- data/lib/rigor/inference/statement_evaluator.rb +754 -1114
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +141 -184
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +92 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +40 -48
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +140 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +56 -59
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +172 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +168 -241
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +189 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +61 -75
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +319 -505
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +91 -96
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/environment.rbs +1 -0
- data/sig/rigor/scope.rbs +6 -0
- metadata +9 -1
|
@@ -6,61 +6,44 @@ require_relative "minitest/assertion_analyzer"
|
|
|
6
6
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
|
-
# rigor-minitest — narrows locals through Minitest /
|
|
10
|
-
# Test::Unit assertions.
|
|
9
|
+
# rigor-minitest — narrows locals through Minitest / Test::Unit assertions.
|
|
11
10
|
#
|
|
12
|
-
# Recognises every supported `assert_*` / `refute_*` /
|
|
13
|
-
# `
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# `post_return_fact` whose `:local`-kind target narrows the
|
|
18
|
-
# named local on the post-call edge so downstream calls in
|
|
19
|
-
# the same `it` / `test_` / `def test_*` body resolve
|
|
20
|
-
# against the narrowed type.
|
|
11
|
+
# Recognises every supported `assert_*` / `refute_*` / `assert_not_*` shape (Minitest core + Test::Unit
|
|
12
|
+
# aliases), plus the Minitest/spec `_(x).must_*` / `.wont_*` matchers (`expect(x)` / `value(x)` wrappers
|
|
13
|
+
# covered too). For each recognised assertion the plugin emits a Rigor `post_return_fact` whose
|
|
14
|
+
# `:local`-kind target narrows the named local on the post-call edge so downstream calls in the same
|
|
15
|
+
# `it` / `test_` / `def test_*` body resolve against the narrowed type.
|
|
21
16
|
#
|
|
22
17
|
# ## Why a single plugin covers two frameworks
|
|
23
18
|
#
|
|
24
|
-
# Minitest and Test::Unit share the `assert_*` / `refute_*`
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# gem layers RSpec-style matcher composition on top of
|
|
28
|
-
# Minitest's `must` API and is covered by the spec-style
|
|
29
|
-
# recogniser without additional wiring.
|
|
19
|
+
# Minitest and Test::Unit share the `assert_*` / `refute_*` surface (Test::Unit's `assert_not_*` aliases
|
|
20
|
+
# are recognised by the same rule table). The matchers_vaccine gem layers RSpec-style matcher composition
|
|
21
|
+
# on top of Minitest's `must` API and is covered by the spec-style recogniser without additional wiring.
|
|
30
22
|
#
|
|
31
23
|
# ## Configuration
|
|
32
24
|
#
|
|
33
|
-
# No configuration knobs. Activate via
|
|
34
|
-
# `plugins: ["rigor-minitest"]` in `.rigor.yml`.
|
|
25
|
+
# No configuration knobs. Activate via `plugins: ["rigor-minitest"]` in `.rigor.yml`.
|
|
35
26
|
#
|
|
36
27
|
# ## Limitations
|
|
37
28
|
#
|
|
38
|
-
# - **No `assert_raises(T) { ... }`** — that's a block-shape
|
|
39
|
-
# matcher and Rigor's narrowing model is for
|
|
29
|
+
# - **No `assert_raises(T) { ... }`** — that's a block-shape matcher and Rigor's narrowing model is for
|
|
40
30
|
# straight-line locals.
|
|
41
|
-
# - **No `assert_predicate(x, :foo?)`** — needs a
|
|
42
|
-
#
|
|
43
|
-
# - **No `
|
|
44
|
-
# (respond-to-set carrier).
|
|
45
|
-
# - **No legacy bare `x.must_be_kind_of(T)`** — the
|
|
46
|
-
# receiver IS the value, so the analyzer has nothing to
|
|
31
|
+
# - **No `assert_predicate(x, :foo?)`** — needs a predicate-state carrier Rigor doesn't model today.
|
|
32
|
+
# - **No `assert_respond_to(x, :m)`** — same shape gap (respond-to-set carrier).
|
|
33
|
+
# - **No legacy bare `x.must_be_kind_of(T)`** — the receiver IS the value, so the analyzer has nothing to
|
|
47
34
|
# narrow against. Users should migrate to `_(x).must_*`.
|
|
48
|
-
# - **No `assert_in_delta` / `assert_operator`** — float-
|
|
49
|
-
#
|
|
50
|
-
# - **No `assert_throws(:tag) { ... }` / `assert_raises`** —
|
|
51
|
-
# block-form expectations need a separate slice.
|
|
35
|
+
# - **No `assert_in_delta` / `assert_operator`** — float-range / generic operator narrowing is future work.
|
|
36
|
+
# - **No `assert_throws(:tag) { ... }` / `assert_raises`** — block-form expectations need a separate slice.
|
|
52
37
|
class Minitest < Rigor::Plugin::Base
|
|
53
38
|
manifest(
|
|
54
39
|
id: "minitest",
|
|
55
40
|
version: "0.1.0",
|
|
56
41
|
description: "Narrows locals through Minitest / Test::Unit `assert_*` / `refute_*` " \
|
|
57
42
|
"and Minitest/spec `_(x).must_*` / `.wont_*` matchers.",
|
|
58
|
-
# ADR-38 — `def setup` runs before every `def test_*`, so
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# type `@conn` (set in `setup`, read in a test) as
|
|
63
|
-
# `T | nil` and surface a false nil-receiver diagnostic.
|
|
43
|
+
# ADR-38 — `def setup` runs before every `def test_*`, so ivars it assigns are initialised by the
|
|
44
|
+
# time a test body reads them. Declaring it an additional initializer stops the read-before-write
|
|
45
|
+
# nil widening that would otherwise type `@conn` (set in `setup`, read in a test) as `T | nil` and
|
|
46
|
+
# surface a false nil-receiver diagnostic.
|
|
64
47
|
additional_initializers: [
|
|
65
48
|
Rigor::Plugin::AdditionalInitializer.new(
|
|
66
49
|
receiver_constraint: "Minitest::Test", methods: [:setup]
|
|
@@ -74,10 +57,8 @@ module Rigor
|
|
|
74
57
|
]
|
|
75
58
|
)
|
|
76
59
|
|
|
77
|
-
# ADR-37 slice 2 — emits `post_return_facts` for every recognised
|
|
78
|
-
#
|
|
79
|
-
# `:local`-kind facts through
|
|
80
|
-
# `StatementEvaluator#apply_local_post_return_fact`.
|
|
60
|
+
# ADR-37 slice 2 — emits `post_return_facts` for every recognised assertion, method-gated by the
|
|
61
|
+
# engine. The engine routes `:local`-kind facts through `StatementEvaluator#apply_local_post_return_fact`.
|
|
81
62
|
narrowing_facts methods: AssertionAnalyzer::SUPPORTED_METHODS do |call_node, scope|
|
|
82
63
|
AssertionAnalyzer.contribution_for(call_node, environment: scope&.environment)&.post_return_facts
|
|
83
64
|
end
|
|
@@ -6,41 +6,32 @@ require "prism"
|
|
|
6
6
|
module Rigor
|
|
7
7
|
module Plugin
|
|
8
8
|
class Pundit < Rigor::Plugin::Base
|
|
9
|
-
# Walks a parsed file's AST looking for Pundit
|
|
10
|
-
# entry-point calls and validates each against the
|
|
9
|
+
# Walks a parsed file's AST looking for Pundit entry-point calls and validates each against the
|
|
11
10
|
# {PolicyIndex}.
|
|
12
11
|
#
|
|
13
12
|
# Recognised shapes:
|
|
14
13
|
#
|
|
15
|
-
# - `authorize(record, :action)` — record's inferred
|
|
16
|
-
# type → `<Type>Policy#<action>?` lookup. Both the
|
|
14
|
+
# - `authorize(record, :action)` — record's inferred type → `<Type>Policy#<action>?` lookup. Both the
|
|
17
15
|
# policy class and the predicate must exist.
|
|
18
|
-
# - `authorize(record)` — without an action argument,
|
|
19
|
-
#
|
|
20
|
-
# action name is determined at runtime from the
|
|
21
|
-
# controller's current action; static validation
|
|
16
|
+
# - `authorize(record)` — without an action argument, we only validate that `<Type>Policy` exists. The
|
|
17
|
+
# action name is determined at runtime from the controller's current action; static validation
|
|
22
18
|
# isn't possible without controller context.
|
|
23
|
-
# - `policy(record)` / `policy_scope(scope)` — same
|
|
24
|
-
# `<Type>Policy` existence check.
|
|
19
|
+
# - `policy(record)` / `policy_scope(scope)` — same `<Type>Policy` existence check.
|
|
25
20
|
#
|
|
26
|
-
# When the first argument's inferred type is NOT a
|
|
27
|
-
#
|
|
28
|
-
# call is silently passed through. The plugin only
|
|
29
|
-
# validates what it can prove from the static type
|
|
21
|
+
# When the first argument's inferred type is NOT a `Nominal[T]` (e.g. an untyped local variable), the
|
|
22
|
+
# call is silently passed through. The plugin only validates what it can prove from the static type
|
|
30
23
|
# carrier.
|
|
31
24
|
module Analyzer
|
|
32
25
|
ENTRY_METHODS = %i[authorize policy policy_scope].freeze
|
|
33
26
|
|
|
34
|
-
# One policy violation: the kebab-case `rule`, `severity`, and
|
|
35
|
-
#
|
|
36
|
-
# (the `node_rule` block) positions it via `Plugin::Base#diagnostic`.
|
|
27
|
+
# One policy violation: the kebab-case `rule`, `severity`, and human-readable `message`. Carries no
|
|
28
|
+
# path/location — the caller (the `node_rule` block) positions it via `Plugin::Base#diagnostic`.
|
|
37
29
|
Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
|
|
38
30
|
|
|
39
31
|
module_function
|
|
40
32
|
|
|
41
|
-
# The policy violations for a single call node (0..2 of them), or
|
|
42
|
-
#
|
|
43
|
-
# is not statically determinable. ADR-37: the engine owns the
|
|
33
|
+
# The policy violations for a single call node (0..2 of them), or `[]` when the node is not a
|
|
34
|
+
# Pundit entry call or its record type is not statically determinable. ADR-37: the engine owns the
|
|
44
35
|
# walk, so this is per-node logic.
|
|
45
36
|
#
|
|
46
37
|
# @param call_node [Prism::Node]
|
|
@@ -71,15 +62,11 @@ module Rigor
|
|
|
71
62
|
ENTRY_METHODS.include?(node.name) && node.receiver.nil?
|
|
72
63
|
end
|
|
73
64
|
|
|
74
|
-
# Resolves the first-argument expression to a policy
|
|
75
|
-
# class name. The candidates are:
|
|
65
|
+
# Resolves the first-argument expression to a policy class name. The candidates are:
|
|
76
66
|
# - `Foo` (a constant) → `FooPolicy`
|
|
77
|
-
# - `Foo.method(...)` whose inferred type is
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
# `Nominal[Bar]` → `BarPolicy`
|
|
81
|
-
# Returns `nil` when the type isn't statically
|
|
82
|
-
# determinable.
|
|
67
|
+
# - `Foo.method(...)` whose inferred type is `Nominal[Bar]` → `BarPolicy`
|
|
68
|
+
# - any other expression whose inferred type is `Nominal[Bar]` → `BarPolicy`
|
|
69
|
+
# Returns `nil` when the type isn't statically determinable.
|
|
83
70
|
def derive_policy_class_name(record_node, scope)
|
|
84
71
|
if record_node.is_a?(Prism::ConstantReadNode) || record_node.is_a?(Prism::ConstantPathNode)
|
|
85
72
|
constant_name = constant_receiver_name(record_node)
|
|
@@ -118,10 +105,8 @@ module Rigor
|
|
|
118
105
|
)
|
|
119
106
|
end
|
|
120
107
|
|
|
121
|
-
# Validates the `authorize(record, :action)` form.
|
|
122
|
-
#
|
|
123
|
-
# (the runtime infers it from the controller — out
|
|
124
|
-
# of scope here) or when the second argument isn't
|
|
108
|
+
# Validates the `authorize(record, :action)` form. Returns nil when the call has no second argument
|
|
109
|
+
# (the runtime infers it from the controller — out of scope here) or when the second argument isn't
|
|
125
110
|
# a literal symbol / string.
|
|
126
111
|
def action_violation(call_node, policy_entry)
|
|
127
112
|
args = call_node.arguments&.arguments || []
|
|
@@ -7,22 +7,17 @@ require_relative "policy_index"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
9
|
class Pundit < Rigor::Plugin::Base
|
|
10
|
-
# Walks the configured policy-search paths via the
|
|
11
|
-
#
|
|
12
|
-
# Prism, and collects classes whose immediate
|
|
13
|
-
# superclass is one of the configured base classes.
|
|
10
|
+
# Walks the configured policy-search paths via the plugin's `IoBoundary`, parses each `.rb` file with
|
|
11
|
+
# Prism, and collects classes whose immediate superclass is one of the configured base classes.
|
|
14
12
|
#
|
|
15
|
-
# For each discovered policy class, the discoverer
|
|
16
|
-
#
|
|
17
|
-
# method. Non-predicate methods (`initialize`,
|
|
18
|
-
# `resolve`, helper methods) are ignored — Pundit's
|
|
13
|
+
# For each discovered policy class, the discoverer collects every instance-side `def name?` predicate
|
|
14
|
+
# method. Non-predicate methods (`initialize`, `resolve`, helper methods) are ignored — Pundit's
|
|
19
15
|
# `authorize` only ever calls predicate methods.
|
|
20
16
|
#
|
|
21
17
|
# Limitations (intentional for v0.1.0):
|
|
22
18
|
#
|
|
23
19
|
# - Direct-superclass match only.
|
|
24
|
-
# - Predicate methods are read from the syntactic
|
|
25
|
-
# `def` list. Methods built via `define_method` /
|
|
20
|
+
# - Predicate methods are read from the syntactic `def` list. Methods built via `define_method` /
|
|
26
21
|
# inherited from a sibling concern are out of scope.
|
|
27
22
|
class PolicyDiscoverer
|
|
28
23
|
def initialize(io_boundary:, search_paths:, base_classes:)
|
|
@@ -120,8 +115,7 @@ module Rigor
|
|
|
120
115
|
end
|
|
121
116
|
end
|
|
122
117
|
|
|
123
|
-
# Returns symbolic predicate names (`:update?`,
|
|
124
|
-
# `:show?`, …) defined on the policy. Only
|
|
118
|
+
# Returns symbolic predicate names (`:update?`, `:show?`, …) defined on the policy. Only
|
|
125
119
|
# instance-side names that end in `?` are recorded.
|
|
126
120
|
def collect_predicate_methods(body)
|
|
127
121
|
return [] if body.nil?
|
|
@@ -3,14 +3,11 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
5
|
class Pundit < Rigor::Plugin::Base
|
|
6
|
-
# Frozen catalogue of discovered Pundit policy classes
|
|
7
|
-
#
|
|
8
|
-
# Each entry tracks the set of predicate methods
|
|
9
|
-
# defined on the policy (instance-side `def name?`)
|
|
6
|
+
# Frozen catalogue of discovered Pundit policy classes keyed by policy class name (e.g. `"PostPolicy"`).
|
|
7
|
+
# Each entry tracks the set of predicate methods defined on the policy (instance-side `def name?`)
|
|
10
8
|
# plus the source file path.
|
|
11
9
|
#
|
|
12
|
-
# The analyzer maps a record's inferred type
|
|
13
|
-
# (`Nominal[Post]`) to the policy class name
|
|
10
|
+
# The analyzer maps a record's inferred type (`Nominal[Post]`) to the policy class name
|
|
14
11
|
# (`"PostPolicy"`) and looks up the predicate.
|
|
15
12
|
class PolicyIndex
|
|
16
13
|
Entry = Data.define(:policy_class_name, :file_path, :predicate_methods) do
|
|
@@ -22,8 +19,7 @@ module Rigor
|
|
|
22
19
|
predicate_methods.to_a.sort
|
|
23
20
|
end
|
|
24
21
|
|
|
25
|
-
# Normalises an action symbol / string by ensuring
|
|
26
|
-
# a trailing `?`. `:update` and `:update?` both
|
|
22
|
+
# Normalises an action symbol / string by ensuring a trailing `?`. `:update` and `:update?` both
|
|
27
23
|
# resolve to `update?`.
|
|
28
24
|
def normalize(name)
|
|
29
25
|
string = name.to_s
|
|
@@ -8,13 +8,11 @@ require_relative "pundit/analyzer"
|
|
|
8
8
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module Plugin
|
|
11
|
-
# rigor-pundit — validates Pundit `authorize` /
|
|
12
|
-
# `policy` / `policy_scope` calls against the project's
|
|
11
|
+
# rigor-pundit — validates Pundit `authorize` / `policy` / `policy_scope` calls against the project's
|
|
13
12
|
# `app/policies/` tree.
|
|
14
13
|
#
|
|
15
14
|
# Tier 3B of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
16
|
-
# Statically discovers policy classes by walking
|
|
17
|
-
# `policy_search_paths` and parsing each file with
|
|
15
|
+
# Statically discovers policy classes by walking `policy_search_paths` and parsing each file with
|
|
18
16
|
# Prism — no `pundit` runtime dependency.
|
|
19
17
|
#
|
|
20
18
|
# ## Configuration
|
|
@@ -27,34 +25,22 @@ module Rigor
|
|
|
27
25
|
#
|
|
28
26
|
# ## What it checks
|
|
29
27
|
#
|
|
30
|
-
# 1. **Policy class existence** — `authorize(record, ...)`
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# 2. **Predicate method existence** — for the
|
|
35
|
-
# `authorize(record, :action)` form, validates that
|
|
36
|
-
# `<Policy>#<action>?` is defined. Missing methods
|
|
37
|
-
# emit `unknown-policy-method` listing the known
|
|
28
|
+
# 1. **Policy class existence** — `authorize(record, ...)` looks up `<inferred-type>Policy` in the index.
|
|
29
|
+
# Missing policies emit `unknown-policy-class` with a did-you-mean suggestion.
|
|
30
|
+
# 2. **Predicate method existence** — for the `authorize(record, :action)` form, validates that
|
|
31
|
+
# `<Policy>#<action>?` is defined. Missing methods emit `unknown-policy-method` listing the known
|
|
38
32
|
# predicates.
|
|
39
33
|
#
|
|
40
34
|
# ## Limitations (v0.1.0)
|
|
41
35
|
#
|
|
42
|
-
# - Records whose inferred type is NOT a `Nominal[T]`
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
# `authorize(record)` (which Pundit resolves at
|
|
51
|
-
# runtime against the controller's current action) is
|
|
52
|
-
# passed through with the policy-class check only.
|
|
53
|
-
# - Direct-superclass match for `policy_base_classes`.
|
|
54
|
-
# Indirect inheritance (`AdminPolicy <
|
|
55
|
-
# ApplicationPolicy`) needs `AdminPolicy` listed in
|
|
56
|
-
# `policy_base_classes` if subclasses inherit from
|
|
57
|
-
# it.
|
|
36
|
+
# - Records whose inferred type is NOT a `Nominal[T]` (untyped local variables, untyped instance
|
|
37
|
+
# variables) are silently passed through. The plugin only validates what it can prove from the
|
|
38
|
+
# static carrier.
|
|
39
|
+
# - The two-argument form `authorize(record, :action_symbol)` is the only one validated. The implicit
|
|
40
|
+
# form `authorize(record)` (which Pundit resolves at runtime against the controller's current action)
|
|
41
|
+
# is passed through with the policy-class check only.
|
|
42
|
+
# - Direct-superclass match for `policy_base_classes`. Indirect inheritance (`AdminPolicy <
|
|
43
|
+
# ApplicationPolicy`) needs `AdminPolicy` listed in `policy_base_classes` if subclasses inherit from it.
|
|
58
44
|
class Pundit < Rigor::Plugin::Base
|
|
59
45
|
manifest(
|
|
60
46
|
id: "pundit",
|
|
@@ -79,10 +65,9 @@ module Rigor
|
|
|
79
65
|
@policy_base_classes = Array(config.fetch("policy_base_classes")).map(&:to_s)
|
|
80
66
|
end
|
|
81
67
|
|
|
82
|
-
# File-level only: the load-error emission. The per-call policy
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# `producer_value`, so both surfaces share one load.
|
|
68
|
+
# File-level only: the load-error emission. The per-call policy validation runs over the engine-owned
|
|
69
|
+
# walk via the node_rule below (ADR-37). The index is lazily loaded + memoised by `producer_value`,
|
|
70
|
+
# so both surfaces share one load.
|
|
86
71
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
87
72
|
index = producer_value(:policy_index)
|
|
88
73
|
return [load_error_diagnostic(path)] if index.nil? && producer_error(:policy_index)
|
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Convenience entry point. `require "rigor-rails"` requires
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# patterns, ad-hoc scripts) do not have to list seven require
|
|
7
|
-
# lines.
|
|
3
|
+
# Convenience entry point. `require "rigor-rails"` requires every Tier 1+2 Rails ecosystem plugin in one go,
|
|
4
|
+
# so projects that prefer a single require statement (some `spec_helper` patterns, ad-hoc scripts) do not
|
|
5
|
+
# have to list seven require lines.
|
|
8
6
|
#
|
|
9
|
-
# Note: requiring this entry point does NOT mark every plugin as
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# users still control which plugins participate in analysis via
|
|
14
|
-
# `.rigor.yml`.
|
|
7
|
+
# Note: requiring this entry point does NOT mark every plugin as active. The Rigor plugin loader walks
|
|
8
|
+
# `.rigor.yml`'s `plugins:` list and instantiates only the plugins enumerated there. This is per ADR-12
|
|
9
|
+
# WD1's "Gemfile-convenience meta-gem" pattern — users still control which plugins participate in analysis
|
|
10
|
+
# via `.rigor.yml`.
|
|
15
11
|
#
|
|
16
|
-
# Sub-plugins ARE registered with `Rigor::Plugin` when this file
|
|
17
|
-
#
|
|
18
|
-
# call); the loader's lookup phase finds them by id when listed.
|
|
12
|
+
# Sub-plugins ARE registered with `Rigor::Plugin` when this file loads (each gem's entry point side-effects
|
|
13
|
+
# a `Plugin.register` call); the loader's lookup phase finds them by id when listed.
|
|
19
14
|
#
|
|
20
|
-
# Adding the gem to a project's Gemfile without listing any
|
|
21
|
-
#
|
|
22
|
-
# `Bundler.require`, but no plugin's `init` / `prepare` / hooks
|
|
23
|
-
# run.
|
|
15
|
+
# Adding the gem to a project's Gemfile without listing any plugin in `.rigor.yml` is harmless: the requires
|
|
16
|
+
# happen on `Bundler.require`, but no plugin's `init` / `prepare` / hooks run.
|
|
24
17
|
|
|
25
18
|
require "rigor-rails-routes"
|
|
26
19
|
require "rigor-rails-i18n"
|
|
@@ -6,85 +6,64 @@ require "prism"
|
|
|
6
6
|
module Rigor
|
|
7
7
|
module Plugin
|
|
8
8
|
class RailsI18n < Rigor::Plugin::Base
|
|
9
|
-
# Walks a parsed file's AST looking for `t(...)` /
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# key (variable, expression) are silently passed through —
|
|
13
|
-
# the plugin only validates what it can prove statically.
|
|
9
|
+
# Walks a parsed file's AST looking for `t(...)` / `I18n.t(...)` / `I18n.translate(...)` calls with a
|
|
10
|
+
# literal-string first argument. Calls with a non-literal key (variable, expression) are silently
|
|
11
|
+
# passed through — the plugin only validates what it can prove statically.
|
|
14
12
|
#
|
|
15
13
|
# ## What gets emitted per recognised call
|
|
16
14
|
#
|
|
17
|
-
# - `plugin.rails-i18n.translation-call` (info) names the
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# - `plugin.rails-i18n.
|
|
24
|
-
#
|
|
25
|
-
# from at least one. Suppressed when the call passes
|
|
26
|
-
# `default:` (the user has signalled they're aware of
|
|
27
|
-
# the partial coverage).
|
|
28
|
-
# - `plugin.rails-i18n.wrong-interpolation` (error) when
|
|
29
|
-
# the call's interpolation hash uses keys that don't
|
|
30
|
-
# match the value's `%{var}` placeholders, or omits a
|
|
31
|
-
# required placeholder.
|
|
15
|
+
# - `plugin.rails-i18n.translation-call` (info) names the key and the locales it resolves in.
|
|
16
|
+
# - `plugin.rails-i18n.unknown-key` (error) when the key is missing from every loaded locale; the
|
|
17
|
+
# message includes a did-you-mean suggestion drawn from the index.
|
|
18
|
+
# - `plugin.rails-i18n.missing-locale` (warning) when the key resolves in some configured locales but
|
|
19
|
+
# is absent from at least one. Suppressed when the call passes `default:` (the user has signalled
|
|
20
|
+
# they're aware of the partial coverage).
|
|
21
|
+
# - `plugin.rails-i18n.wrong-interpolation` (error) when the call's interpolation hash uses keys that
|
|
22
|
+
# don't match the value's `%{var}` placeholders, or omits a required placeholder.
|
|
32
23
|
module Analyzer
|
|
33
24
|
TRANSLATE_METHODS = %i[t translate].freeze
|
|
34
25
|
|
|
35
|
-
# Methods that are always I18n receivers (`I18n.t`,
|
|
36
|
-
# `::I18n.t`).
|
|
26
|
+
# Methods that are always I18n receivers (`I18n.t`, `::I18n.t`).
|
|
37
27
|
I18N_RECEIVER_NAMES = %w[I18n ::I18n].freeze
|
|
38
28
|
|
|
39
|
-
# Matches controller file paths so lazy keys (`.key`)
|
|
40
|
-
#
|
|
41
|
-
# Captures the path segment between `controllers/` and
|
|
29
|
+
# Matches controller file paths so lazy keys (`.key`) can be expanded to
|
|
30
|
+
# `<controller_scope>.<action>.<key>`. Captures the path segment between `controllers/` and
|
|
42
31
|
# `_controller.rb` (e.g. `users`, `admin/users`).
|
|
43
32
|
CONTROLLER_PATH_RE = %r{(?:^|/)controllers/(.+)_controller\.rb$}
|
|
44
33
|
|
|
45
|
-
# Matches Rails view-template file paths to derive the
|
|
46
|
-
#
|
|
47
|
-
# lazy `t('.key')` lookups inside a template.
|
|
34
|
+
# Matches Rails view-template file paths to derive the I18n "virtual path" — the scope that Rails
|
|
35
|
+
# uses for lazy `t('.key')` lookups inside a template.
|
|
48
36
|
#
|
|
49
|
-
# Captures the path segment between `views/` and the
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
# `_form.html.erb` as "form", not "_form".
|
|
37
|
+
# Captures the path segment between `views/` and the format+variant+handler suffix, then strips a
|
|
38
|
+
# leading underscore from each segment — Rails templates resolve `_form.html.erb` as "form", not
|
|
39
|
+
# "_form".
|
|
53
40
|
#
|
|
54
41
|
# app/views/setting/index.html.erb → setting.index
|
|
55
42
|
# app/views/admin/users/new.html.erb → admin.users.new
|
|
56
43
|
# app/views/home/index.html+mobile.erb → home.index
|
|
57
44
|
# app/views/users/_form.html.erb → users.form
|
|
58
45
|
#
|
|
59
|
-
# The view-scope lazy expansion replaces the action
|
|
60
|
-
# part with the view's virtual path:
|
|
46
|
+
# The view-scope lazy expansion replaces the action part with the view's virtual path:
|
|
61
47
|
# `<%= t('.title') %>` → `setting.index.title`
|
|
62
48
|
VIEW_SCOPE_RE = %r{views/(.+?)\.(?:\w+)(?:\+\w+)?\.\w+\z}
|
|
63
49
|
|
|
64
|
-
# Regex to extract lazy-key arguments from ERB / HTML
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
# leading dot on the key string. Captures only the key
|
|
68
|
-
# part after the dot.
|
|
50
|
+
# Regex to extract lazy-key arguments from ERB / HTML template content. Matches `t('.key')`,
|
|
51
|
+
# `t(".key")`, `I18n.t('.key')`, and `I18n.translate('.key')` with a leading dot on the key string.
|
|
52
|
+
# Captures only the key part after the dot.
|
|
69
53
|
LAZY_T_KEY_RE = /\b(?:I18n\.)?(?:t|translate)\s*\(\s*(?:"\.([^"\\]*)"|'\.([^'\\]*)')/
|
|
70
54
|
|
|
71
|
-
# Reserved option keys — these are recognised by I18n
|
|
72
|
-
#
|
|
55
|
+
# Reserved option keys — these are recognised by I18n itself and not treated as interpolation
|
|
56
|
+
# variables.
|
|
73
57
|
RESERVED_OPTION_KEYS = %i[
|
|
74
58
|
default scope locale count raise throw fallback
|
|
75
59
|
fallback_in_progress separator deep_interpolation
|
|
76
60
|
].to_set.freeze
|
|
77
61
|
|
|
78
|
-
# Key prefixes Rails / `rails-i18n` ship in every
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# used by `Settings::Date::Order` and date-of-birth
|
|
84
|
-
# selects, never authored project-side. Skip
|
|
85
|
-
# `unknown-key` for these; downstream interpolation
|
|
86
|
-
# checks have nothing to validate without a leaf entry
|
|
87
|
-
# so they decline silently too.
|
|
62
|
+
# Key prefixes Rails / `rails-i18n` ship in every locale by default. Projects whose own locale
|
|
63
|
+
# files don't redeclare them still get them at runtime (via the `rails-i18n` gem's bundled locale
|
|
64
|
+
# catalogues). `t('date.order')` is the canonical Mastodon case — used by `Settings::Date::Order`
|
|
65
|
+
# and date-of-birth selects, never authored project-side. Skip `unknown-key` for these; downstream
|
|
66
|
+
# interpolation checks have nothing to validate without a leaf entry so they decline silently too.
|
|
88
67
|
RAILS_SHIPPED_KEY_PREFIXES = %w[
|
|
89
68
|
date.
|
|
90
69
|
time.
|
|
@@ -101,9 +80,8 @@ module Rigor
|
|
|
101
80
|
activerecord.errors.models.
|
|
102
81
|
].freeze
|
|
103
82
|
|
|
104
|
-
# One translation-call observation. Carries no path/location —
|
|
105
|
-
#
|
|
106
|
-
# `Plugin::Base#diagnostic`.
|
|
83
|
+
# One translation-call observation. Carries no path/location — the caller (the `node_rule` block)
|
|
84
|
+
# positions it via `Plugin::Base#diagnostic`.
|
|
107
85
|
Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
|
|
108
86
|
|
|
109
87
|
module_function
|
|
@@ -118,12 +96,10 @@ module Rigor
|
|
|
118
96
|
# @param locale_index [LocaleIndex]
|
|
119
97
|
# @param configured_locales [Array<String>]
|
|
120
98
|
# @return [Array<Diagnostic>]
|
|
121
|
-
# The translation violations for a single call node, or `[]`
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
# the node-rule `NodeContext`, and `controller_scope` from the
|
|
126
|
-
# file path.
|
|
99
|
+
# The translation violations for a single call node, or `[]` when it is not a recognised `t` /
|
|
100
|
+
# `translate` call with a literal key. ADR-37: the engine owns the walk; `action` (the enclosing
|
|
101
|
+
# method, for lazy `t('.key')` expansion) comes from the node-rule `NodeContext`, and
|
|
102
|
+
# `controller_scope` from the file path.
|
|
127
103
|
#
|
|
128
104
|
# @param call_node [Prism::Node]
|
|
129
105
|
# @param locale_index [LocaleIndex]
|
|
@@ -143,11 +119,11 @@ module Rigor
|
|
|
143
119
|
options = options_hash(call_node)
|
|
144
120
|
entry = locale_index.find(literal_key)
|
|
145
121
|
if entry.nil?
|
|
146
|
-
# CLDR pluralization namespace: `t('accounts.posts', count: n)`
|
|
147
|
-
#
|
|
122
|
+
# CLDR pluralization namespace: `t('accounts.posts', count: n)` resolves through a `.one` /
|
|
123
|
+
# `.other` child — not missing.
|
|
148
124
|
return [] if locale_index.pluralization_namespace?(literal_key)
|
|
149
|
-
# Rails / rails-i18n ship `date.order`, `time.am`, etc. in
|
|
150
|
-
#
|
|
125
|
+
# Rails / rails-i18n ship `date.order`, `time.am`, etc. in every locale at runtime even when
|
|
126
|
+
# project files omit them.
|
|
151
127
|
return [] if rails_shipped_key?(literal_key)
|
|
152
128
|
|
|
153
129
|
return [unknown_key_violation(literal_key, locale_index)]
|
|
@@ -162,9 +138,8 @@ module Rigor
|
|
|
162
138
|
violations
|
|
163
139
|
end
|
|
164
140
|
|
|
165
|
-
# Derives the Rails controller scope from the file path,
|
|
166
|
-
#
|
|
167
|
-
# Returns nil for non-controller paths.
|
|
141
|
+
# Derives the Rails controller scope from the file path, e.g.
|
|
142
|
+
# `app/controllers/admin/users_controller.rb` → `admin.users`. Returns nil for non-controller paths.
|
|
168
143
|
def controller_scope_from_path(path)
|
|
169
144
|
m = CONTROLLER_PATH_RE.match(path.to_s)
|
|
170
145
|
return nil unless m
|
|
@@ -198,12 +173,10 @@ module Rigor
|
|
|
198
173
|
violations
|
|
199
174
|
end
|
|
200
175
|
|
|
201
|
-
# Expands a lazy key (starting with `.`) to its full
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
# or without an enclosing action — these are silently
|
|
206
|
-
# skipped to avoid false positives.
|
|
176
|
+
# Expands a lazy key (starting with `.`) to its full dotted path using the controller scope and
|
|
177
|
+
# action name. Returns the raw key unchanged for absolute keys. Returns nil for lazy keys outside a
|
|
178
|
+
# controller context or without an enclosing action — these are silently skipped to avoid false
|
|
179
|
+
# positives.
|
|
207
180
|
def expand_key(raw_key, controller_scope:, action:)
|
|
208
181
|
return raw_key unless raw_key.start_with?(".")
|
|
209
182
|
return nil unless controller_scope && action
|
|
@@ -219,9 +192,8 @@ module Rigor
|
|
|
219
192
|
I18N_RECEIVER_NAMES.include?(receiver_name)
|
|
220
193
|
end
|
|
221
194
|
|
|
222
|
-
# Extracts the literal-string first argument when
|
|
223
|
-
#
|
|
224
|
-
# only literal keys are statically validated.
|
|
195
|
+
# Extracts the literal-string first argument when present. Returns nil for variable / expression
|
|
196
|
+
# keys — only literal keys are statically validated.
|
|
225
197
|
def literal_key_for(call_node)
|
|
226
198
|
args = call_node.arguments&.arguments || []
|
|
227
199
|
return nil if args.empty?
|
|
@@ -232,11 +204,9 @@ module Rigor
|
|
|
232
204
|
first.unescaped
|
|
233
205
|
end
|
|
234
206
|
|
|
235
|
-
# Pulls the interpolation hash from the call's
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
# options (`default:`, `scope:`, …) and interpolation
|
|
239
|
-
# variables. Returns:
|
|
207
|
+
# Pulls the interpolation hash from the call's arguments. The trailing `Hash` argument (or
|
|
208
|
+
# `Prism::KeywordHashNode`) carries both reserved I18n options (`default:`, `scope:`, …) and
|
|
209
|
+
# interpolation variables. Returns:
|
|
240
210
|
# {
|
|
241
211
|
# :has_default => bool,
|
|
242
212
|
# :all_keys => Set<Symbol> (every assoc key in the hash),
|
|
@@ -244,15 +214,11 @@ module Rigor
|
|
|
244
214
|
# :hash_node => Prism::Node (or nil)
|
|
245
215
|
# }
|
|
246
216
|
#
|
|
247
|
-
# Note: a reserved option key (e.g. `count:`) can
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
# `%{count}` placeholder) and the
|
|
253
|
-
# extra-placeholder set against `non_reserved` (so
|
|
254
|
-
# `default:` / `scope:` are never reported as
|
|
255
|
-
# extra interpolation arguments).
|
|
217
|
+
# Note: a reserved option key (e.g. `count:`) can also serve as an interpolation value when the
|
|
218
|
+
# locale's leaf string has `%{count}`. The analyzer therefore checks the missing-placeholder set
|
|
219
|
+
# against `all_keys` (so `count:` satisfies a `%{count}` placeholder) and the extra-placeholder set
|
|
220
|
+
# against `non_reserved` (so `default:` / `scope:` are never reported as extra interpolation
|
|
221
|
+
# arguments).
|
|
256
222
|
def options_hash(call_node)
|
|
257
223
|
args = call_node.arguments&.arguments || []
|
|
258
224
|
last = args.last
|
|
@@ -275,8 +241,8 @@ module Rigor
|
|
|
275
241
|
end
|
|
276
242
|
|
|
277
243
|
def collect_assoc_keys(hash_node)
|
|
278
|
-
# Both `Prism::HashNode` and `Prism::KeywordHashNode`
|
|
279
|
-
#
|
|
244
|
+
# Both `Prism::HashNode` and `Prism::KeywordHashNode` expose `#elements`, so a single path
|
|
245
|
+
# handles both.
|
|
280
246
|
hash_node.elements.filter_map do |element|
|
|
281
247
|
next nil unless element.is_a?(Prism::AssocNode)
|
|
282
248
|
|