rigortype 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/docs/manual/02-cli-reference.md +2 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/08-skills.md +21 -0
- data/docs/manual/15-type-protection-coverage.md +6 -4
- 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 +96 -151
- 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 +45 -20
- 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 +43 -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_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +27 -17
- 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 +70 -36
- 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/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +433 -503
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +176 -291
- 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 +774 -1152
- 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/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 +509 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
- 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 -817
- data/lib/rigor/inference/statement_evaluator.rb +706 -1115
- 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 +208 -309
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +15 -15
- 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 +141 -173
- 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 +118 -176
- 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 +107 -83
- 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 +30 -47
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
- 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.rb +148 -236
- 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-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 +20 -36
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +23 -30
- 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/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
- 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 +182 -328
- 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/scope.rbs +6 -0
- data/skills/rigor-ask/SKILL.md +21 -1
- data/skills/rigor-baseline-reduce/SKILL.md +16 -0
- data/skills/rigor-ci-setup/SKILL.md +96 -249
- data/skills/rigor-doctor/SKILL.md +39 -49
- data/skills/rigor-doctor/references/01-checks.md +52 -0
- data/skills/rigor-editor-setup/SKILL.md +14 -0
- data/skills/rigor-mcp-setup/SKILL.md +14 -0
- data/skills/rigor-monkeypatch-resolve/SKILL.md +15 -0
- data/skills/rigor-plugin-author/SKILL.md +16 -0
- data/skills/rigor-plugin-review/SKILL.md +174 -0
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +214 -0
- data/skills/rigor-plugin-tune/SKILL.md +21 -2
- data/skills/rigor-project-init/SKILL.md +16 -0
- data/skills/rigor-protection-uplift/SKILL.md +15 -0
- data/skills/rigor-rbs-setup/SKILL.md +15 -0
- data/skills/rigor-upgrade/SKILL.md +16 -0
- metadata +8 -4
data/lib/rigor/sig_gen/writer.rb
CHANGED
|
@@ -8,61 +8,47 @@ require_relative "write_result"
|
|
|
8
8
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module SigGen
|
|
11
|
-
# Applies a per-source-file group of {MethodCandidate}s to
|
|
12
|
-
#
|
|
11
|
+
# Applies a per-source-file group of {MethodCandidate}s to the target `.rbs` file under the project
|
|
12
|
+
# signature tree.
|
|
13
13
|
#
|
|
14
|
-
# ADR-14 slice 2: the writer parses the target with
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# closing `end` keyword. Existing declarations are NEVER
|
|
18
|
-
# touched unless `--overwrite` is set AND the candidate's
|
|
19
|
-
# classification is `tighter-return`.
|
|
14
|
+
# ADR-14 slice 2: the writer parses the target with `RBS::Parser` to find the matching class declaration and
|
|
15
|
+
# inserts new method declarations just before the class's closing `end` keyword. Existing declarations are
|
|
16
|
+
# NEVER touched unless `--overwrite` is set AND the candidate's classification is `tighter-return`.
|
|
20
17
|
#
|
|
21
|
-
# The slice does NOT re-render the whole file through
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# the *same* class declaration may still lose trailing
|
|
27
|
-
# blank lines on the touched ranges; the `--diff` review
|
|
28
|
-
# surface from slice 1 is the user's escape hatch.
|
|
18
|
+
# The slice does NOT re-render the whole file through `RBS::Writer`. That would lose comments / blank-line
|
|
19
|
+
# formatting per upstream design; the byte-range insertion approach taken here preserves untouched
|
|
20
|
+
# declarations verbatim. Mixed hand-written + generated output inside the *same* class declaration may still
|
|
21
|
+
# lose trailing blank lines on the touched ranges; the `--diff` review surface from slice 1 is the user's
|
|
22
|
+
# escape hatch.
|
|
29
23
|
#
|
|
30
|
-
# Safety boundary: the writer ASSERTS the target lives
|
|
31
|
-
#
|
|
32
|
-
# disk. Files outside that tree route through
|
|
33
|
-
# `WriteResult(action: :skipped_outside_sig_root)`; the
|
|
24
|
+
# Safety boundary: the writer ASSERTS the target lives inside the configured signature tree before touching
|
|
25
|
+
# the disk. Files outside that tree route through `WriteResult(action: :skipped_outside_sig_root)`; the
|
|
34
26
|
# caller decides whether to warn or fail.
|
|
35
27
|
class Writer # rubocop:disable Metrics/ClassLength
|
|
36
28
|
INDENT = " "
|
|
37
29
|
private_constant :INDENT
|
|
38
30
|
|
|
39
|
-
# Per-`update_existing` accumulator. The merge_class
|
|
40
|
-
#
|
|
41
|
-
# `skipped` in place as each class is processed so the
|
|
42
|
-
# next class sees the latest byte positions.
|
|
31
|
+
# Per-`update_existing` accumulator. The merge_class helper mutates `source` / `decls` / `applied` /
|
|
32
|
+
# `skipped` in place as each class is processed so the next class sees the latest byte positions.
|
|
43
33
|
MergeState = Struct.new(:source, :decls, :applied, :skipped, keyword_init: true)
|
|
44
34
|
private_constant :MergeState
|
|
45
35
|
|
|
46
36
|
def initialize(path_mapper:, overwrite: false)
|
|
47
37
|
@path_mapper = path_mapper
|
|
48
38
|
@overwrite = overwrite
|
|
49
|
-
# Run-level (cross-file) namespace-kind view, populated
|
|
50
|
-
#
|
|
51
|
-
# Empty until then so the single-target `#write` path
|
|
52
|
-
# falls back to per-candidate kinds only.
|
|
39
|
+
# Run-level (cross-file) namespace-kind view, populated per `#write_all` from every candidate's per-file
|
|
40
|
+
# map. Empty until then so the single-target `#write` path falls back to per-candidate kinds only.
|
|
53
41
|
@global_namespace_kinds = {}
|
|
42
|
+
# Run-level qualified-class-name => superclass-token view, same lifecycle as `@global_namespace_kinds`.
|
|
43
|
+
@global_superclasses = {}
|
|
54
44
|
end
|
|
55
45
|
|
|
56
|
-
# Process the full candidate list by resolving each
|
|
57
|
-
#
|
|
58
|
-
# may route consolidated-layout classes to existing
|
|
59
|
-
# files) and grouping candidates that share a target
|
|
46
|
+
# Process the full candidate list by resolving each candidate's target sig file via the path mapper (which
|
|
47
|
+
# may route consolidated-layout classes to existing files) and grouping candidates that share a target
|
|
60
48
|
# before writing.
|
|
61
49
|
#
|
|
62
|
-
# ADR-14 follow-up: this is the consolidated-layout
|
|
63
|
-
#
|
|
64
|
-
# below assumes all candidates share a target and
|
|
65
|
-
# remains for spec convenience.
|
|
50
|
+
# ADR-14 follow-up: this is the consolidated-layout entry point. The legacy `write(source_path,
|
|
51
|
+
# candidates)` below assumes all candidates share a target and remains for spec convenience.
|
|
66
52
|
#
|
|
67
53
|
# @param candidates [Array<MethodCandidate>]
|
|
68
54
|
# @return [Array<WriteResult>] one per target sig file.
|
|
@@ -71,6 +57,7 @@ module Rigor
|
|
|
71
57
|
return [] if emittable.empty?
|
|
72
58
|
|
|
73
59
|
@global_namespace_kinds = build_namespace_kinds(candidates)
|
|
60
|
+
@global_superclasses = build_superclasses(candidates)
|
|
74
61
|
emittable.group_by { |c| @path_mapper.target_for(c.path, class_name: c.class_name) }
|
|
75
62
|
.map { |target, group| write_target(target, group) }
|
|
76
63
|
end
|
|
@@ -91,10 +78,8 @@ module Rigor
|
|
|
91
78
|
|
|
92
79
|
private
|
|
93
80
|
|
|
94
|
-
# Shared per-target write path used by both `#write` and
|
|
95
|
-
#
|
|
96
|
-
# the {WriteResult} when multiple candidates merge into
|
|
97
|
-
# one target.
|
|
81
|
+
# Shared per-target write path used by both `#write` and `#write_all`. Picks a representative
|
|
82
|
+
# `source_path` for the {WriteResult} when multiple candidates merge into one target.
|
|
98
83
|
def write_target(target, candidates, source_path: nil)
|
|
99
84
|
source_path ||= candidates.first&.path
|
|
100
85
|
unless inside_sig_root?(target)
|
|
@@ -112,8 +97,7 @@ module Rigor
|
|
|
112
97
|
root = @path_mapper.sig_root_dir.realpath
|
|
113
98
|
target.expand_path.ascend.any? { |ancestor| realpath_or_nil(ancestor) == root }
|
|
114
99
|
rescue Errno::ENOENT
|
|
115
|
-
# The sig root doesn't exist yet; we'll create it
|
|
116
|
-
# alongside the target file. Allow this case.
|
|
100
|
+
# The sig root doesn't exist yet; we'll create it alongside the target file. Allow this case.
|
|
117
101
|
target.expand_path.to_s.start_with?(@path_mapper.sig_root_dir.expand_path.to_s)
|
|
118
102
|
end
|
|
119
103
|
|
|
@@ -130,35 +114,26 @@ module Rigor
|
|
|
130
114
|
action: :created, applied: candidates)
|
|
131
115
|
end
|
|
132
116
|
|
|
133
|
-
# ADR-14 gap-#3 follow-up (c): when one candidate's
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
# earlier `group_by(&:class_name)` flattened that into
|
|
140
|
-
# two top-level wraps (one for GemResolver's own
|
|
141
|
-
# methods, one for GemResolver::Resolved's), which
|
|
142
|
-
# Steep accepted but is not the canonical layout in
|
|
143
|
-
# this project's `sig/`.
|
|
117
|
+
# ADR-14 gap-#3 follow-up (c): when one candidate's `class_name` is a strict prefix of another's, emit a
|
|
118
|
+
# single nested tree instead of two flat sibling blocks. The third-round self-dogfood surfaced
|
|
119
|
+
# `Analysis::DependencySourceInference::GemResolver` containing `class Resolved < Data.define(...)` — the
|
|
120
|
+
# earlier `group_by(&:class_name)` flattened that into two top-level wraps (one for GemResolver's own
|
|
121
|
+
# methods, one for GemResolver::Resolved's), which Steep accepted but is not the canonical layout in this
|
|
122
|
+
# project's `sig/`.
|
|
144
123
|
#
|
|
145
|
-
# The writer now builds a tree keyed by qualified-name
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
# block. Empty class shells (gap-#3 follow-up (e), e.g.
|
|
150
|
-
# `Unresolvable = Data.define(...)`) participate as
|
|
151
|
-
# zero-method tree nodes — they emit `class Foo\nend`
|
|
152
|
-
# at their position.
|
|
124
|
+
# The writer now builds a tree keyed by qualified-name segments. Each tree node carries (qualified_name,
|
|
125
|
+
# methods, shells, children); rendering walks the tree so every nested class appears inside its parent's
|
|
126
|
+
# block. Empty class shells (gap-#3 follow-up (e), e.g. `Unresolvable = Data.define(...)`) participate as
|
|
127
|
+
# zero-method tree nodes — they emit `class Foo\nend` at their position.
|
|
153
128
|
def render_new_file(candidates)
|
|
154
129
|
shells = collect_class_shells(candidates)
|
|
155
130
|
tree = build_namespace_tree(candidates, shells)
|
|
156
131
|
kinds = merged_namespace_kinds(candidates)
|
|
157
|
-
|
|
132
|
+
supers = merged_superclasses(candidates)
|
|
133
|
+
render_tree_nodes(tree, kinds, supers, 0)
|
|
158
134
|
end
|
|
159
135
|
|
|
160
|
-
# Drains `class_shells` from every candidate; the
|
|
161
|
-
# generator's walker populates the same set on every
|
|
136
|
+
# Drains `class_shells` from every candidate; the generator's walker populates the same set on every
|
|
162
137
|
# candidate produced from a given file (gap-#3 (e)).
|
|
163
138
|
def collect_class_shells(candidates)
|
|
164
139
|
shells = Set.new
|
|
@@ -174,30 +149,44 @@ module Rigor
|
|
|
174
149
|
merged
|
|
175
150
|
end
|
|
176
151
|
|
|
177
|
-
# Folds every candidate's per-file namespace-kind map
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
# `foo
|
|
182
|
-
#
|
|
183
|
-
# no kind for it. Without this view that sibling target
|
|
184
|
-
# wraps the nested class in `module Foo` while `foo.rbs`
|
|
185
|
-
# declares `class Foo`; loading both raises
|
|
186
|
-
# `RBS::DuplicatedDeclarationError`, aborting the whole
|
|
187
|
-
# RBS env build.
|
|
152
|
+
# Folds every candidate's per-file namespace-kind map into one run-level view so a `class Foo` recorded
|
|
153
|
+
# while scanning `foo.rb` governs the wrapper keyword emitted for `Foo` in a *sibling* file's target — e.g.
|
|
154
|
+
# `foo/bar.rb` declaring `class Foo::Bar`, whose compact constant path never names `Foo`, so the walker
|
|
155
|
+
# records no kind for it. Without this view that sibling target wraps the nested class in `module Foo`
|
|
156
|
+
# while `foo.rbs` declares `class Foo`; loading both raises `RBS::DuplicatedDeclarationError`, aborting the
|
|
157
|
+
# whole RBS env build.
|
|
188
158
|
def build_namespace_kinds(candidates)
|
|
189
159
|
candidates.each_with_object({}) do |candidate, acc|
|
|
190
160
|
(candidate.namespace_kinds || {}).each { |name, kind| apply_namespace_kind(acc, name, kind) }
|
|
191
161
|
end
|
|
192
162
|
end
|
|
193
163
|
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
164
|
+
# Superclass twins of {#merged_namespace_kinds} / {#build_namespace_kinds}: fold every candidate's
|
|
165
|
+
# per-file `class_superclasses` map into one view keyed by qualified class name. Only plain-constant
|
|
166
|
+
# superclasses are ever recorded (the generator skips computed ones), so a missing key means "emit no
|
|
167
|
+
# superclass", never "unknown".
|
|
168
|
+
def merged_superclasses(candidates)
|
|
169
|
+
merged = @global_superclasses.dup
|
|
170
|
+
candidates.each do |c|
|
|
171
|
+
next unless c.respond_to?(:class_superclasses)
|
|
172
|
+
|
|
173
|
+
(c.class_superclasses || {}).each { |name, sup| merged[name] = sup }
|
|
174
|
+
end
|
|
175
|
+
merged
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def build_superclasses(candidates)
|
|
179
|
+
candidates.each_with_object({}) do |candidate, acc|
|
|
180
|
+
next unless candidate.respond_to?(:class_superclasses)
|
|
181
|
+
|
|
182
|
+
(candidate.class_superclasses || {}).each { |name, sup| acc[name] = sup }
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# A `class` declaration is authoritative and MUST win over the `:module` wrapper default: a compact
|
|
187
|
+
# `class Foo::Bar` never names `Foo`, so the only signal for `Foo`'s kind is an actual `class Foo` (or
|
|
188
|
+
# `Const = Data.define(...)` shell) seen elsewhere. This guarantees the generated tree never mixes
|
|
189
|
+
# `class` / `module` for the same constant.
|
|
201
190
|
def apply_namespace_kind(map, key, kind)
|
|
202
191
|
if kind == :class
|
|
203
192
|
map[key] = :class
|
|
@@ -206,12 +195,9 @@ module Rigor
|
|
|
206
195
|
end
|
|
207
196
|
end
|
|
208
197
|
|
|
209
|
-
# Tree node: { name:, children: Hash{String => node},
|
|
210
|
-
# methods
|
|
211
|
-
#
|
|
212
|
-
# only (no methods of their own); rendering uses it to
|
|
213
|
-
# default the keyword to `:class` for the `class Const`
|
|
214
|
-
# = `Data.define(...)` case.
|
|
198
|
+
# Tree node: { name:, children: Hash{String => node}, methods: Array<MethodCandidate>, shell: Boolean }.
|
|
199
|
+
# `shell` flags nodes that came in via `class_shells` only (no methods of their own); rendering uses it to
|
|
200
|
+
# default the keyword to `:class` for the `class Const` = `Data.define(...)` case.
|
|
215
201
|
def build_namespace_tree(candidates, shells)
|
|
216
202
|
root = { name: nil, children: {}, methods: [], shell: false }
|
|
217
203
|
candidates.group_by(&:class_name).each do |class_name, methods|
|
|
@@ -234,35 +220,44 @@ module Rigor
|
|
|
234
220
|
end
|
|
235
221
|
end
|
|
236
222
|
|
|
237
|
-
def render_tree_nodes(node, kinds, depth)
|
|
238
|
-
node[:children].values.map
|
|
223
|
+
def render_tree_nodes(node, kinds, supers, depth)
|
|
224
|
+
node[:children].values.map do |child|
|
|
225
|
+
render_tree_node(child, kinds, supers, depth, [node[:name]].compact)
|
|
226
|
+
end.join("\n")
|
|
239
227
|
end
|
|
240
228
|
|
|
241
|
-
def render_tree_node(node, kinds, depth, prefix)
|
|
229
|
+
def render_tree_node(node, kinds, supers, depth, prefix)
|
|
242
230
|
indent = INDENT * depth
|
|
243
231
|
qualified = (prefix + [node[:name]]).join("::")
|
|
244
232
|
keyword = node_keyword(node, kinds, qualified)
|
|
245
|
-
|
|
246
|
-
|
|
233
|
+
header = "#{keyword} #{node[:name]}#{superclass_suffix(keyword, supers, qualified)}"
|
|
234
|
+
body = render_tree_node_body(node, kinds, supers, depth, prefix)
|
|
235
|
+
"#{indent}#{header}\n#{body}#{indent}end\n"
|
|
247
236
|
end
|
|
248
237
|
|
|
249
|
-
def render_tree_node_body(node, kinds, depth, prefix)
|
|
238
|
+
def render_tree_node_body(node, kinds, supers, depth, prefix)
|
|
250
239
|
inner_indent = INDENT * (depth + 1)
|
|
251
240
|
method_lines = node[:methods].map { |c| "#{inner_indent}#{c.rbs}\n" }.join
|
|
252
241
|
child_blocks = node[:children].values.map do |child|
|
|
253
|
-
render_tree_node(child, kinds, depth + 1, prefix + [node[:name]])
|
|
242
|
+
render_tree_node(child, kinds, supers, depth + 1, prefix + [node[:name]])
|
|
254
243
|
end.join
|
|
255
244
|
method_lines + child_blocks
|
|
256
245
|
end
|
|
257
246
|
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
247
|
+
# ` < Super` for a `class` node whose qualified name has a recorded superclass, else the empty string. A
|
|
248
|
+
# `module` never takes a superclass (RBS forbids it), so the keyword gates emission — a name coincidentally
|
|
249
|
+
# recorded as both (impossible from one source class, but cheap to guard) stays a bare module.
|
|
250
|
+
def superclass_suffix(keyword, supers, qualified)
|
|
251
|
+
return "" unless keyword == :class
|
|
252
|
+
|
|
253
|
+
superclass = supers[qualified]
|
|
254
|
+
superclass ? " < #{superclass}" : ""
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Per ADR-14 gap-#3 (a) the keyword for a segment comes from `namespace_kinds` when known. The default for
|
|
258
|
+
# an explicit class shell (gap-#3 (e), `Const = Data.define(...)`) is `:class`; otherwise default to
|
|
259
|
+
# `:class` for a method-bearing leaf node and `:module` for an intermediate (children-only) segment.
|
|
260
|
+
# Defaulting intermediates to `:module` matches RBS's "multiple `module Foo` declarations merge" rule.
|
|
266
261
|
def node_keyword(node, kinds, qualified)
|
|
267
262
|
return kinds.fetch(qualified) if kinds.key?(qualified)
|
|
268
263
|
return :class if node[:shell]
|
|
@@ -277,7 +272,8 @@ module Rigor
|
|
|
277
272
|
return WriteResult.new(source_path: source_path, target_path: target, action: :noop) if decls.nil?
|
|
278
273
|
|
|
279
274
|
state = MergeState.new(source: source, decls: decls, applied: [], skipped: [])
|
|
280
|
-
|
|
275
|
+
supers = merged_superclasses(candidates)
|
|
276
|
+
candidates.group_by(&:class_name).each { |class_name, methods| merge_class(state, class_name, methods, supers) }
|
|
281
277
|
merge_class_shells(state, collect_class_shells(candidates), merged_namespace_kinds(candidates))
|
|
282
278
|
|
|
283
279
|
action = state.applied.empty? ? :noop : :updated
|
|
@@ -286,15 +282,11 @@ module Rigor
|
|
|
286
282
|
action: action, applied: state.applied, skipped: state.skipped)
|
|
287
283
|
end
|
|
288
284
|
|
|
289
|
-
# ADR-14 gap-#3 (e): for every requested class shell
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
293
|
-
#
|
|
294
|
-
# `applied` accumulator does NOT grow — shells are
|
|
295
|
-
# structural declarations, not methods, so the
|
|
296
|
-
# action-count surface (`updated +N`) keeps
|
|
297
|
-
# reflecting method changes only.
|
|
285
|
+
# ADR-14 gap-#3 (e): for every requested class shell that isn't already declared in the target file,
|
|
286
|
+
# insert an empty `class Const\nend` block inside the nearest existing ancestor. Shells already covered by
|
|
287
|
+
# an existing declaration are silently a no-op. The `applied` accumulator does NOT grow — shells are
|
|
288
|
+
# structural declarations, not methods, so the action-count surface (`updated +N`) keeps reflecting method
|
|
289
|
+
# changes only.
|
|
298
290
|
def merge_class_shells(state, shells, kinds)
|
|
299
291
|
shells.each do |qualified|
|
|
300
292
|
next if find_class_decl(state.decls, qualified)
|
|
@@ -325,11 +317,9 @@ module Rigor
|
|
|
325
317
|
[[], segments]
|
|
326
318
|
end
|
|
327
319
|
|
|
328
|
-
# Pulls the indent depth (in `INDENT` units) one level
|
|
329
|
-
#
|
|
330
|
-
#
|
|
331
|
-
# `class Foo; end`) so the keyword column is the
|
|
332
|
-
# robust signal.
|
|
320
|
+
# Pulls the indent depth (in `INDENT` units) one level deeper than the anchor decl's own column.
|
|
321
|
+
# Pre-existing members might be missing (an empty `class Foo; end`) so the keyword column is the robust
|
|
322
|
+
# signal.
|
|
333
323
|
def anchor_decl_indent_depth(decl)
|
|
334
324
|
decl_column = decl.location[:keyword].start_column
|
|
335
325
|
(decl_column / INDENT.size) + 1
|
|
@@ -367,22 +357,19 @@ module Rigor
|
|
|
367
357
|
nil
|
|
368
358
|
end
|
|
369
359
|
|
|
370
|
-
def merge_class(state, class_name, methods)
|
|
360
|
+
def merge_class(state, class_name, methods, supers = {})
|
|
371
361
|
decl = find_class_decl(state.decls, class_name)
|
|
372
362
|
state.source = if decl.nil?
|
|
373
|
-
append_new_class(state.source, class_name, methods, state.applied)
|
|
363
|
+
append_new_class(state.source, class_name, methods, state.applied, supers[class_name])
|
|
374
364
|
else
|
|
375
365
|
merge_into_existing_class(state.source, decl, methods, state.applied, state.skipped)
|
|
376
366
|
end
|
|
377
367
|
state.decls = parse_signature(state.source) || state.decls
|
|
378
368
|
end
|
|
379
369
|
|
|
380
|
-
# Walks the parsed decl tree recursively, tracking the
|
|
381
|
-
#
|
|
382
|
-
#
|
|
383
|
-
# `qualified_name`. Recursing into modules lets us
|
|
384
|
-
# match `Rigor::Type::Nominal` against the
|
|
385
|
-
# `class Nominal` declaration nested inside
|
|
370
|
+
# Walks the parsed decl tree recursively, tracking the enclosing module/class prefix, and returns the
|
|
371
|
+
# declaration whose fully-qualified name matches `qualified_name`. Recursing into modules lets us match
|
|
372
|
+
# `Rigor::Type::Nominal` against the `class Nominal` declaration nested inside
|
|
386
373
|
# `module Rigor; module Type; … end; end`.
|
|
387
374
|
def find_class_decl(decls, qualified_name)
|
|
388
375
|
find_class_decl_in(decls, [], qualified_name)
|
|
@@ -402,12 +389,12 @@ module Rigor
|
|
|
402
389
|
nil
|
|
403
390
|
end
|
|
404
391
|
|
|
405
|
-
# Appends an entirely new `class Foo … end` block at the
|
|
406
|
-
# end of the file (with a leading blank line as
|
|
392
|
+
# Appends an entirely new `class Foo … end` block at the end of the file (with a leading blank line as
|
|
407
393
|
# separator).
|
|
408
|
-
def append_new_class(source, class_name, methods, applied)
|
|
394
|
+
def append_new_class(source, class_name, methods, applied, superclass = nil)
|
|
409
395
|
body = methods.map { |c| "#{INDENT}#{c.rbs}" }.join("\n")
|
|
410
|
-
|
|
396
|
+
header = superclass ? "class #{class_name} < #{superclass}" : "class #{class_name}"
|
|
397
|
+
snippet = "\n#{header}\n#{body}\nend\n"
|
|
411
398
|
applied.concat(methods)
|
|
412
399
|
ends_with_newline?(source) ? source + snippet : "#{source}\n#{snippet}"
|
|
413
400
|
end
|
|
@@ -434,13 +421,10 @@ module Rigor
|
|
|
434
421
|
source
|
|
435
422
|
end
|
|
436
423
|
|
|
437
|
-
# Returns a list of `[method_name (Symbol), kind (Symbol)]`
|
|
438
|
-
#
|
|
439
|
-
#
|
|
440
|
-
#
|
|
441
|
-
# `attr_*` declaration kinds so a source-side
|
|
442
|
-
# `attr_reader :name` and an RBS-side `attr_reader name: T`
|
|
443
|
-
# are treated as the same member (i.e. user-authored).
|
|
424
|
+
# Returns a list of `[method_name (Symbol), kind (Symbol)]` pairs for every method-like member in the
|
|
425
|
+
# declaration. ADR-14 slice 4 recognises `MethodDefinition`'s `:instance` / `:singleton` kind plus the
|
|
426
|
+
# three `attr_*` declaration kinds so a source-side `attr_reader :name` and an RBS-side
|
|
427
|
+
# `attr_reader name: T` are treated as the same member (i.e. user-authored).
|
|
444
428
|
def collect_member_pairs(decl)
|
|
445
429
|
pairs = []
|
|
446
430
|
decl.members.each { |m| collect_pairs_for_member(m, pairs) }
|
|
@@ -465,10 +449,9 @@ module Rigor
|
|
|
465
449
|
methods.partition { |c| !existing_pairs.include?([c.method_name, c.kind]) }
|
|
466
450
|
end
|
|
467
451
|
|
|
468
|
-
# Inserts each new method line one column before the
|
|
469
|
-
#
|
|
470
|
-
#
|
|
471
|
-
# the surrounding source's whitespace stays intact.
|
|
452
|
+
# Inserts each new method line one column before the class declaration's `end` keyword. The insertion
|
|
453
|
+
# text carries its own leading indent + trailing newline so the surrounding source's whitespace stays
|
|
454
|
+
# intact.
|
|
472
455
|
def insert_into_class(source, decl, new_methods)
|
|
473
456
|
return source if new_methods.empty?
|
|
474
457
|
|
|
@@ -477,34 +460,25 @@ module Rigor
|
|
|
477
460
|
source[0...end_pos] + addition + source[end_pos..]
|
|
478
461
|
end
|
|
479
462
|
|
|
480
|
-
# Walks the class's existing method declarations; for
|
|
481
|
-
#
|
|
482
|
-
# name, slices out the old declaration's source range
|
|
483
|
-
# and substitutes the new RBS one-liner. Members that
|
|
463
|
+
# Walks the class's existing method declarations; for each replaceable candidate that matches a member
|
|
464
|
+
# name, slices out the old declaration's source range and substitutes the new RBS one-liner. Members that
|
|
484
465
|
# are not `MethodDefinition`s are left alone.
|
|
485
466
|
#
|
|
486
|
-
# Two candidate classifications are eligible for
|
|
487
|
-
# replacement under `--overwrite`:
|
|
467
|
+
# Two candidate classifications are eligible for replacement under `--overwrite`:
|
|
488
468
|
#
|
|
489
|
-
# 1. `TIGHTER_RETURN` — the classifier already proved the
|
|
490
|
-
#
|
|
491
|
-
#
|
|
492
|
-
#
|
|
493
|
-
# `
|
|
494
|
-
#
|
|
495
|
-
# bypasses the existing-RBS comparison and always
|
|
496
|
-
# classifies as `NEW_METHOD` — when sig-gen's
|
|
497
|
-
# `--params=observed` upgrades a `(path: untyped) -> void`
|
|
498
|
-
# declaration to `(path: String) -> void` we want
|
|
499
|
-
# `--overwrite` to apply it.
|
|
469
|
+
# 1. `TIGHTER_RETURN` — the classifier already proved the new return type is a strict subtype of the
|
|
470
|
+
# declared one (with lenience guards passed).
|
|
471
|
+
# 2. `NEW_METHOD` whose new RBS strictly tightens an `untyped` position in the existing declaration. The
|
|
472
|
+
# canonical case is `initialize_stub_candidate`, which bypasses the existing-RBS comparison and always
|
|
473
|
+
# classifies as `NEW_METHOD` — when sig-gen's `--params=observed` upgrades a `(path: untyped) -> void`
|
|
474
|
+
# declaration to `(path: String) -> void` we want `--overwrite` to apply it.
|
|
500
475
|
def replace_eligible_conflicts(source, decl, candidates)
|
|
501
476
|
eligible = candidates.select { |c| eligible_for_replacement?(c, decl, source) }
|
|
502
477
|
return [source, []] if eligible.empty?
|
|
503
478
|
|
|
504
479
|
replaced = []
|
|
505
|
-
# Apply replacements from highest byte position downward
|
|
506
|
-
#
|
|
507
|
-
# grows or shrinks.
|
|
480
|
+
# Apply replacements from highest byte position downward so earlier byte offsets remain valid as the
|
|
481
|
+
# source grows or shrinks.
|
|
508
482
|
sorted = eligible.sort_by { |c| -member_position(decl, c.method_name, c.kind) }
|
|
509
483
|
sorted.each do |candidate|
|
|
510
484
|
source = apply_replacement(source, decl, candidate) and replaced << candidate
|
|
@@ -520,14 +494,10 @@ module Rigor
|
|
|
520
494
|
end
|
|
521
495
|
end
|
|
522
496
|
|
|
523
|
-
# Compares the existing member's source-side RBS text
|
|
524
|
-
#
|
|
525
|
-
#
|
|
526
|
-
# `untyped`
|
|
527
|
-
# least one `untyped` slot becomes a concrete type AND
|
|
528
|
-
# no concrete slot becomes `untyped`. Word-boundary
|
|
529
|
-
# matching ensures we count `untyped` only as a type
|
|
530
|
-
# token, not as a substring inside identifiers.
|
|
497
|
+
# Compares the existing member's source-side RBS text against the candidate's proposed RBS text. Returns
|
|
498
|
+
# true when the new spelling has STRICTLY FEWER bare `untyped` tokens than the existing one — i.e. at
|
|
499
|
+
# least one `untyped` slot becomes a concrete type AND no concrete slot becomes `untyped`. Word-boundary
|
|
500
|
+
# matching ensures we count `untyped` only as a type token, not as a substring inside identifiers.
|
|
531
501
|
def tightens_untyped?(candidate, decl, source)
|
|
532
502
|
member = find_method_member(decl, candidate.method_name, candidate.kind)
|
|
533
503
|
return false if member.nil?
|
|
@@ -551,10 +521,8 @@ module Rigor
|
|
|
551
521
|
end
|
|
552
522
|
end
|
|
553
523
|
|
|
554
|
-
# Splices the new RBS one-liner over the existing
|
|
555
|
-
#
|
|
556
|
-
# starts at the `def` keyword, NOT at the column zero of
|
|
557
|
-
# the line, so the leading whitespace stays inside
|
|
524
|
+
# Splices the new RBS one-liner over the existing declaration's byte range. `RBS::Parser`'s location
|
|
525
|
+
# starts at the `def` keyword, NOT at the column zero of the line, so the leading whitespace stays inside
|
|
558
526
|
# `source[0...start_pos]` and we do not re-emit it.
|
|
559
527
|
def apply_replacement(source, decl, candidate)
|
|
560
528
|
member = find_method_member(decl, candidate.method_name, candidate.kind)
|
data/lib/rigor/sig_gen.rb
CHANGED
|
@@ -13,13 +13,10 @@ require_relative "sig_gen/write_result"
|
|
|
13
13
|
require_relative "sig_gen/writer"
|
|
14
14
|
|
|
15
15
|
module Rigor
|
|
16
|
-
# Namespace for the RBS signature generator that powers
|
|
17
|
-
# `rigor sig-gen` (ADR-14).
|
|
16
|
+
# Namespace for the RBS signature generator that powers `rigor sig-gen` (ADR-14).
|
|
18
17
|
#
|
|
19
|
-
# The generator emits RBS from Rigor's inference results so
|
|
20
|
-
#
|
|
21
|
-
# See `docs/adr/14-rbs-sig-generation.md` for the design
|
|
22
|
-
# rationale and the slicing plan.
|
|
18
|
+
# The generator emits RBS from Rigor's inference results so users close RBS coverage gaps without freehand
|
|
19
|
+
# authorship. See `docs/adr/14-rbs-sig-generation.md` for the design rationale and the slicing plan.
|
|
23
20
|
module SigGen
|
|
24
21
|
end
|
|
25
22
|
end
|
|
@@ -1,33 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
|
-
# Classifies each configured `signature_paths:` entry by what it
|
|
5
|
-
#
|
|
6
|
-
# when a configured path resolves to nothing.
|
|
4
|
+
# Classifies each configured `signature_paths:` entry by what it actually contributes to
|
|
5
|
+
# the RBS environment, so a caller can warn when a configured path resolves to nothing.
|
|
7
6
|
#
|
|
8
|
-
# The failure this guards against is silent. {Environment::RbsLoader}
|
|
9
|
-
# `
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# hundreds of plausible-looking false positives; surfacing the empty
|
|
17
|
-
# entry makes the real cause visible.
|
|
7
|
+
# The failure this guards against is silent. {Environment::RbsLoader} `add`s a
|
|
8
|
+
# `signature_paths:` entry only when `path.directory?`, and only the `.rbs` files under it
|
|
9
|
+
# carry signatures — so a typo'd or moved path (or a directory holding no `.rbs`) loads
|
|
10
|
+
# zero signatures with no trace on stderr or in the run summary. The downstream symptom is
|
|
11
|
+
# the most authoritative diagnostics: every call into the extensions the missing RBS was
|
|
12
|
+
# meant to describe fires `call.undefined-method` at `evidence_tier: high`. A one-character
|
|
13
|
+
# path typo can manufacture hundreds of plausible-looking false positives; surfacing the
|
|
14
|
+
# empty entry makes the real cause visible.
|
|
18
15
|
#
|
|
19
|
-
# The audit deliberately mirrors the loader's own acceptance test
|
|
20
|
-
#
|
|
21
|
-
#
|
|
16
|
+
# The audit deliberately mirrors the loader's own acceptance test (`path.directory?` + a
|
|
17
|
+
# recursive `**/*.rbs` glob) so a `:ok` verdict means the loader did load from it and a
|
|
18
|
+
# warning means it did not.
|
|
22
19
|
module SignaturePathAudit
|
|
23
20
|
# One configured `signature_paths:` entry's resolution status.
|
|
24
21
|
#
|
|
25
22
|
# `status` is one of:
|
|
26
23
|
# - `:ok` — a directory containing at least one `.rbs`.
|
|
27
24
|
# - `:missing` — the path does not exist.
|
|
28
|
-
# - `:not_directory` — the path exists but is not a directory (the
|
|
29
|
-
#
|
|
30
|
-
# is silently ignored).
|
|
25
|
+
# - `:not_directory` — the path exists but is not a directory (the loader only `add`s
|
|
26
|
+
# directories, so a `.rbs` file passed directly is silently ignored).
|
|
31
27
|
# - `:empty` — a directory with no `.rbs` file (recursive).
|
|
32
28
|
Entry = Data.define(:path, :status, :rbs_file_count) do
|
|
33
29
|
def ok?
|
|
@@ -38,9 +34,9 @@ module Rigor
|
|
|
38
34
|
!ok?
|
|
39
35
|
end
|
|
40
36
|
|
|
41
|
-
# One-line, human-facing reason. The wording matches the loader's
|
|
42
|
-
#
|
|
43
|
-
#
|
|
37
|
+
# One-line, human-facing reason. The wording matches the loader's actual behaviour
|
|
38
|
+
# ("loaded nothing from it") rather than the filesystem error, so the message points
|
|
39
|
+
# at the consequence.
|
|
44
40
|
def message
|
|
45
41
|
case status
|
|
46
42
|
when :missing
|
|
@@ -59,12 +55,11 @@ module Rigor
|
|
|
59
55
|
end
|
|
60
56
|
end
|
|
61
57
|
|
|
62
|
-
# Audits each configured entry. `signature_paths` is the
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
# a misconfiguration, so it is never audited.
|
|
58
|
+
# Audits each configured entry. `signature_paths` is the {Configuration#signature_paths}
|
|
59
|
+
# array (absolute paths, already resolved against the config file's directory). Pass
|
|
60
|
+
# `nil` — the unset default, where Rigor auto-detects `<root>/sig` — to get an empty
|
|
61
|
+
# result: an absent auto-detected `sig/` is a normal setup, not a misconfiguration, so it
|
|
62
|
+
# is never audited.
|
|
68
63
|
#
|
|
69
64
|
# @param signature_paths [Array<String, Pathname>, nil]
|
|
70
65
|
# @return [Array<Entry>]
|
|
@@ -72,8 +67,7 @@ module Rigor
|
|
|
72
67
|
Array(signature_paths).map { |path| classify(path.to_s) }
|
|
73
68
|
end
|
|
74
69
|
|
|
75
|
-
# The subset of {audit} that resolved to nothing — the entries worth
|
|
76
|
-
# warning about.
|
|
70
|
+
# The subset of {audit} that resolved to nothing — the entries worth warning about.
|
|
77
71
|
#
|
|
78
72
|
# @param signature_paths [Array<String, Pathname>, nil]
|
|
79
73
|
# @return [Array<Entry>]
|