rigortype 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/core_overlay/csv.rbs +28 -0
- data/data/core_overlay/psych.rbs +22 -0
- data/docs/handbook/01-getting-started.md +9 -1
- data/docs/handbook/02-everyday-types.md +4 -1
- data/docs/handbook/08-understanding-errors.md +3 -3
- data/docs/manual/01-installation.md +1 -0
- data/docs/manual/02-cli-reference.md +16 -7
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/14-rails-quickstart.md +4 -2
- data/docs/manual/15-type-protection-coverage.md +27 -4
- data/docs/manual/plugins/rigor-actionpack.md +1 -1
- data/docs/manual/plugins/rigor-activerecord.md +12 -5
- data/docs/manual/plugins/rigor-rails-routes.md +11 -0
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +248 -186
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +62 -48
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +46 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_fork_scan.rb +55 -0
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +34 -18
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/missing_gem_constant_index.rb +128 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +225 -304
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +794 -1154
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/fork_map.rb +87 -0
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +627 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +154 -109
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -821
- data/lib/rigor/inference/statement_evaluator.rb +754 -1114
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +141 -184
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +92 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +40 -48
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +140 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +56 -59
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +172 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +168 -241
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +32 -0
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +189 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +61 -75
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +319 -505
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +91 -96
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/environment.rbs +1 -0
- data/sig/rigor/scope.rbs +6 -0
- metadata +9 -1
|
@@ -5,17 +5,17 @@ require "rigor/plugin"
|
|
|
5
5
|
require_relative "rails_routes/helper_table"
|
|
6
6
|
require_relative "rails_routes/routes_parser"
|
|
7
7
|
require_relative "rails_routes/helper_discoverer"
|
|
8
|
+
require_relative "rails_routes/grape_api_discoverer"
|
|
8
9
|
require_relative "rails_routes/analyzer"
|
|
9
10
|
|
|
10
11
|
module Rigor
|
|
11
12
|
module Plugin
|
|
12
|
-
# rigor-rails-routes — validates Rails route-helper calls
|
|
13
|
-
#
|
|
14
|
-
# project's `config/routes.rb`.
|
|
13
|
+
# rigor-rails-routes — validates Rails route-helper calls (`users_path`, `edit_user_path(@user)`, …)
|
|
14
|
+
# against the project's `config/routes.rb`.
|
|
15
15
|
#
|
|
16
16
|
# Tier 1A of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
|
|
17
|
-
# Statically interprets the routes DSL via Prism — no
|
|
18
|
-
#
|
|
17
|
+
# Statically interprets the routes DSL via Prism — no `rails` runtime dependency. Recognised v0.1.0
|
|
18
|
+
# surface:
|
|
19
19
|
#
|
|
20
20
|
# - `Rails.application.routes.draw do ... end`
|
|
21
21
|
# - `resources :name [, only: [...] | except: [...]]`
|
|
@@ -25,10 +25,8 @@ module Rigor
|
|
|
25
25
|
# - One level of `namespace :foo do ... end`
|
|
26
26
|
# - One level of nested `resources`
|
|
27
27
|
#
|
|
28
|
-
# The plugin publishes its parsed `:helper_table` through
|
|
29
|
-
#
|
|
30
|
-
# Phase 4 consumes it for route-helper validation in
|
|
31
|
-
# controller code.
|
|
28
|
+
# The plugin publishes its parsed `:helper_table` through the ADR-9 cross-plugin fact store;
|
|
29
|
+
# `rigor-actionpack` Phase 4 consumes it for route-helper validation in controller code.
|
|
32
30
|
#
|
|
33
31
|
# ## Configuration
|
|
34
32
|
#
|
|
@@ -37,69 +35,63 @@ module Rigor
|
|
|
37
35
|
# config:
|
|
38
36
|
# routes_file: config/routes.rb # default; optional
|
|
39
37
|
#
|
|
38
|
+
# A project mounting a Grape API keeps its `grape-path-helpers` helpers (`api_v4_groups_badges_path`)
|
|
39
|
+
# out of `unknown-helper`: those names come from grape's runtime route table, so {GrapeApiDiscoverer}
|
|
40
|
+
# recognises the namespace their `prefix` / `version` declarations open rather than enumerating names
|
|
41
|
+
# it cannot know.
|
|
42
|
+
#
|
|
40
43
|
# ## Limitations (v0.1.0)
|
|
41
44
|
#
|
|
42
|
-
# - `scope :path:` / `scope :module:` / `scope :as:` are
|
|
43
|
-
# not interpreted — helpers nested inside these
|
|
45
|
+
# - `scope :path:` / `scope :module:` / `scope :as:` are not interpreted — helpers nested inside these
|
|
44
46
|
# 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.
|
|
47
|
+
# - Constraints / format restrictions / mountable engines are out of scope.
|
|
48
|
+
# - The English inflector is intentionally tiny: it handles `posts` ↔ `post`, `users` ↔ `user`,
|
|
49
|
+
# `categories` ↔ `category`, `boxes` ↔ `box`. Custom inflections (`fish` ↔ `fish`, `child` ↔
|
|
50
|
+
# `children`) are out of scope; users who need them ship a hand-written RBS for the affected helper.
|
|
53
51
|
class RailsRoutes < Rigor::Plugin::Base
|
|
54
52
|
manifest(
|
|
55
53
|
id: "rails-routes",
|
|
56
|
-
# Bumped 2026-05-28 — GitLab FOSS sweep adds: (a)
|
|
57
|
-
# `
|
|
58
|
-
#
|
|
59
|
-
# (
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
# this to shorten `namespace_project_*` → `project_*`.
|
|
66
|
-
version: "0.27.0",
|
|
54
|
+
# Bumped 2026-05-28 — GitLab FOSS sweep adds: (a) `draw_all :name` support (action_dispatch-draw_all
|
|
55
|
+
# gem; single-file load semantics matching `draw :name`); (b) keyword-style `scope(path:
|
|
56
|
+
# ':project_id', as: :project)` — path read from the `:path` keyword, not only from the positional
|
|
57
|
+
# first arg; (c) detection of iterative `direct(name.sub(FROM, TO)) do ... end` alias-generation
|
|
58
|
+
# idiom — generates `<TO>_*` aliases for every registered `<FROM>_*` helper. GitLab uses this to
|
|
59
|
+
# shorten `namespace_project_*` → `project_*`. Bumped 2026-07-10 — recognises the open helper
|
|
60
|
+
# namespace `grape-path-helpers` generates (`api_v4_*_path`), grounded in the project's own Grape
|
|
61
|
+
# `prefix` / `version` declarations (see {GrapeApiDiscoverer}).
|
|
62
|
+
version: "0.28.0",
|
|
67
63
|
description: "Validates Rails route-helper calls against `config/routes.rb`.",
|
|
68
64
|
config_schema: {
|
|
69
65
|
"routes_file" => { kind: :string, default: "config/routes.rb" },
|
|
70
|
-
# `helper_paths` — the directories `HelperDiscoverer` walks
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
# `app/
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
# `
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
"helper_paths" => { kind: :array, default: ["app"] }
|
|
66
|
+
# `helper_paths` — the directories `HelperDiscoverer` walks for project-defined `*_path` / `*_url`
|
|
67
|
+
# methods. Default to the whole `app/` tree — the suffix filter inside the discoverer keeps the
|
|
68
|
+
# registered set tight, and real-world Rails apps routinely keep URL builders under
|
|
69
|
+
# `app/controllers` (private `def page_url`, `def callback_url` shapes), `app/lib` (Mastodon's
|
|
70
|
+
# `TranslationService::DeepL#base_url`), `app/services` (`SoftwareUpdateCheckService#api_url`),
|
|
71
|
+
# `app/serializers`, `app/presenters`, `app/decorators`, not only `app/helpers/`. Walking the
|
|
72
|
+
# whole tree is the honest answer to "does this `_path` / `_url` name exist anywhere in the
|
|
73
|
+
# project?"; the cost is a one-time Prism parse per file at startup, which is bounded.
|
|
74
|
+
"helper_paths" => { kind: :array, default: ["app"] },
|
|
75
|
+
# `grape_api_paths` — the directories scanned for Grape API classes, whose `prefix` / `version`
|
|
76
|
+
# declarations ground the open `api_v4_*_path` helper namespace `grape-path-helpers` generates
|
|
77
|
+
# (see {GrapeApiDiscoverer}). Defaults cover the two conventional homes; a project with no grape
|
|
78
|
+
# API declares no prefixes and nothing changes for it.
|
|
79
|
+
"grape_api_paths" => { kind: :array, default: ["lib/api", "app/api"] }
|
|
85
80
|
},
|
|
86
81
|
produces: [:helper_table]
|
|
87
82
|
)
|
|
88
83
|
|
|
89
|
-
# Cached producer — reads `config/routes.rb` through
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
# `FileEntry` digest invalidates the cache on routes-
|
|
93
|
-
# file edits.
|
|
84
|
+
# Cached producer — reads `config/routes.rb` through the trusted `IoBoundary` and parses through
|
|
85
|
+
# {RoutesParser}. The descriptor's auto-collected `FileEntry` digest invalidates the cache on
|
|
86
|
+
# routes-file edits.
|
|
94
87
|
#
|
|
95
|
-
# Passes a `file_reader` lambda so the parser can follow
|
|
96
|
-
# `
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
producer :helper_table, watch: -> { @helper_paths.map { |dir| [dir, "**/*.rb"] } } do |_params|
|
|
88
|
+
# Passes a `file_reader` lambda so the parser can follow `draw(:admin)` → `config/routes/admin.rb`
|
|
89
|
+
# partials. `watch:` (ADR-60 WD3) covers every `.rb` under `helper_paths:` and `grape_api_paths:` so
|
|
90
|
+
# ADDING a helper file — or a Grape API whose `version` declaration opens a new namespace —
|
|
91
|
+
# invalidates the table. The producer's own in-block reads (the routes file + the partials it follows
|
|
92
|
+
# via `draw`) are captured post-compute, but a brand-new file the prior run never read would otherwise
|
|
93
|
+
# read as fresh.
|
|
94
|
+
producer :helper_table, watch: -> { (@helper_paths + @grape_api_paths).map { |dir| [dir, "**/*.rb"] } } do |_p|
|
|
103
95
|
routes_dir = "#{File.dirname(@routes_file)}/routes"
|
|
104
96
|
file_reader = lambda do |name|
|
|
105
97
|
io_boundary.read_file("#{routes_dir}/#{name}")
|
|
@@ -107,26 +99,24 @@ module Rigor
|
|
|
107
99
|
nil
|
|
108
100
|
end
|
|
109
101
|
contents = io_boundary.read_file(@routes_file)
|
|
110
|
-
custom_helpers
|
|
111
|
-
|
|
102
|
+
RoutesParser.parse(contents, file_reader: file_reader, custom_helpers: discover_custom_helpers,
|
|
103
|
+
grape_prefixes: discover_grape_prefixes)
|
|
112
104
|
end
|
|
113
105
|
|
|
114
106
|
def init(_services)
|
|
115
107
|
@routes_file = config.fetch("routes_file")
|
|
116
108
|
@helper_paths = Array(config.fetch("helper_paths")).map(&:to_s)
|
|
109
|
+
@grape_api_paths = Array(config.fetch("grape_api_paths")).map(&:to_s)
|
|
117
110
|
@helper_table = nil
|
|
118
111
|
@helper_table_built = false
|
|
119
112
|
@load_error = nil
|
|
120
113
|
end
|
|
121
114
|
|
|
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.
|
|
115
|
+
# Walks every configured `helper_paths:` directory through the trusted `IoBoundary` and returns the
|
|
116
|
+
# set of project-defined `*_path` / `*_url` method names for {HelperDiscoverer}. Each file digest is
|
|
117
|
+
# captured by the boundary so editing a helper file invalidates the `:helper_table` cache
|
|
118
|
+
# automatically. Returns the empty set when nothing under `helper_paths:` exists — the routes table
|
|
119
|
+
# still works.
|
|
130
120
|
def discover_custom_helpers
|
|
131
121
|
contents_per_path = {}
|
|
132
122
|
each_helper_file do |path|
|
|
@@ -137,8 +127,23 @@ module Rigor
|
|
|
137
127
|
HelperDiscoverer.discover(contents_per_path)
|
|
138
128
|
end
|
|
139
129
|
|
|
140
|
-
|
|
141
|
-
|
|
130
|
+
# Walks `grape_api_paths:` through the trusted `IoBoundary` and returns the helper-name prefixes the
|
|
131
|
+
# project's Grape API classes generate (`["api_v3", "api_v4"]`). Empty for a project with no grape API,
|
|
132
|
+
# which leaves `unknown-helper` behaviour exactly as it was.
|
|
133
|
+
def discover_grape_prefixes
|
|
134
|
+
contents_per_path = {}
|
|
135
|
+
each_ruby_file(@grape_api_paths) do |path|
|
|
136
|
+
contents_per_path[path] = io_boundary.read_file(path)
|
|
137
|
+
rescue Plugin::AccessDeniedError, Errno::ENOENT
|
|
138
|
+
next
|
|
139
|
+
end
|
|
140
|
+
GrapeApiDiscoverer.discover(contents_per_path)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def each_helper_file(&) = each_ruby_file(@helper_paths, &)
|
|
144
|
+
|
|
145
|
+
def each_ruby_file(dirs, &)
|
|
146
|
+
dirs.each do |dir|
|
|
142
147
|
absolute = File.expand_path(dir)
|
|
143
148
|
next unless File.directory?(absolute)
|
|
144
149
|
|
|
@@ -146,8 +151,7 @@ module Rigor
|
|
|
146
151
|
end
|
|
147
152
|
end
|
|
148
153
|
|
|
149
|
-
# Publishes the parsed table to the cross-plugin fact
|
|
150
|
-
# store; `rigor-actionpack` Phase 4 reads it via
|
|
154
|
+
# Publishes the parsed table to the cross-plugin fact store; `rigor-actionpack` Phase 4 reads it via
|
|
151
155
|
# `services.fact_store.read`.
|
|
152
156
|
def prepare(services)
|
|
153
157
|
table = helper_table_or_nil
|
|
@@ -160,10 +164,9 @@ module Rigor
|
|
|
160
164
|
)
|
|
161
165
|
end
|
|
162
166
|
|
|
163
|
-
# File-level only: the once-per-run load-error emission. Per-call
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
# built once as the node-rule file context.
|
|
167
|
+
# File-level only: the once-per-run load-error emission. Per-call helper validation runs over the
|
|
168
|
+
# engine-owned walk via the node_rule below (ADR-37), with the same-file `shadowing` set built once
|
|
169
|
+
# as the node-rule file context.
|
|
167
170
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
168
171
|
table = helper_table_or_nil
|
|
169
172
|
if table.nil? && @load_error
|
|
@@ -191,31 +194,23 @@ module Rigor
|
|
|
191
194
|
|
|
192
195
|
private
|
|
193
196
|
|
|
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.
|
|
197
|
+
# The load-error path used to emit the same warning on every analyzed file in the project. On large
|
|
198
|
+
# monorepos (Mastodon: 1,302 files; Solidus: ~1,000 files) and on legacy projects without a top-level
|
|
199
|
+
# `config/routes.rb`, this multiplied a single root cause into 1,000+ identical diagnostics. The
|
|
200
|
+
# error is project-global — report it once per run.
|
|
201
201
|
|
|
202
202
|
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.
|
|
203
|
+
# Memoise the build *attempt*, not just a truthy result: when the routes file is missing or
|
|
204
|
+
# unparseable the table is nil, and the old `return @helper_table if @helper_table` re-ran the
|
|
205
|
+
# whole read-and-parse on every call. The engine consults this per route-helper dispatch, so a nil
|
|
206
|
+
# table meant re-reading (and, on a real project, re-parsing) `config/routes.rb` tens of thousands
|
|
207
|
+
# of times per run. The result — nil included — is stable for the run, so one attempt is correct.
|
|
211
208
|
return @helper_table if @helper_table_built
|
|
212
209
|
|
|
213
210
|
@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.
|
|
211
|
+
# ADR-60 WD3 record-and-validate: the producer's in-block reads (the routes file + the partials it
|
|
212
|
+
# follows) are captured into the dependency descriptor AFTER the block runs, and the producer's
|
|
213
|
+
# `watch:` covers helper-file additions — so no priming read is needed here.
|
|
219
214
|
@helper_table = cache_for(:helper_table, params: {}).call
|
|
220
215
|
rescue Plugin::AccessDeniedError => e
|
|
221
216
|
@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
|
|