rigortype 0.2.7 → 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 +1 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- 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 +19 -26
- 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 +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/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 +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 -1111
- 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 +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 +71 -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 +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 -235
- 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 +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/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 +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/scope.rbs +6 -0
- metadata +2 -1
|
@@ -9,13 +9,12 @@ require_relative "rails_routes/analyzer"
|
|
|
9
9
|
|
|
10
10
|
module Rigor
|
|
11
11
|
module Plugin
|
|
12
|
-
# rigor-rails-routes — validates Rails route-helper calls
|
|
13
|
-
#
|
|
14
|
-
# project's `config/routes.rb`.
|
|
12
|
+
# rigor-rails-routes — validates Rails route-helper calls (`users_path`, `edit_user_path(@user)`, …)
|
|
13
|
+
# against the project's `config/routes.rb`.
|
|
15
14
|
#
|
|
16
15
|
# Tier 1A of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
17
|
-
# Statically interprets the routes DSL via Prism — no
|
|
18
|
-
#
|
|
16
|
+
# Statically interprets the routes DSL via Prism — no `rails` runtime dependency. Recognised v0.1.0
|
|
17
|
+
# surface:
|
|
19
18
|
#
|
|
20
19
|
# - `Rails.application.routes.draw do ... end`
|
|
21
20
|
# - `resources :name [, only: [...] | except: [...]]`
|
|
@@ -25,10 +24,8 @@ module Rigor
|
|
|
25
24
|
# - One level of `namespace :foo do ... end`
|
|
26
25
|
# - One level of nested `resources`
|
|
27
26
|
#
|
|
28
|
-
# The plugin publishes its parsed `:helper_table` through
|
|
29
|
-
#
|
|
30
|
-
# Phase 4 consumes it for route-helper validation in
|
|
31
|
-
# controller code.
|
|
27
|
+
# The plugin publishes its parsed `:helper_table` through the ADR-9 cross-plugin fact store;
|
|
28
|
+
# `rigor-actionpack` Phase 4 consumes it for route-helper validation in controller code.
|
|
32
29
|
#
|
|
33
30
|
# ## Configuration
|
|
34
31
|
#
|
|
@@ -39,66 +36,47 @@ module Rigor
|
|
|
39
36
|
#
|
|
40
37
|
# ## Limitations (v0.1.0)
|
|
41
38
|
#
|
|
42
|
-
# - `scope :path:` / `scope :module:` / `scope :as:` are
|
|
43
|
-
# not interpreted — helpers nested inside these
|
|
39
|
+
# - `scope :path:` / `scope :module:` / `scope :as:` are not interpreted — helpers nested inside these
|
|
44
40
|
# constructs are silently skipped.
|
|
45
|
-
# - Constraints / format restrictions / mountable
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# `categories` ↔ `category`, `boxes` ↔ `box`. Custom
|
|
50
|
-
# inflections (`fish` ↔ `fish`, `child` ↔ `children`)
|
|
51
|
-
# are out of scope; users who need them ship a hand-
|
|
52
|
-
# written RBS for the affected helper.
|
|
41
|
+
# - Constraints / format restrictions / mountable engines are out of scope.
|
|
42
|
+
# - The English inflector is intentionally tiny: it handles `posts` ↔ `post`, `users` ↔ `user`,
|
|
43
|
+
# `categories` ↔ `category`, `boxes` ↔ `box`. Custom inflections (`fish` ↔ `fish`, `child` ↔
|
|
44
|
+
# `children`) are out of scope; users who need them ship a hand-written RBS for the affected helper.
|
|
53
45
|
class RailsRoutes < Rigor::Plugin::Base
|
|
54
46
|
manifest(
|
|
55
47
|
id: "rails-routes",
|
|
56
|
-
# Bumped 2026-05-28 — GitLab FOSS sweep adds: (a)
|
|
57
|
-
# `
|
|
58
|
-
#
|
|
59
|
-
# (
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# of iterative `direct(name.sub(FROM, TO)) do ... end`
|
|
63
|
-
# alias-generation idiom — generates `<TO>_*` aliases
|
|
64
|
-
# for every registered `<FROM>_*` helper. GitLab uses
|
|
65
|
-
# this to shorten `namespace_project_*` → `project_*`.
|
|
48
|
+
# Bumped 2026-05-28 — GitLab FOSS sweep adds: (a) `draw_all :name` support (action_dispatch-draw_all
|
|
49
|
+
# gem; single-file load semantics matching `draw :name`); (b) keyword-style `scope(path:
|
|
50
|
+
# ':project_id', as: :project)` — path read from the `:path` keyword, not only from the positional
|
|
51
|
+
# first arg; (c) detection of iterative `direct(name.sub(FROM, TO)) do ... end` alias-generation
|
|
52
|
+
# idiom — generates `<TO>_*` aliases for every registered `<FROM>_*` helper. GitLab uses this to
|
|
53
|
+
# shorten `namespace_project_*` → `project_*`.
|
|
66
54
|
version: "0.27.0",
|
|
67
55
|
description: "Validates Rails route-helper calls against `config/routes.rb`.",
|
|
68
56
|
config_schema: {
|
|
69
57
|
"routes_file" => { kind: :string, default: "config/routes.rb" },
|
|
70
|
-
# `helper_paths` — the directories `HelperDiscoverer` walks
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
# `app/
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
# (`SoftwareUpdateCheckService#api_url`), `app/serializers`,
|
|
79
|
-
# `app/presenters`, `app/decorators`, not only `app/helpers/`.
|
|
80
|
-
# Walking the whole tree is the honest answer to "does this
|
|
81
|
-
# `_path` / `_url` name exist anywhere in the project?"; the
|
|
82
|
-
# cost is a one-time Prism parse per file at startup, which is
|
|
83
|
-
# bounded.
|
|
58
|
+
# `helper_paths` — the directories `HelperDiscoverer` walks for project-defined `*_path` / `*_url`
|
|
59
|
+
# methods. Default to the whole `app/` tree — the suffix filter inside the discoverer keeps the
|
|
60
|
+
# registered set tight, and real-world Rails apps routinely keep URL builders under
|
|
61
|
+
# `app/controllers` (private `def page_url`, `def callback_url` shapes), `app/lib` (Mastodon's
|
|
62
|
+
# `TranslationService::DeepL#base_url`), `app/services` (`SoftwareUpdateCheckService#api_url`),
|
|
63
|
+
# `app/serializers`, `app/presenters`, `app/decorators`, not only `app/helpers/`. Walking the
|
|
64
|
+
# whole tree is the honest answer to "does this `_path` / `_url` name exist anywhere in the
|
|
65
|
+
# project?"; the cost is a one-time Prism parse per file at startup, which is bounded.
|
|
84
66
|
"helper_paths" => { kind: :array, default: ["app"] }
|
|
85
67
|
},
|
|
86
68
|
produces: [:helper_table]
|
|
87
69
|
)
|
|
88
70
|
|
|
89
|
-
# Cached producer — reads `config/routes.rb` through
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
# `FileEntry` digest invalidates the cache on routes-
|
|
93
|
-
# file edits.
|
|
71
|
+
# Cached producer — reads `config/routes.rb` through the trusted `IoBoundary` and parses through
|
|
72
|
+
# {RoutesParser}. The descriptor's auto-collected `FileEntry` digest invalidates the cache on
|
|
73
|
+
# routes-file edits.
|
|
94
74
|
#
|
|
95
|
-
# Passes a `file_reader` lambda so the parser can follow
|
|
96
|
-
# `
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
# via `draw`) are captured post-compute, but a brand-new helper
|
|
101
|
-
# file the prior run never read would otherwise read as fresh.
|
|
75
|
+
# Passes a `file_reader` lambda so the parser can follow `draw(:admin)` → `config/routes/admin.rb`
|
|
76
|
+
# partials. `watch:` (ADR-60 WD3) covers every `.rb` under `helper_paths:` so ADDING a helper file
|
|
77
|
+
# invalidates the table — the producer's own in-block reads (the routes file + the partials it
|
|
78
|
+
# follows via `draw`) are captured post-compute, but a brand-new helper file the prior run never read
|
|
79
|
+
# would otherwise read as fresh.
|
|
102
80
|
producer :helper_table, watch: -> { @helper_paths.map { |dir| [dir, "**/*.rb"] } } do |_params|
|
|
103
81
|
routes_dir = "#{File.dirname(@routes_file)}/routes"
|
|
104
82
|
file_reader = lambda do |name|
|
|
@@ -119,14 +97,11 @@ module Rigor
|
|
|
119
97
|
@load_error = nil
|
|
120
98
|
end
|
|
121
99
|
|
|
122
|
-
# Walks every configured `helper_paths:` directory
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
# the `:helper_table` cache automatically. Returns the
|
|
128
|
-
# empty set when nothing under `helper_paths:` exists —
|
|
129
|
-
# the routes table still works.
|
|
100
|
+
# Walks every configured `helper_paths:` directory through the trusted `IoBoundary` and returns the
|
|
101
|
+
# set of project-defined `*_path` / `*_url` method names for {HelperDiscoverer}. Each file digest is
|
|
102
|
+
# captured by the boundary so editing a helper file invalidates the `:helper_table` cache
|
|
103
|
+
# automatically. Returns the empty set when nothing under `helper_paths:` exists — the routes table
|
|
104
|
+
# still works.
|
|
130
105
|
def discover_custom_helpers
|
|
131
106
|
contents_per_path = {}
|
|
132
107
|
each_helper_file do |path|
|
|
@@ -146,8 +121,7 @@ module Rigor
|
|
|
146
121
|
end
|
|
147
122
|
end
|
|
148
123
|
|
|
149
|
-
# Publishes the parsed table to the cross-plugin fact
|
|
150
|
-
# store; `rigor-actionpack` Phase 4 reads it via
|
|
124
|
+
# Publishes the parsed table to the cross-plugin fact store; `rigor-actionpack` Phase 4 reads it via
|
|
151
125
|
# `services.fact_store.read`.
|
|
152
126
|
def prepare(services)
|
|
153
127
|
table = helper_table_or_nil
|
|
@@ -160,10 +134,9 @@ module Rigor
|
|
|
160
134
|
)
|
|
161
135
|
end
|
|
162
136
|
|
|
163
|
-
# File-level only: the once-per-run load-error emission. Per-call
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
# built once as the node-rule file context.
|
|
137
|
+
# File-level only: the once-per-run load-error emission. Per-call helper validation runs over the
|
|
138
|
+
# engine-owned walk via the node_rule below (ADR-37), with the same-file `shadowing` set built once
|
|
139
|
+
# as the node-rule file context.
|
|
167
140
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
168
141
|
table = helper_table_or_nil
|
|
169
142
|
if table.nil? && @load_error
|
|
@@ -191,31 +164,23 @@ module Rigor
|
|
|
191
164
|
|
|
192
165
|
private
|
|
193
166
|
|
|
194
|
-
# The load-error path used to emit the same warning on
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
# this multiplied a single root cause into 1,000+
|
|
199
|
-
# identical diagnostics. The error is project-global —
|
|
200
|
-
# report it once per run.
|
|
167
|
+
# The load-error path used to emit the same warning on every analyzed file in the project. On large
|
|
168
|
+
# monorepos (Mastodon: 1,302 files; Solidus: ~1,000 files) and on legacy projects without a top-level
|
|
169
|
+
# `config/routes.rb`, this multiplied a single root cause into 1,000+ identical diagnostics. The
|
|
170
|
+
# error is project-global — report it once per run.
|
|
201
171
|
|
|
202
172
|
def helper_table_or_nil
|
|
203
|
-
# Memoise the build *attempt*, not just a truthy result: when the
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
#
|
|
208
|
-
# a real project, re-parsing) `config/routes.rb` tens of thousands
|
|
209
|
-
# of times per run. The result — nil included — is stable for the
|
|
210
|
-
# run, so one attempt is correct.
|
|
173
|
+
# Memoise the build *attempt*, not just a truthy result: when the routes file is missing or
|
|
174
|
+
# unparseable the table is nil, and the old `return @helper_table if @helper_table` re-ran the
|
|
175
|
+
# whole read-and-parse on every call. The engine consults this per route-helper dispatch, so a nil
|
|
176
|
+
# table meant re-reading (and, on a real project, re-parsing) `config/routes.rb` tens of thousands
|
|
177
|
+
# of times per run. The result — nil included — is stable for the run, so one attempt is correct.
|
|
211
178
|
return @helper_table if @helper_table_built
|
|
212
179
|
|
|
213
180
|
@helper_table_built = true
|
|
214
|
-
# ADR-60 WD3 record-and-validate: the producer's in-block reads
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
# producer's `watch:` covers helper-file additions — so no
|
|
218
|
-
# priming read is needed here.
|
|
181
|
+
# ADR-60 WD3 record-and-validate: the producer's in-block reads (the routes file + the partials it
|
|
182
|
+
# follows) are captured into the dependency descriptor AFTER the block runs, and the producer's
|
|
183
|
+
# `watch:` covers helper-file additions — so no priming read is needed here.
|
|
219
184
|
@helper_table = cache_for(:helper_table, params: {}).call
|
|
220
185
|
rescue Plugin::AccessDeniedError => e
|
|
221
186
|
@load_error = "rigor-rails-routes: #{e.message}"
|
|
@@ -4,24 +4,18 @@ require "rigor/plugin"
|
|
|
4
4
|
|
|
5
5
|
# ADR-32 — bundled `rigor-rbs-inline` plugin.
|
|
6
6
|
#
|
|
7
|
-
# Synthesises RBS from project Ruby files that carry
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# result to the analysis environment through the
|
|
11
|
-
# `source_rbs_synthesizer:` manifest hook.
|
|
7
|
+
# Synthesises RBS from project Ruby files that carry rbs-inline-shaped comments (`#: () -> T`, `# @rbs
|
|
8
|
+
# name: T`, `# @rbs return: T`, attribute `#:`, …) and contributes the result to the analysis environment
|
|
9
|
+
# through the `source_rbs_synthesizer:` manifest hook.
|
|
12
10
|
#
|
|
13
|
-
# By default (WD2) only files starting with the upstream
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# false` in the plugin config (WD10).
|
|
11
|
+
# By default (WD2) only files starting with the upstream `# rbs_inline: enabled` magic comment are
|
|
12
|
+
# processed; a host context can flip this off by setting `require_magic_comment: false` in the plugin
|
|
13
|
+
# config (WD10).
|
|
17
14
|
module Rigor
|
|
18
15
|
module Plugin
|
|
19
|
-
# The plugin gem requires `rbs/inline` at load time; without
|
|
20
|
-
# the
|
|
21
|
-
#
|
|
22
|
-
# the user activated this plugin without installing the
|
|
23
|
-
# `rbs-inline` gem (loud-on-activation, fail-soft to no
|
|
24
|
-
# contribution).
|
|
16
|
+
# The plugin gem requires `rbs/inline` at load time; without the upstream library the synthesizer can't
|
|
17
|
+
# do its job. Wrapped in a begin/rescue so the analyzer still loads if the user activated this plugin
|
|
18
|
+
# without installing the `rbs-inline` gem (loud-on-activation, fail-soft to no contribution).
|
|
25
19
|
begin
|
|
26
20
|
require "prism"
|
|
27
21
|
require "rbs/inline"
|
|
@@ -37,18 +31,13 @@ module Rigor
|
|
|
37
31
|
end
|
|
38
32
|
|
|
39
33
|
class RbsInline < Rigor::Plugin::Base
|
|
40
|
-
# Synthesizer callable invoked once per project Ruby
|
|
41
|
-
# source
|
|
42
|
-
#
|
|
43
|
-
# or `nil` when the file contributes nothing (no magic
|
|
44
|
-
# comment in the default mode, empty annotation set,
|
|
45
|
-
# parse error per WD6).
|
|
34
|
+
# Synthesizer callable invoked once per project Ruby source file by `Environment.for_project` at
|
|
35
|
+
# env-build time. Returns the synthesised RBS source as a String, or `nil` when the file contributes
|
|
36
|
+
# nothing (no magic comment in the default mode, empty annotation set, parse error per WD6).
|
|
46
37
|
class Synthesizer
|
|
47
|
-
# @param require_magic_comment [Boolean] when `true`
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
# When `false` (WD10 host-context override), every
|
|
51
|
-
# file is treated as if it carried the magic comment.
|
|
38
|
+
# @param require_magic_comment [Boolean] when `true` (the default, WD2), only files with
|
|
39
|
+
# `# rbs_inline: enabled` at the top are processed. When `false` (WD10 host-context override),
|
|
40
|
+
# every file is treated as if it carried the magic comment.
|
|
52
41
|
def initialize(require_magic_comment:)
|
|
53
42
|
@require_magic_comment = require_magic_comment
|
|
54
43
|
freeze
|
|
@@ -57,8 +46,7 @@ module Rigor
|
|
|
57
46
|
# Return value contract:
|
|
58
47
|
# - `String` (non-empty) → successful synthesis
|
|
59
48
|
# - `nil` → no contribution
|
|
60
|
-
# - `[:error, message_string]` → parse failed, surface
|
|
61
|
-
# info diagnostic per ADR-32 WD6
|
|
49
|
+
# - `[:error, message_string]` → parse failed, surface info diagnostic per ADR-32 WD6
|
|
62
50
|
def call(source_file_path)
|
|
63
51
|
return nil unless RBS_INLINE_AVAILABLE
|
|
64
52
|
return nil unless File.file?(source_file_path)
|
|
@@ -67,10 +55,8 @@ module Rigor
|
|
|
67
55
|
return nil if source.empty?
|
|
68
56
|
|
|
69
57
|
result = ::Prism.parse(source)
|
|
70
|
-
# `opt_in: true` is rbs-inline's "require the magic
|
|
71
|
-
#
|
|
72
|
-
# plugin's `require_magic_comment:` config knob maps
|
|
73
|
-
# directly onto it.
|
|
58
|
+
# `opt_in: true` is rbs-inline's "require the magic comment" mode (per upstream parser.rb:62).
|
|
59
|
+
# The plugin's `require_magic_comment:` config knob maps directly onto it.
|
|
74
60
|
parsed = ::RBS::Inline::Parser.parse(result, opt_in: @require_magic_comment)
|
|
75
61
|
return nil if parsed.nil?
|
|
76
62
|
|
|
@@ -80,11 +66,9 @@ module Rigor
|
|
|
80
66
|
|
|
81
67
|
rendered
|
|
82
68
|
rescue ::StandardError => e
|
|
83
|
-
# WD6 fail-soft — surface a structured error tuple so
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# naming the file + the upstream error message,
|
|
87
|
-
# without crashing analysis.
|
|
69
|
+
# WD6 fail-soft — surface a structured error tuple so the engine's `Environment.for_project` can
|
|
70
|
+
# emit a `source-rbs-synthesis-failed` info diagnostic naming the file + the upstream error
|
|
71
|
+
# message, without crashing analysis.
|
|
88
72
|
[:error, "#{e.class}: #{e.message.to_s.lines.first.to_s.strip}"]
|
|
89
73
|
end
|
|
90
74
|
end
|
|
@@ -98,24 +82,19 @@ module Rigor
|
|
|
98
82
|
source_rbs_synthesizer: nil # set per-instance below
|
|
99
83
|
)
|
|
100
84
|
|
|
101
|
-
# Per-instance synthesizer — built from the manifest's
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
# `#manifest`, which `Plugin::Registry#source_rbs_synthesizers`
|
|
106
|
-
# consults via `plugin.manifest.source_rbs_synthesizer`).
|
|
85
|
+
# Per-instance synthesizer — built from the manifest's default + the project's plugin config. The
|
|
86
|
+
# manifest `source_rbs_synthesizer:` is nil at the class level so the registry sees the instance's
|
|
87
|
+
# override (returned by `#manifest`, which `Plugin::Registry#source_rbs_synthesizers` consults via
|
|
88
|
+
# `plugin.manifest.source_rbs_synthesizer`).
|
|
107
89
|
#
|
|
108
|
-
# ADR-32 WD10 — `require_magic_comment` defaults to
|
|
109
|
-
#
|
|
110
|
-
# synthesizer into "process every file" mode.
|
|
90
|
+
# ADR-32 WD10 — `require_magic_comment` defaults to `true`. Setting it to `false` in `.rigor.yml`
|
|
91
|
+
# flips the synthesizer into "process every file" mode.
|
|
111
92
|
def initialize(services:, config: {})
|
|
112
93
|
super
|
|
113
94
|
@require_magic_comment = config.fetch("require_magic_comment", true) ? true : false
|
|
114
95
|
@synthesizer = Synthesizer.new(require_magic_comment: @require_magic_comment)
|
|
115
|
-
# Build the per-instance manifest eagerly (before
|
|
116
|
-
#
|
|
117
|
-
# the same object and we don't need to mutate a
|
|
118
|
-
# frozen instance later.
|
|
96
|
+
# Build the per-instance manifest eagerly (before `freeze`) so the registry's repeated reads
|
|
97
|
+
# return the same object and we don't need to mutate a frozen instance later.
|
|
119
98
|
base = self.class.manifest
|
|
120
99
|
@manifest_with_synth = build_manifest_with_synthesizer(base)
|
|
121
100
|
freeze
|
|
@@ -123,10 +102,9 @@ module Rigor
|
|
|
123
102
|
|
|
124
103
|
attr_reader :synthesizer
|
|
125
104
|
|
|
126
|
-
# Override the manifest-level `source_rbs_synthesizer:`
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
# reads this through `plugin.manifest.source_rbs_synthesizer`.
|
|
105
|
+
# Override the manifest-level `source_rbs_synthesizer:` (which is nil at the class level) with the
|
|
106
|
+
# per-instance synthesizer built from the merged config. The registry reads this through
|
|
107
|
+
# `plugin.manifest.source_rbs_synthesizer`.
|
|
130
108
|
def manifest
|
|
131
109
|
@manifest_with_synth
|
|
132
110
|
end
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# rigor-rbs-inline — ingests rbs-inline-shaped comments
|
|
4
|
-
#
|
|
5
|
-
# `#:`, ivars, generics, override, …) as RBS contributions to
|
|
6
|
-
# the analysis environment.
|
|
3
|
+
# rigor-rbs-inline — ingests rbs-inline-shaped comments (`# @rbs name: T`, `#: () -> T`, `# @rbs return: T`,
|
|
4
|
+
# attribute `#:`, ivars, generics, override, …) as RBS contributions to the analysis environment.
|
|
7
5
|
#
|
|
8
|
-
# Per ADR-32 the plugin gates per file on the upstream
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# false` in the plugin config to skip the gate — every file
|
|
13
|
-
# the synthesizer sees is treated as if it carried the magic
|
|
14
|
-
# comment.
|
|
6
|
+
# Per ADR-32 the plugin gates per file on the upstream `# rbs_inline: enabled` magic comment by default. A
|
|
7
|
+
# host context that owns the entire analysis scope (e.g. the ADR-29 browser playground) can set
|
|
8
|
+
# `require_magic_comment: false` in the plugin config to skip the gate — every file the synthesizer sees is
|
|
9
|
+
# treated as if it carried the magic comment.
|
|
15
10
|
#
|
|
16
11
|
# # .rigor.yml
|
|
17
12
|
# plugins:
|
|
@@ -19,6 +14,6 @@
|
|
|
19
14
|
# config:
|
|
20
15
|
# require_magic_comment: false # default true
|
|
21
16
|
#
|
|
22
|
-
# The plugin depends on the upstream `rbs-inline` gem; Rigor's
|
|
23
|
-
#
|
|
17
|
+
# The plugin depends on the upstream `rbs-inline` gem; Rigor's core `rigortype` gemspec stays zero-dep per
|
|
18
|
+
# ADR-0 / ADR-32 WD1.
|
|
24
19
|
require_relative "rigor/plugin/rbs_inline"
|
|
@@ -9,16 +9,12 @@ module Rigor
|
|
|
9
9
|
class Rspec < Rigor::Plugin::Base
|
|
10
10
|
# Per-file walker that:
|
|
11
11
|
#
|
|
12
|
-
# 1. Collects every RSpec scope (each `RSpec.describe`
|
|
13
|
-
# plus its nested `describe` / `context` blocks)
|
|
12
|
+
# 1. Collects every RSpec scope (each `RSpec.describe` plus its nested `describe` / `context` blocks)
|
|
14
13
|
# via {ScopeWalker}.
|
|
15
|
-
# 2. Reports duplicate `let(:name)` / `subject(:name)`
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# 3. Reports recursive self-references —
|
|
20
|
-
# `let(:user) { user.something }` will infinite-loop
|
|
21
|
-
# at runtime — an easy oversight.
|
|
14
|
+
# 2. Reports duplicate `let(:name)` / `subject(:name)` declarations within the same scope (the second
|
|
15
|
+
# declaration wins at runtime — an easy copy-paste bug).
|
|
16
|
+
# 3. Reports recursive self-references — `let(:user) { user.something }` will infinite-loop at
|
|
17
|
+
# runtime — an easy oversight.
|
|
22
18
|
module Analyzer
|
|
23
19
|
Diagnostic = Struct.new(:path, :line, :column, :severity, :rule, :message, keyword_init: true)
|
|
24
20
|
|
|
@@ -49,11 +45,9 @@ module Rigor
|
|
|
49
45
|
end
|
|
50
46
|
|
|
51
47
|
def duplicate_diagnostics_for(path, name, decls)
|
|
52
|
-
# Report each subsequent occurrence; the first
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# at runtime, so flag everything after the first
|
|
56
|
-
# so the user can see the full list.
|
|
48
|
+
# Report each subsequent occurrence; the first one is the "winner" only by literal source order,
|
|
49
|
+
# but RSpec lets the LAST declaration win at runtime, so flag everything after the first so the
|
|
50
|
+
# user can see the full list.
|
|
57
51
|
decls.drop(1).map do |decl|
|
|
58
52
|
Diagnostic.new(
|
|
59
53
|
path: path,
|
|
@@ -76,8 +70,7 @@ module Rigor
|
|
|
76
70
|
end
|
|
77
71
|
end
|
|
78
72
|
|
|
79
|
-
# Walks the declaration's block body looking for a
|
|
80
|
-
# call to its own name with no explicit receiver.
|
|
73
|
+
# Walks the declaration's block body looking for a call to its own name with no explicit receiver.
|
|
81
74
|
# Returns true if at least one such call exists.
|
|
82
75
|
def self_references?(decl)
|
|
83
76
|
body = decl.block_node&.body
|
|
@@ -8,22 +8,17 @@ require_relative "scope_walker"
|
|
|
8
8
|
module Rigor
|
|
9
9
|
module Plugin
|
|
10
10
|
class Rspec < Rigor::Plugin::Base
|
|
11
|
-
# Per-file index of RSpec scope hierarchy. Each
|
|
12
|
-
# describe scope carries:
|
|
11
|
+
# Per-file index of RSpec scope hierarchy. Each describe scope carries:
|
|
13
12
|
#
|
|
14
|
-
# - `outer_range`: `Range[Integer]` of line numbers
|
|
15
|
-
#
|
|
16
|
-
# - `describe_const`: the model constant string named in
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# - `lets`: `Hash{Symbol => Prism::BlockNode}` of `let(:name)
|
|
21
|
-
# { ... }` and `subject(:name) { ... }` declarations.
|
|
22
|
-
# `:subject` is the key for the implicit `subject { ... }`.
|
|
13
|
+
# - `outer_range`: `Range[Integer]` of line numbers covered by the describe's block body
|
|
14
|
+
# (start_line..end_line).
|
|
15
|
+
# - `describe_const`: the model constant string named in `RSpec.describe SomeModel do` / `describe
|
|
16
|
+
# SomeModel do`, or `nil` when the describe's first arg is a String (`describe ".foo"`).
|
|
17
|
+
# - `lets`: `Hash{Symbol => Prism::BlockNode}` of `let(:name) { ... }` and `subject(:name) { ... }`
|
|
18
|
+
# declarations. `:subject` is the key for the implicit `subject { ... }`.
|
|
23
19
|
#
|
|
24
|
-
# Used by the plugin's let-binding `dynamic_return`
|
|
25
|
-
#
|
|
26
|
-
# `it` bodies to the let block's inferred type.
|
|
20
|
+
# Used by the plugin's let-binding `dynamic_return` rule to bind `let`-named method-shape calls
|
|
21
|
+
# inside `it` bodies to the let block's inferred type.
|
|
27
22
|
class LetScopeIndex
|
|
28
23
|
Record = Struct.new(:outer_range, :describe_const, :lets, keyword_init: true) do
|
|
29
24
|
def contains?(line) = outer_range.cover?(line)
|
|
@@ -38,27 +33,22 @@ module Rigor
|
|
|
38
33
|
freeze
|
|
39
34
|
end
|
|
40
35
|
|
|
41
|
-
# Returns every record whose outer_range contains the
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
# method-resolution order (RSpec nested `let` shadows
|
|
45
|
-
# the outer `let`).
|
|
36
|
+
# Returns every record whose outer_range contains the given line, ordered from outermost to
|
|
37
|
+
# innermost. The innermost wins on `let` name collisions per Ruby's method-resolution order (RSpec
|
|
38
|
+
# nested `let` shadows the outer `let`).
|
|
46
39
|
def records_at(line)
|
|
47
40
|
@records.select { |rec| rec.contains?(line) }
|
|
48
41
|
end
|
|
49
42
|
|
|
50
|
-
# ADR-52 slice 5a — every `let` / `subject` name declared
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
# consults the rule for a call whose name appears here; the
|
|
54
|
-
# precise line-scoped resolution stays in `let_block_at`.
|
|
43
|
+
# ADR-52 slice 5a — every `let` / `subject` name declared anywhere in the file, across all describe
|
|
44
|
+
# scopes. Feeds the plugin's `dynamic_return file_methods:` gate: the engine only consults the rule
|
|
45
|
+
# for a call whose name appears here; the precise line-scoped resolution stays in `let_block_at`.
|
|
55
46
|
# @return [Array<Symbol>]
|
|
56
47
|
def let_names
|
|
57
48
|
@records.flat_map { |rec| rec.lets.keys }.uniq
|
|
58
49
|
end
|
|
59
50
|
|
|
60
|
-
# Resolves a `let` name at the given line by walking
|
|
61
|
-
# records innermost to outermost.
|
|
51
|
+
# Resolves a `let` name at the given line by walking records innermost to outermost.
|
|
62
52
|
# @return [Prism::BlockNode, nil]
|
|
63
53
|
def let_block_at(line, name)
|
|
64
54
|
name_sym = name.to_sym
|
|
@@ -68,9 +58,8 @@ module Rigor
|
|
|
68
58
|
nil
|
|
69
59
|
end
|
|
70
60
|
|
|
71
|
-
# Resolves the `describe`-anchor constant name at the
|
|
72
|
-
#
|
|
73
|
-
# anchor wins; describe-with-String anchors are skipped.
|
|
61
|
+
# Resolves the `describe`-anchor constant name at the given line. The innermost describe with a
|
|
62
|
+
# constant anchor wins; describe-with-String anchors are skipped.
|
|
74
63
|
# @return [String, nil]
|
|
75
64
|
def describe_const_at(line)
|
|
76
65
|
records_at(line).reverse.each do |rec|
|
|
@@ -92,9 +81,8 @@ module Rigor
|
|
|
92
81
|
if describe_call?(node)
|
|
93
82
|
record = build_record(node, anchor)
|
|
94
83
|
accumulator << record
|
|
95
|
-
# `describe_call?` already guarantees a `Prism::CallNode`; the
|
|
96
|
-
#
|
|
97
|
-
# from `Prism::Node` and resolves `#block` (a CallNode method).
|
|
84
|
+
# `describe_call?` already guarantees a `Prism::CallNode`; the explicit `is_a?` re-states it so
|
|
85
|
+
# the analyzer narrows `node` from `Prism::Node` and resolves `#block` (a CallNode method).
|
|
98
86
|
if node.is_a?(Prism::CallNode) && node.block&.body
|
|
99
87
|
collect(node.block.body, anchor: record.describe_const || anchor, accumulator: accumulator)
|
|
100
88
|
end
|
|
@@ -123,8 +111,7 @@ module Rigor
|
|
|
123
111
|
return false unless ScopeWalker::SCOPE_METHODS.include?(node.name) || node.name == :describe
|
|
124
112
|
return false unless node.block.is_a?(Prism::BlockNode)
|
|
125
113
|
|
|
126
|
-
# `RSpec.describe ...` has receiver = RSpec; bare
|
|
127
|
-
# `describe ... do` has nil receiver. Accept both.
|
|
114
|
+
# `RSpec.describe ...` has receiver = RSpec; bare `describe ... do` has nil receiver. Accept both.
|
|
128
115
|
node.receiver.nil? || describe_receiver?(node.receiver)
|
|
129
116
|
end
|
|
130
117
|
|
|
@@ -158,11 +145,9 @@ module Rigor
|
|
|
158
145
|
end
|
|
159
146
|
end
|
|
160
147
|
|
|
161
|
-
# Walks the describe's body shallowly: collects every
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
# nested describe / context blocks — those have their
|
|
165
|
-
# own record built by `collect` above.
|
|
148
|
+
# Walks the describe's body shallowly: collects every top-level `let(:name) { ... }` /
|
|
149
|
+
# `subject(:name) { ... }` / `subject { ... }` declaration. Does NOT recurse into nested describe /
|
|
150
|
+
# context blocks — those have their own record built by `collect` above.
|
|
166
151
|
def self.collect_lets(body)
|
|
167
152
|
return {} unless body.is_a?(Prism::Node)
|
|
168
153
|
|
|
@@ -6,28 +6,22 @@ require "rigor/type"
|
|
|
6
6
|
module Rigor
|
|
7
7
|
module Plugin
|
|
8
8
|
class Rspec < Rigor::Plugin::Base
|
|
9
|
-
# Resolves the runtime type of a
|
|
10
|
-
#
|
|
11
|
-
# block by pattern-matching its body's tail expression.
|
|
9
|
+
# Resolves the runtime type of a `let(:name) { body }` or `subject(:name) { body }` block by
|
|
10
|
+
# pattern-matching its body's tail expression.
|
|
12
11
|
#
|
|
13
12
|
# Recognised body shapes (floor):
|
|
14
13
|
#
|
|
15
14
|
# - `ConstantClass.new(...)` → `Nominal[ConstantClass]`
|
|
16
15
|
# - `Foo::Bar.new(...)` → `Nominal[Foo::Bar]`
|
|
17
|
-
# - `described_class.new(...)` → `Nominal[<describe const>]`
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# `build_stubbed(:factory)` / `attributes_for(:factory)`
|
|
22
|
-
# → `Nominal[<factory.model_class>]` when the
|
|
23
|
-
# `:factory_index` ADR-9 fact is available and the
|
|
16
|
+
# - `described_class.new(...)` → `Nominal[<describe const>]` when the surrounding describe block has
|
|
17
|
+
# a constant anchor.
|
|
18
|
+
# - `create(:factory)` / `build(:factory)` / `build_stubbed(:factory)` / `attributes_for(:factory)` →
|
|
19
|
+
# `Nominal[<factory.model_class>]` when the `:factory_index` ADR-9 fact is available and the
|
|
24
20
|
# factory name is known.
|
|
25
|
-
# - `FactoryBot.create(:factory)` etc. — same as the
|
|
26
|
-
# implicit-receiver factorybot form.
|
|
21
|
+
# - `FactoryBot.create(:factory)` etc. — same as the implicit-receiver factorybot form.
|
|
27
22
|
#
|
|
28
|
-
# Returns `nil` for any other body shape. Multi-statement
|
|
29
|
-
#
|
|
30
|
-
# value).
|
|
23
|
+
# Returns `nil` for any other body shape. Multi-statement bodies use the LAST statement (the block's
|
|
24
|
+
# return value).
|
|
31
25
|
module LetTypeResolver
|
|
32
26
|
FACTORYBOT_ENTRY_METHODS = %i[
|
|
33
27
|
create build build_stubbed attributes_for
|
|
@@ -38,9 +32,8 @@ module Rigor
|
|
|
38
32
|
|
|
39
33
|
# @param block_node [Prism::BlockNode]
|
|
40
34
|
# @param describe_const [String, nil]
|
|
41
|
-
# @param factory_index [Object, nil] something
|
|
42
|
-
#
|
|
43
|
-
# an entry that responds to `model_class`.
|
|
35
|
+
# @param factory_index [Object, nil] something responding to `find(factory_name)` and returning an
|
|
36
|
+
# entry that responds to `model_class`.
|
|
44
37
|
# @param environment [Rigor::Environment, nil]
|
|
45
38
|
# @return [Rigor::Type, nil]
|
|
46
39
|
def resolve(block_node, describe_const:, factory_index:, environment:)
|
|
@@ -103,10 +96,8 @@ module Rigor
|
|
|
103
96
|
nominal_for(model_class, environment: environment)
|
|
104
97
|
end
|
|
105
98
|
|
|
106
|
-
# Recognises both `create(:name)` (implicit receiver,
|
|
107
|
-
#
|
|
108
|
-
# explicit `FactoryBot.create(:name)` /
|
|
109
|
-
# `FactoryGirl.create(:name)` (legacy) forms.
|
|
99
|
+
# Recognises both `create(:name)` (implicit receiver, via `include FactoryBot::Syntax::Methods`)
|
|
100
|
+
# and the explicit `FactoryBot.create(:name)` / `FactoryGirl.create(:name)` (legacy) forms.
|
|
110
101
|
def factorybot_call?(call_node)
|
|
111
102
|
return false unless FACTORYBOT_ENTRY_METHODS.include?(call_node.name)
|
|
112
103
|
|