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
data/lib/rigor/cli/command.rb
CHANGED
|
@@ -4,16 +4,12 @@ module Rigor
|
|
|
4
4
|
class CLI
|
|
5
5
|
# Base class for the `rigor <subcommand>` command objects.
|
|
6
6
|
#
|
|
7
|
-
# Every subcommand captured the same invariant wiring — the argument
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# Centralising it here gives one consistent shape and lets a test
|
|
12
|
-
# instantiate a command with just `argv:` (the streams default so a
|
|
13
|
-
# spec can pass a `StringIO` for one and ignore the other).
|
|
7
|
+
# Every subcommand captured the same invariant wiring — the argument vector plus the output and error streams — in
|
|
8
|
+
# an identical `initialize(argv:, out:, err:)`, and defaulted the streams inconsistently (some to `$stdout` /
|
|
9
|
+
# `$stderr`, most not at all). Centralising it here gives one consistent shape and lets a test instantiate a command
|
|
10
|
+
# with just `argv:` (the streams default so a spec can pass a `StringIO` for one and ignore the other).
|
|
14
11
|
#
|
|
15
|
-
# Subclasses read the `@argv` / `@out` / `@err` ivars directly, as
|
|
16
|
-
# they did before.
|
|
12
|
+
# Subclasses read the `@argv` / `@out` / `@err` ivars directly, as they did before.
|
|
17
13
|
class Command
|
|
18
14
|
def initialize(argv:, out: $stdout, err: $stderr)
|
|
19
15
|
@argv = argv
|
|
@@ -23,11 +19,9 @@ module Rigor
|
|
|
23
19
|
|
|
24
20
|
private
|
|
25
21
|
|
|
26
|
-
# Expands `args` (a mix of files and directories) into a unique
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# `@err` — on the first arg that is neither. Shared by the
|
|
30
|
-
# path-walking commands (`type-scan`, `coverage`).
|
|
22
|
+
# Expands `args` (a mix of files and directories) into a unique list of `.rb` paths, recursing into directories.
|
|
23
|
+
# Returns nil — after writing `<command_name>: not a file or directory: <arg>` to `@err` — on the first arg that
|
|
24
|
+
# is neither. Shared by the path-walking commands (`type-scan`, `coverage`).
|
|
31
25
|
def collect_paths(args, command_name:)
|
|
32
26
|
paths = []
|
|
33
27
|
args.each do |arg|
|
|
@@ -25,18 +25,19 @@ require_relative "mutation_protection_renderer"
|
|
|
25
25
|
require_relative "fused_protection_report"
|
|
26
26
|
require_relative "fused_protection_renderer"
|
|
27
27
|
require_relative "coverage_mutation"
|
|
28
|
+
require_relative "protection_fork_scan"
|
|
29
|
+
require_relative "check_runner_factory"
|
|
28
30
|
require_relative "command"
|
|
29
31
|
|
|
30
32
|
module Rigor
|
|
31
33
|
class CLI
|
|
32
34
|
# Executes the `rigor coverage` command.
|
|
33
35
|
#
|
|
34
|
-
# Walks every Prism node in one or more files, infers its type via
|
|
35
|
-
#
|
|
36
|
-
# (constant / nominal / shaped / refined / bot / dynamic_specific /
|
|
36
|
+
# Walks every Prism node in one or more files, infers its type via `Rigor::Scope#type_of`, and classifies the result
|
|
37
|
+
# into precision tiers (constant / nominal / shaped / refined / bot / dynamic_specific /
|
|
37
38
|
# dynamic_top / top). Reports aggregate and per-file statistics so
|
|
38
|
-
# maintainers can track type-precision trends and SKILL pipelines can
|
|
39
|
-
#
|
|
39
|
+
# maintainers can track type-precision trends and SKILL pipelines can measure the impact of adding new constant-fold
|
|
40
|
+
# or shape-dispatch rules.
|
|
40
41
|
#
|
|
41
42
|
# Exit codes:
|
|
42
43
|
# 0 — scan complete, precision ratio ≥ threshold (or no threshold given)
|
|
@@ -47,8 +48,8 @@ module Rigor
|
|
|
47
48
|
|
|
48
49
|
USAGE = "Usage: rigor coverage [options] PATH..."
|
|
49
50
|
|
|
50
|
-
# ADR-70 — the default test runner hook for `--with-tests`. The
|
|
51
|
-
#
|
|
51
|
+
# ADR-70 — the default test runner hook for `--with-tests`. The conventional Ruby test task; override with
|
|
52
|
+
# `--test-command`.
|
|
52
53
|
DEFAULT_TEST_COMMAND = %w[bundle exec rake].freeze
|
|
53
54
|
|
|
54
55
|
# @return [Integer] CLI exit status.
|
|
@@ -59,23 +60,32 @@ module Rigor
|
|
|
59
60
|
return include_dynamic_misuse_error if options[:include_dynamic] && !options[:with_tests]
|
|
60
61
|
return run_mutation_protection(options) if options[:mutation]
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
configuration = Configuration.load(options.fetch(:config))
|
|
64
|
+
paths = resolve_paths(configuration)
|
|
63
65
|
return CLI::EXIT_USAGE if paths.nil?
|
|
64
66
|
return usage_error if paths.empty?
|
|
65
67
|
|
|
66
|
-
return run_protection(paths, options) if options[:protection]
|
|
68
|
+
return run_protection(paths, options, configuration) if options[:protection]
|
|
67
69
|
|
|
68
|
-
report = scan_paths(paths,
|
|
70
|
+
report = scan_paths(paths, configuration)
|
|
69
71
|
CoverageRenderer.new(out: @out).render(report, format: options.fetch(:format))
|
|
70
72
|
determine_exit(report, options)
|
|
71
73
|
end
|
|
72
74
|
|
|
73
75
|
private
|
|
74
76
|
|
|
77
|
+
# Like `rigor check`, fall back to the configured `paths:` when no path is given on the command line
|
|
78
|
+
# (`coverage` previously required an explicit path — operational friction the two commands should not
|
|
79
|
+
# differ on). The mutation mode keeps its own git-changed-files default and returns before this.
|
|
80
|
+
def resolve_paths(configuration)
|
|
81
|
+
args = @argv.empty? ? configuration.paths : @argv
|
|
82
|
+
collect_paths(args, command_name: "coverage")
|
|
83
|
+
end
|
|
84
|
+
|
|
75
85
|
def parse_options
|
|
76
86
|
options = { format: "text", threshold: nil, config: nil, protection: false, mutation: false,
|
|
77
87
|
with_tests: false, test_command: DEFAULT_TEST_COMMAND, include_dynamic: false,
|
|
78
|
-
limit: nil, seed: 1 }
|
|
88
|
+
limit: nil, seed: 1, workers: nil }
|
|
79
89
|
OptionParser.new { |opts| define_options(opts, options) }.parse!(@argv)
|
|
80
90
|
options
|
|
81
91
|
end
|
|
@@ -88,6 +98,10 @@ module Rigor
|
|
|
88
98
|
options[:protection] = true
|
|
89
99
|
end
|
|
90
100
|
define_mutation_options(opts, options)
|
|
101
|
+
opts.on("--workers=N", Integer, "With --protection: fork N workers over the scanned files " \
|
|
102
|
+
"(default: config parallel.workers / RIGOR_RACTOR_WORKERS / 0)") do |v|
|
|
103
|
+
options[:workers] = v
|
|
104
|
+
end
|
|
91
105
|
opts.on("--threshold=RATIO", Float, "Exit 1 when the precision (or, with --protection, " \
|
|
92
106
|
"protection/effectiveness) ratio is below RATIO (0.0–1.0)") do |v|
|
|
93
107
|
options[:threshold] = v
|
|
@@ -136,26 +150,41 @@ module Rigor
|
|
|
136
150
|
CLI::EXIT_USAGE
|
|
137
151
|
end
|
|
138
152
|
|
|
139
|
-
def run_protection(paths, options)
|
|
140
|
-
report = scan_protection(paths, options)
|
|
153
|
+
def run_protection(paths, options, configuration)
|
|
154
|
+
report = scan_protection(paths, options, configuration)
|
|
141
155
|
ProtectionRenderer.new(out: @out).render(report, format: options.fetch(:format))
|
|
142
156
|
determine_protection_exit(report, options)
|
|
143
157
|
end
|
|
144
158
|
|
|
145
|
-
|
|
146
|
-
|
|
159
|
+
# Builds the plugin-aware environment and the seeded scope ONCE on the parent, then fork-pools the
|
|
160
|
+
# per-file scan across the resolved worker count (P3-10). The scan is pure-read and each file is
|
|
161
|
+
# independent, so this is a fork-map + ordered reduce: {ProtectionForkScan} returns `{path => result}`
|
|
162
|
+
# and the parent absorbs in `paths` order so the report is byte-identical to a sequential run.
|
|
163
|
+
def scan_protection(paths, options, configuration)
|
|
147
164
|
environment = plugin_aware_environment(configuration)
|
|
148
|
-
|
|
165
|
+
workers = CheckRunnerFactory.resolve_workers(options, configuration)
|
|
166
|
+
scope = scope_with_inferred_params(paths, configuration, environment, workers)
|
|
149
167
|
scanner = Inference::ProtectionScanner.new(scope: scope)
|
|
150
168
|
accumulator = ProtectionAccumulator.new
|
|
151
169
|
|
|
152
|
-
|
|
170
|
+
results = ProtectionForkScan.run(
|
|
171
|
+
paths: paths, scanner: scanner, environment: environment,
|
|
172
|
+
configuration: configuration, workers: workers
|
|
173
|
+
)
|
|
174
|
+
paths.each { |path| absorb_protection_result(accumulator, path, results.fetch(path)) }
|
|
153
175
|
accumulator.to_report
|
|
154
176
|
end
|
|
155
177
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
178
|
+
def absorb_protection_result(accumulator, path, result)
|
|
179
|
+
if result.is_a?(ProtectionForkScan::ParseError)
|
|
180
|
+
accumulator.record_parse_error_count(path, result.count)
|
|
181
|
+
else
|
|
182
|
+
accumulator.absorb(path, result)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Seed the protection scan's scope with the same cross-file facts `rigor check` resolves against, so a receiver
|
|
187
|
+
# reads the type it actually has rather than a stripped-scope `Dynamic`:
|
|
159
188
|
#
|
|
160
189
|
# - `discovered_classes` — a project constant referring to a class
|
|
161
190
|
# defined in a *sibling* file (`Account`, `User`) types as
|
|
@@ -168,9 +197,9 @@ module Rigor
|
|
|
168
197
|
# parameter) counts as protected when its call sites resolve to
|
|
169
198
|
# concrete argument types.
|
|
170
199
|
#
|
|
171
|
-
# Both span the scanned `paths` only (no whole-project pre-pass) —
|
|
172
|
-
#
|
|
173
|
-
def scope_with_inferred_params(paths, configuration, environment)
|
|
200
|
+
# Both span the scanned `paths` only (no whole-project pre-pass) — a site that gains neither is classified exactly
|
|
201
|
+
# as before.
|
|
202
|
+
def scope_with_inferred_params(paths, configuration, environment, workers)
|
|
174
203
|
base = Scope.empty(environment: environment)
|
|
175
204
|
seed = {}
|
|
176
205
|
|
|
@@ -178,7 +207,7 @@ module Rigor
|
|
|
178
207
|
seed[:discovered_classes] = discovered unless discovered.empty?
|
|
179
208
|
|
|
180
209
|
table = Inference::ParameterInferenceCollector.collect(
|
|
181
|
-
files: paths, environment: environment, target_ruby: configuration.target_ruby
|
|
210
|
+
files: paths, environment: environment, target_ruby: configuration.target_ruby, workers: workers
|
|
182
211
|
)
|
|
183
212
|
seed[:param_inferred_types] = table unless table.empty?
|
|
184
213
|
|
|
@@ -202,36 +231,21 @@ module Rigor
|
|
|
202
231
|
CLI::EXIT_USAGE
|
|
203
232
|
end
|
|
204
233
|
|
|
205
|
-
def scan_paths(paths,
|
|
206
|
-
CoverageScan.precision_report(files: paths, configuration:
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# Delegated to the shared scan module (see {CoverageScan}); the
|
|
210
|
-
# protection path below reuses both, and `rigor check --coverage`
|
|
211
|
-
# reuses `precision_report` over the same machinery.
|
|
212
|
-
def project_environment(configuration)
|
|
213
|
-
CoverageScan.project_environment(configuration)
|
|
234
|
+
def scan_paths(paths, configuration)
|
|
235
|
+
CoverageScan.precision_report(files: paths, configuration: configuration)
|
|
214
236
|
end
|
|
215
237
|
|
|
216
|
-
# The protection scan must see the same receiver types `rigor check`
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
# undercount of what Rigor actually types on a plugin-using project.
|
|
224
|
-
# `ProjectContext` builds the plugin-aware environment (registry
|
|
225
|
-
# materialised + the per-run prepare pass that primes producers like
|
|
226
|
-
# the controller / model index) exactly as the LSP and the runner do.
|
|
238
|
+
# The protection scan must see the same receiver types `rigor check` does — including plugin-contributed
|
|
239
|
+
# `dynamic_return` types (a controller's `params` → `ActionController::Parameters`, a `Model.where` →
|
|
240
|
+
# `ActiveRecord::Relation[Model]`). The bare `CoverageScan.project_environment` carries only the RBS environment
|
|
241
|
+
# (no plugin registry), so every plugin-typed receiver reads `Dynamic` and its dispatch site is miscounted as
|
|
242
|
+
# *unprotected* — a systematic undercount of what Rigor actually types on a plugin-using project.
|
|
243
|
+
# `ProjectContext` builds the plugin-aware environment (registry materialised + the per-run prepare pass that
|
|
244
|
+
# primes producers like the controller / model index) exactly as the LSP and the runner do.
|
|
227
245
|
def plugin_aware_environment(configuration)
|
|
228
246
|
LanguageServer::ProjectContext.new(configuration: configuration).environment
|
|
229
247
|
end
|
|
230
248
|
|
|
231
|
-
def scan_one(path, scanner, accumulator, configuration)
|
|
232
|
-
CoverageScan.scan_into(path, scanner, accumulator, configuration)
|
|
233
|
-
end
|
|
234
|
-
|
|
235
249
|
def determine_exit(report, options)
|
|
236
250
|
return 1 unless report.parse_errors.empty?
|
|
237
251
|
|
|
@@ -5,19 +5,16 @@ require "prism"
|
|
|
5
5
|
|
|
6
6
|
module Rigor
|
|
7
7
|
class CLI
|
|
8
|
-
# ADR-63 Tier 2 + ADR-70 — the mutation-effectiveness and fused static∪dynamic
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# and the Protection + LanguageServer collaborators the command requires).
|
|
8
|
+
# ADR-63 Tier 2 + ADR-70 — the mutation-effectiveness and fused static∪dynamic protection paths, factored out of
|
|
9
|
+
# {CoverageCommand} to keep that command focused on dispatch. Mixed in, so each method runs in the command instance
|
|
10
|
+
# (using `@out` / `@err` / `@argv` / `collect_paths` / `determine_protection_exit` and the Protection +
|
|
11
|
+
# LanguageServer collaborators the command requires).
|
|
13
12
|
module CoverageMutation
|
|
14
13
|
private
|
|
15
14
|
|
|
16
|
-
# ADR-63 Tier 2 — the mutation-effectiveness deep dive. Builds the RBS
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# git-changed `.rb` files; explicit paths override (and enable the
|
|
20
|
-
# whole-project opt-in, which is minutes).
|
|
15
|
+
# ADR-63 Tier 2 — the mutation-effectiveness deep dive. Builds the RBS environment + project pre-pass once (the
|
|
16
|
+
# warm loop), then re-analyses each target file's mutants against its clean baseline. Defaults to the git-changed
|
|
17
|
+
# `.rb` files; explicit paths override (and enable the whole-project opt-in, which is minutes).
|
|
21
18
|
def run_mutation_protection(options)
|
|
22
19
|
explicit = collect_paths(@argv, command_name: "coverage")
|
|
23
20
|
return CLI::EXIT_USAGE if explicit.nil?
|
|
@@ -36,8 +33,8 @@ module Rigor
|
|
|
36
33
|
determine_protection_exit(report, options)
|
|
37
34
|
end
|
|
38
35
|
|
|
39
|
-
# A `--limit` sample makes the report an estimate (per-file ratios over a
|
|
40
|
-
#
|
|
36
|
+
# A `--limit` sample makes the report an estimate (per-file ratios over a random N of the mutations). Say so on
|
|
37
|
+
# stderr — stdout stays clean for JSON.
|
|
41
38
|
def note_sampling(options)
|
|
42
39
|
return unless options[:limit]
|
|
43
40
|
|
|
@@ -47,10 +44,9 @@ module Rigor
|
|
|
47
44
|
)
|
|
48
45
|
end
|
|
49
46
|
|
|
50
|
-
# ADR-70 — the fused static∪dynamic deep dive. The type pass is the ADR-63
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
# "a mutant survived" is meaningless — abort with a clear message if not.
|
|
47
|
+
# ADR-70 — the fused static∪dynamic deep dive. The type pass is the ADR-63 Tier 2 warm loop; each type-survivor is
|
|
48
|
+
# then run against the project's test suite (the runner hook). The suite MUST pass on clean code first, or "a
|
|
49
|
+
# mutant survived" is meaningless — abort with a clear message if not.
|
|
54
50
|
def run_fused_protection(paths, options)
|
|
55
51
|
configuration = Configuration.load(options.fetch(:config))
|
|
56
52
|
test_oracle = Protection::TestSuiteOracle.new(command: options.fetch(:test_command))
|
|
@@ -117,9 +113,8 @@ module Rigor
|
|
|
117
113
|
accumulator.absorb(scanner.scan_file(path, source: source))
|
|
118
114
|
end
|
|
119
115
|
|
|
120
|
-
# The git-changed (modified / added / untracked) `.rb` files that exist on
|
|
121
|
-
#
|
|
122
|
-
# when git is unavailable; the caller then reports "nothing to measure".
|
|
116
|
+
# The git-changed (modified / added / untracked) `.rb` files that exist on disk — the default Tier 2 scope.
|
|
117
|
+
# Returns [] outside a git work tree or when git is unavailable; the caller then reports "nothing to measure".
|
|
123
118
|
def changed_ruby_files
|
|
124
119
|
output = git_status_porcelain
|
|
125
120
|
return [] if output.nil?
|
|
@@ -11,7 +11,6 @@ module Rigor
|
|
|
11
11
|
:per_file,
|
|
12
12
|
:total
|
|
13
13
|
)
|
|
14
|
-
# Sum of all per-file totals.
|
|
15
14
|
def grand_total
|
|
16
15
|
total.total
|
|
17
16
|
end
|
|
@@ -44,7 +43,6 @@ module Rigor
|
|
|
44
43
|
def initialize
|
|
45
44
|
@per_file = []
|
|
46
45
|
@parse_errors = []
|
|
47
|
-
# Accumulated totals across all files.
|
|
48
46
|
@total_total = 0
|
|
49
47
|
@total_tier_counts = Inference::PrecisionScanner::TIERS.to_h { |t| [t, 0] }
|
|
50
48
|
end
|
|
@@ -10,12 +10,10 @@ require_relative "coverage_report"
|
|
|
10
10
|
|
|
11
11
|
module Rigor
|
|
12
12
|
class CLI
|
|
13
|
-
# Shared type-precision scan behind both `rigor coverage` (the
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# `CoverageReport`. Extracted so the two surfaces stay byte-identical
|
|
18
|
-
# on the same file set.
|
|
13
|
+
# Shared type-precision scan behind both `rigor coverage` (the dedicated command) and `rigor check --coverage` (the
|
|
14
|
+
# in-run coverage block). Walks each file's AST, types every expression via `Scope#type_of`, and accumulates the
|
|
15
|
+
# precision-tier breakdown into a `CoverageReport`. Extracted so the two surfaces stay byte-identical on the same
|
|
16
|
+
# file set.
|
|
19
17
|
module CoverageScan
|
|
20
18
|
module_function
|
|
21
19
|
|
|
@@ -37,11 +35,9 @@ module Rigor
|
|
|
37
35
|
)
|
|
38
36
|
end
|
|
39
37
|
|
|
40
|
-
# Parses one file and feeds the scan result (or a parse-error
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
# `ProtectionScanner` + `ProtectionAccumulator` — both of which
|
|
44
|
-
# respond to `scan(node)` and `absorb(path, result)`.
|
|
38
|
+
# Parses one file and feeds the scan result (or a parse-error record) into `accumulator`. `scanner` /
|
|
39
|
+
# `accumulator` are a matched pair — a `PrecisionScanner` + `CoverageAccumulator`, or a `ProtectionScanner` +
|
|
40
|
+
# `ProtectionAccumulator` — both of which respond to `scan(node)` and `absorb(path, result)`.
|
|
45
41
|
def scan_into(path, scanner, accumulator, configuration)
|
|
46
42
|
source = File.read(path)
|
|
47
43
|
parse_result = Prism.parse(source, filepath: path, version: configuration.target_ruby)
|
|
@@ -6,11 +6,9 @@ require_relative "../version"
|
|
|
6
6
|
|
|
7
7
|
module Rigor
|
|
8
8
|
class CLI
|
|
9
|
-
# CI-native diagnostic output formats (ADR-51). Each renders an
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# job log. They read the same `Diagnostic` fields as `--format json`
|
|
13
|
-
# (path / line / column / severity / qualified rule / message) and add
|
|
9
|
+
# CI-native diagnostic output formats (ADR-51). Each renders an `Analysis::Result` to a string a CI platform
|
|
10
|
+
# consumes to surface diagnostics inline in a pull / merge request, rather than only in the job log. They read the
|
|
11
|
+
# same `Diagnostic` fields as `--format json` (path / line / column / severity / qualified rule / message) and add
|
|
14
12
|
# no new information — only a platform-native rendering of it.
|
|
15
13
|
#
|
|
16
14
|
# sarif — SARIF 2.1.0 (cross-platform; GitHub code-scanning, any
|
|
@@ -23,11 +21,9 @@ module Rigor
|
|
|
23
21
|
# reviewdog (`-f=checkstyle`) and Jenkins/etc. consume
|
|
24
22
|
# junit — JUnit XML, the test-report format many CI systems render
|
|
25
23
|
#
|
|
26
|
-
# Severity maps once per format from Rigor's `:error` / `:warning` /
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# ruleless producers (parse / internal errors), which each format
|
|
30
|
-
# degrades gracefully.
|
|
24
|
+
# Severity maps once per format from Rigor's `:error` / `:warning` / `:info`; the qualified rule
|
|
25
|
+
# (`<source_family>.<rule>` or the bare rule for the builtin family) is the stable identifier, nil only for the
|
|
26
|
+
# ruleless producers (parse / internal errors), which each format degrades gracefully.
|
|
31
27
|
module DiagnosticFormats
|
|
32
28
|
FORMATS = %w[sarif github gitlab checkstyle junit teamcity].freeze
|
|
33
29
|
|
|
@@ -37,8 +33,9 @@ module Rigor
|
|
|
37
33
|
FORMATS.include?(format)
|
|
38
34
|
end
|
|
39
35
|
|
|
40
|
-
# Renders `result` in the named CI format. Callers gate on
|
|
41
|
-
# {
|
|
36
|
+
# Renders `result` in the named CI format. Callers gate on {.supports?} first, so an unrecognised format means
|
|
37
|
+
# {FORMATS} and the dispatch below have drifted apart — raise rather than return nil, which every caller would
|
|
38
|
+
# then have to guard.
|
|
42
39
|
def render(result, format)
|
|
43
40
|
case format
|
|
44
41
|
when "sarif" then Sarif.new(result).render
|
|
@@ -47,12 +44,12 @@ module Rigor
|
|
|
47
44
|
when "checkstyle" then Checkstyle.new(result).render
|
|
48
45
|
when "junit" then Junit.new(result).render
|
|
49
46
|
when "teamcity" then Teamcity.new(result).render
|
|
47
|
+
else raise ArgumentError, "unsupported format: #{format}"
|
|
50
48
|
end
|
|
51
49
|
end
|
|
52
50
|
|
|
53
|
-
# XML attribute / text escaping shared by the Checkstyle and JUnit
|
|
54
|
-
#
|
|
55
|
-
# message carrying `<`, `&`, or a quote can't break the document.
|
|
51
|
+
# XML attribute / text escaping shared by the Checkstyle and JUnit formatters. Covers the five predefined XML
|
|
52
|
+
# entities so a diagnostic message carrying `<`, `&`, or a quote can't break the document.
|
|
56
53
|
module XmlEscaping
|
|
57
54
|
ENTITIES = { "&" => "&", "<" => "<", ">" => ">",
|
|
58
55
|
'"' => """, "'" => "'" }.freeze
|
|
@@ -62,15 +59,14 @@ module Rigor
|
|
|
62
59
|
end
|
|
63
60
|
end
|
|
64
61
|
|
|
65
|
-
# SARIF 2.1.0 — the OASIS static-analysis interchange format. GitHub's
|
|
66
|
-
#
|
|
67
|
-
# diff and in the Security tab; it is the cross-platform anchor format.
|
|
62
|
+
# SARIF 2.1.0 — the OASIS static-analysis interchange format. GitHub's `codeql-action/upload-sarif` ingests it to
|
|
63
|
+
# render findings on the PR diff and in the Security tab; it is the cross-platform anchor format.
|
|
68
64
|
class Sarif
|
|
69
65
|
SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json"
|
|
70
66
|
INFORMATION_URI = "https://github.com/rigortype/rigor"
|
|
71
67
|
|
|
72
|
-
# SARIF defines exactly three result levels; Rigor's `:info` is a
|
|
73
|
-
#
|
|
68
|
+
# SARIF defines exactly three result levels; Rigor's `:info` is a `note` (the SARIF spelling for advisory
|
|
69
|
+
# findings).
|
|
74
70
|
LEVELS = { error: "error", warning: "warning", info: "note" }.freeze
|
|
75
71
|
|
|
76
72
|
def initialize(result)
|
|
@@ -103,9 +99,8 @@ module Rigor
|
|
|
103
99
|
}
|
|
104
100
|
end
|
|
105
101
|
|
|
106
|
-
# The distinct rule ids seen in this run, declared so consumers can
|
|
107
|
-
#
|
|
108
|
-
# SARIF rule object; richer per-rule metadata is a later enrichment.
|
|
102
|
+
# The distinct rule ids seen in this run, declared so consumers can cross-reference each result's `ruleId`.
|
|
103
|
+
# Id-only is a valid minimal SARIF rule object; richer per-rule metadata is a later enrichment.
|
|
109
104
|
def rules
|
|
110
105
|
@result.diagnostics.filter_map(&:qualified_rule).uniq.map { |id| { "id" => id } }
|
|
111
106
|
end
|
|
@@ -121,9 +116,8 @@ module Rigor
|
|
|
121
116
|
entry
|
|
122
117
|
end
|
|
123
118
|
|
|
124
|
-
# Rigor lines / columns are already 1-based, matching SARIF's
|
|
125
|
-
#
|
|
126
|
-
# SARIF URIs use forward slashes on every platform.
|
|
119
|
+
# Rigor lines / columns are already 1-based, matching SARIF's 1-based `startLine` / `startColumn`. Paths are
|
|
120
|
+
# project-relative; SARIF URIs use forward slashes on every platform.
|
|
127
121
|
def location_for(diagnostic)
|
|
128
122
|
{
|
|
129
123
|
"physicalLocation" => {
|
|
@@ -134,9 +128,8 @@ module Rigor
|
|
|
134
128
|
end
|
|
135
129
|
end
|
|
136
130
|
|
|
137
|
-
# GitHub Actions workflow commands — `::<level> file=…,line=…,col=…,
|
|
138
|
-
#
|
|
139
|
-
# into inline PR annotations, with no separate upload step.
|
|
131
|
+
# GitHub Actions workflow commands — `::<level> file=…,line=…,col=…, title=…::<message>` lines the runner parses
|
|
132
|
+
# out of stdout and turns into inline PR annotations, with no separate upload step.
|
|
140
133
|
class GithubActions
|
|
141
134
|
# GitHub's annotation levels; Rigor's `:info` is a `notice`.
|
|
142
135
|
LEVELS = { error: "error", warning: "warning", info: "notice" }.freeze
|
|
@@ -160,26 +153,24 @@ module Rigor
|
|
|
160
153
|
"::#{level} #{props.join(',')}::#{escape_data(diagnostic.message)}"
|
|
161
154
|
end
|
|
162
155
|
|
|
163
|
-
# GitHub's documented workflow-command escaping: `%` and the CR / LF
|
|
164
|
-
#
|
|
156
|
+
# GitHub's documented workflow-command escaping: `%` and the CR / LF that would otherwise terminate the command
|
|
157
|
+
# line, for message data.
|
|
165
158
|
def escape_data(value)
|
|
166
159
|
value.to_s.gsub("%", "%25").gsub("\r", "%0D").gsub("\n", "%0A")
|
|
167
160
|
end
|
|
168
161
|
|
|
169
|
-
# Property values additionally escape the `,` (property separator)
|
|
170
|
-
#
|
|
162
|
+
# Property values additionally escape the `,` (property separator) and `:` (command terminator) so a path or
|
|
163
|
+
# rule id can carry them.
|
|
171
164
|
def escape_property(value)
|
|
172
165
|
escape_data(value).gsub(",", "%2C").gsub(":", "%3A")
|
|
173
166
|
end
|
|
174
167
|
end
|
|
175
168
|
|
|
176
|
-
# GitLab Code Quality report — the CodeClimate-subset JSON array GitLab
|
|
177
|
-
#
|
|
178
|
-
# Code Quality widget.
|
|
169
|
+
# GitLab Code Quality report — the CodeClimate-subset JSON array GitLab reads from a `codequality` CI artifact to
|
|
170
|
+
# populate the merge-request Code Quality widget.
|
|
179
171
|
class GitlabCodeQuality
|
|
180
|
-
# GitLab's severity vocabulary (a CodeClimate subset). Rigor maps
|
|
181
|
-
#
|
|
182
|
-
# are left for a louder future tier.
|
|
172
|
+
# GitLab's severity vocabulary (a CodeClimate subset). Rigor maps error → major, warning → minor, info → info;
|
|
173
|
+
# `critical` / `blocker` are left for a louder future tier.
|
|
183
174
|
SEVERITIES = { error: "major", warning: "minor", info: "info" }.freeze
|
|
184
175
|
|
|
185
176
|
def initialize(result)
|
|
@@ -205,17 +196,16 @@ module Rigor
|
|
|
205
196
|
}
|
|
206
197
|
end
|
|
207
198
|
|
|
208
|
-
# The rule id is folded into the description (the widget shows it)
|
|
209
|
-
#
|
|
199
|
+
# The rule id is folded into the description (the widget shows it) because Code Quality has no dedicated rule
|
|
200
|
+
# field.
|
|
210
201
|
def description(diagnostic)
|
|
211
202
|
rule_id = diagnostic.qualified_rule
|
|
212
203
|
rule_id ? "#{diagnostic.message} [#{rule_id}]" : diagnostic.message
|
|
213
204
|
end
|
|
214
205
|
|
|
215
|
-
# GitLab dedups findings by fingerprint and tracks them across runs
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
# message) satisfies both — order-independent, no run-volatile input.
|
|
206
|
+
# GitLab dedups findings by fingerprint and tracks them across runs by it, so it must be stable for an unchanged
|
|
207
|
+
# finding and unique per finding. Hashing the locating tuple (path, rule, line, column, message) satisfies both
|
|
208
|
+
# — order-independent, no run-volatile input.
|
|
219
209
|
def fingerprint(diagnostic)
|
|
220
210
|
payload = [diagnostic.path, diagnostic.qualified_rule, diagnostic.line,
|
|
221
211
|
diagnostic.column, diagnostic.message].join("")
|
|
@@ -223,12 +213,10 @@ module Rigor
|
|
|
223
213
|
end
|
|
224
214
|
end
|
|
225
215
|
|
|
226
|
-
# Checkstyle XML — the lint-interchange format a wide range of tools
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
# are
|
|
230
|
-
# code reviewdog surfaces). Checkstyle's native severities are
|
|
231
|
-
# `error` / `warning` / `info`, so Rigor's map through unchanged.
|
|
216
|
+
# Checkstyle XML — the lint-interchange format a wide range of tools read, most usefully reviewdog
|
|
217
|
+
# (`-f=checkstyle`), which then posts to any of its reporters (GitHub PR review, GitLab MR, Gerrit, …). Errors are
|
|
218
|
+
# grouped by file; the qualified rule rides in `source` (the rule code reviewdog surfaces). Checkstyle's native
|
|
219
|
+
# severities are `error` / `warning` / `info`, so Rigor's map through unchanged.
|
|
232
220
|
class Checkstyle
|
|
233
221
|
include XmlEscaping
|
|
234
222
|
|
|
@@ -258,12 +246,10 @@ module Rigor
|
|
|
258
246
|
end
|
|
259
247
|
end
|
|
260
248
|
|
|
261
|
-
# JUnit XML — the test-report format GitHub's test reporting, GitLab,
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
#
|
|
265
|
-
# are visible in the report. The exit code (errors only) remains the
|
|
266
|
-
# gate; this view is for surfacing, not gating.
|
|
249
|
+
# JUnit XML — the test-report format GitHub's test reporting, GitLab, Jenkins, and CircleCI render. Following the
|
|
250
|
+
# established linter convention (rubocop / eslint / PHPStan): every diagnostic is a `testcase` carrying a
|
|
251
|
+
# `failure` typed by its severity, so all of them are visible in the report. The exit code (errors only) remains
|
|
252
|
+
# the gate; this view is for surfacing, not gating.
|
|
267
253
|
class Junit
|
|
268
254
|
include XmlEscaping
|
|
269
255
|
|
|
@@ -299,11 +285,9 @@ module Rigor
|
|
|
299
285
|
end
|
|
300
286
|
end
|
|
301
287
|
|
|
302
|
-
# TeamCity inspection service messages — the `##teamcity[…]` lines a
|
|
303
|
-
#
|
|
304
|
-
#
|
|
305
|
-
# CI-detection auto-emits. One `inspectionType` declares the category;
|
|
306
|
-
# each diagnostic is an `inspection` typed by severity.
|
|
288
|
+
# TeamCity inspection service messages — the `##teamcity[…]` lines a TeamCity build agent parses out of the build
|
|
289
|
+
# log into its Inspections view. The one stdout-native format (besides `github`) that CI-detection auto-emits. One
|
|
290
|
+
# `inspectionType` declares the category; each diagnostic is an `inspection` typed by severity.
|
|
307
291
|
class Teamcity
|
|
308
292
|
SEVERITIES = { error: "ERROR", warning: "WARNING", info: "INFO" }.freeze
|
|
309
293
|
|
|
@@ -7,10 +7,8 @@ require "optionparser"
|
|
|
7
7
|
|
|
8
8
|
module Rigor
|
|
9
9
|
class CLI
|
|
10
|
-
# Executes `rigor diff <baseline.json> [paths...]`. Compares
|
|
11
|
-
# the
|
|
12
|
-
# baseline JSON (the output of a previous `rigor check
|
|
13
|
-
# --format=json` run) and prints the delta:
|
|
10
|
+
# Executes `rigor diff <baseline.json> [paths...]`. Compares the current `rigor check` diagnostics against a saved
|
|
11
|
+
# baseline JSON (the output of a previous `rigor check --format=json` run) and prints the delta:
|
|
14
12
|
#
|
|
15
13
|
# - **new** — diagnostics in the current run that were not
|
|
16
14
|
# in the baseline (typically a regression introduced in
|
|
@@ -18,19 +16,13 @@ module Rigor
|
|
|
18
16
|
# - **fixed** — diagnostics in the baseline that no longer
|
|
19
17
|
# appear in the current run (typically progress).
|
|
20
18
|
#
|
|
21
|
-
# Identity for matching is the tuple
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# one fixed + one new pair, which lines up with the user's
|
|
25
|
-
# mental model of "you changed the line, the analyzer's
|
|
26
|
-
# position changed too."
|
|
19
|
+
# Identity for matching is the tuple `(path, line, column, rule, source_family, message)`. An edit that moves a
|
|
20
|
+
# diagnostic to a new line surfaces as one fixed + one new pair, which lines up with the user's mental model of "you
|
|
21
|
+
# changed the line, the analyzer's position changed too."
|
|
27
22
|
#
|
|
28
|
-
# CI usage: commit a `rigor.baseline.json` produced once
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# is `1` when any new diagnostic appears, `0` otherwise —
|
|
32
|
-
# so adding new errors fails CI but legacy errors recorded
|
|
33
|
-
# in the baseline don't.
|
|
23
|
+
# CI usage: commit a `rigor.baseline.json` produced once with `rigor check --format=json > rigor.baseline.json`,
|
|
24
|
+
# then run `rigor diff rigor.baseline.json` in CI. Exit code is `1` when any new diagnostic appears, `0` otherwise —
|
|
25
|
+
# so adding new errors fails CI but legacy errors recorded in the baseline don't.
|
|
34
26
|
class DiffCommand < Command
|
|
35
27
|
USAGE = "Usage: rigor diff [options] <baseline.json> [paths...]"
|
|
36
28
|
|
|
@@ -77,10 +69,8 @@ module Rigor
|
|
|
77
69
|
options
|
|
78
70
|
end
|
|
79
71
|
|
|
80
|
-
# Runs `rigor check` against the remaining argv (or the
|
|
81
|
-
#
|
|
82
|
-
# Reuses the analyzer + configuration plumbing the
|
|
83
|
-
# check-command path uses.
|
|
72
|
+
# Runs `rigor check` against the remaining argv (or the configured paths) and returns the diagnostics array.
|
|
73
|
+
# Reuses the analyzer + configuration plumbing the check-command path uses.
|
|
84
74
|
def run_current(options)
|
|
85
75
|
require_relative "../analysis/runner"
|
|
86
76
|
require_relative "../configuration"
|