rigortype 0.3.8 → 0.3.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/capability_roles/capability_roles.rbs +36 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +37 -4
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +14 -1
- data/docs/handbook/11-sig-gen.md +72 -1
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +32 -4
- data/docs/manual/03-configuration.md +11 -0
- data/docs/manual/04-diagnostics.md +11 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +6 -1
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/plugins/rigor-actionpack.md +18 -0
- data/docs/manual/plugins/rigor-activerecord.md +28 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
- data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
- data/lib/rigor/analysis/baseline.rb +2 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +200 -32
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +6 -1
- data/lib/rigor/analysis/incremental.rb +16 -0
- data/lib/rigor/analysis/incremental_session.rb +81 -22
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +4 -1
- data/lib/rigor/analysis/run_cache_key.rb +1 -1
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
- data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
- data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +133 -35
- data/lib/rigor/analysis/worker_session.rb +9 -10
- data/lib/rigor/bleeding_edge.rb +12 -24
- data/lib/rigor/builtins/hkt_builtins.rb +2 -2
- data/lib/rigor/builtins/imported_refinements.rb +155 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +4 -4
- data/lib/rigor/builtins/static_return_refinements.rb +5 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +58 -20
- data/lib/rigor/cache/engine_source.rb +4 -4
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +40 -6
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +79 -11
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +77 -32
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +3 -3
- data/lib/rigor/cli/docs_command.rb +2 -2
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +1 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +2 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +2 -2
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +36 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +10 -6
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +68 -6
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -6
- data/lib/rigor/configuration.rb +43 -4
- data/lib/rigor/effects/attribution.rb +1 -3
- data/lib/rigor/effects/config_envelopes.rb +6 -8
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +0 -1
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +8 -8
- data/lib/rigor/effects/envelope_index.rb +5 -8
- data/lib/rigor/effects/framework_units.rb +4 -6
- data/lib/rigor/effects/identity.rb +4 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/method_key.rb +1 -1
- data/lib/rigor/effects/plugin_facts.rb +4 -6
- data/lib/rigor/effects/propagator.rb +2 -3
- data/lib/rigor/effects/scanner.rb +1 -1
- data/lib/rigor/effects/signature_sources.rb +3 -5
- data/lib/rigor/effects/snapshot.rb +10 -10
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/unit_scan.rb +10 -10
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +51 -4
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +388 -59
- data/lib/rigor/environment.rb +105 -20
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/inference/acceptance.rb +153 -8
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +5 -5
- data/lib/rigor/inference/budget_trace.rb +1 -5
- data/lib/rigor/inference/captured_locals.rb +2 -3
- data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +168 -0
- data/lib/rigor/inference/expression_typer.rb +763 -143
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +1 -5
- data/lib/rigor/inference/macro_block_self_type.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +12 -13
- data/lib/rigor/inference/method_parameter_binder.rb +4 -6
- data/lib/rigor/inference/multi_target_binder.rb +1 -3
- data/lib/rigor/inference/mutation_rejoin.rb +168 -0
- data/lib/rigor/inference/mutation_widening.rb +100 -51
- data/lib/rigor/inference/narrowing.rb +299 -46
- data/lib/rigor/inference/optimistic_origin.rb +1 -7
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +3 -3
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +3 -3
- data/lib/rigor/inference/refinement_mutation.rb +72 -0
- data/lib/rigor/inference/scope_indexer.rb +688 -151
- data/lib/rigor/inference/statement_evaluator.rb +221 -50
- data/lib/rigor/inference/string_mutation.rb +60 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +1 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +2 -2
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +2 -5
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/plugin/base.rb +90 -21
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +7 -7
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +31 -12
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +23 -1
- data/lib/rigor/plugin/registry.rb +42 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +11 -11
- data/lib/rigor/protection/kill_signature.rb +2 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +6 -9
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +61 -21
- data/lib/rigor/reflection/constant_path.rb +126 -0
- data/lib/rigor/reflection.rb +26 -10
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +30 -0
- data/lib/rigor/scope.rb +203 -4
- data/lib/rigor/sig_gen/generator.rb +115 -22
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/observation_collector.rb +17 -15
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +4 -5
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/writer.rb +23 -6
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +2 -5
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +68 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +5 -5
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +3 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/environment.rbs +5 -0
- data/sig/rigor/inference.rbs +3 -10
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/manifest.rbs +2 -1
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +24 -3
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
- data/sig/rigor/type.rbs +27 -1
- data/sig/rigor.rbs +1 -0
- data/skills/rigor-ask/SKILL.md +8 -6
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
- data/skills/rigor-project-init/SKILL.md +6 -3
- data/skills/rigor-project-init/references/02-configure.md +10 -6
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-type-oracle/SKILL.md +228 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
- metadata +26 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Active Record's framework namespaces — issue #534 item 7.
|
|
2
|
+
#
|
|
3
|
+
# The 2026-09-01 corpus opacity sweep counted `ActiveRecord::RecordNotFound` 115 times on
|
|
4
|
+
# mastodon, modal in `rescue` clauses, and `ActiveModel` 165 / `Arel` 35 — every one of them
|
|
5
|
+
# resolving to `untyped` because nothing in the bundle named the constant. This file names them,
|
|
6
|
+
# and nothing more.
|
|
7
|
+
#
|
|
8
|
+
# ## The admission rule: a namespace or a rescued error, never a base class
|
|
9
|
+
#
|
|
10
|
+
# Declaring a constant makes it RBS-KNOWN, and a known class with an incomplete signature turns
|
|
11
|
+
# every member the signature omits into a false `call.undefined-method`. That risk is not
|
|
12
|
+
# uniform, so this file admits exactly two shapes:
|
|
13
|
+
#
|
|
14
|
+
# - **Bare namespace modules.** Nobody dispatches on a module used only as a constant path
|
|
15
|
+
# (`ActiveModel::Type::Boolean`, `Arel::Nodes`), so an empty declaration asserts nothing.
|
|
16
|
+
# - **Exception classes**, which are dispatched on — as the `=> e` of a rescue — but whose whole
|
|
17
|
+
# inherited surface is `StandardError`'s, which Rigor's core RBS already carries. Each is listed
|
|
18
|
+
# in the manifest's `open_receivers:` (ADR-26) as well, so the framework-specific readers this
|
|
19
|
+
# file does not enumerate (`RecordInvalid#record`, `StatementInvalid#cause`, …) stay lenient.
|
|
20
|
+
#
|
|
21
|
+
# What is deliberately NOT here is every class a project INHERITS from. `ActiveRecord::Base` is
|
|
22
|
+
# the case that matters: declaring it empty would close every model in the project, and turn
|
|
23
|
+
# `Account.where(...)`, `record.save!` and the whole generated column surface into
|
|
24
|
+
# `call.undefined-method` on working Rails code. The plugin types models through its model index
|
|
25
|
+
# instead, and the namespace stays open for it. The same argument keeps `ActiveModel::Model` and
|
|
26
|
+
# the other mixin modules bare rather than populated.
|
|
27
|
+
#
|
|
28
|
+
# The exception hierarchy is transcribed from Rails v8.0.2 `activerecord/lib/active_record/errors.rb`;
|
|
29
|
+
# a class whose superclass could not be stated with certainty was left out rather than guessed,
|
|
30
|
+
# because a wrong superclass is a wrong `rescue` reachability answer.
|
|
31
|
+
|
|
32
|
+
module ActiveRecord
|
|
33
|
+
class ActiveRecordError < StandardError
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class RecordNotFound < ActiveRecordError
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class RecordInvalid < ActiveRecordError
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class RecordNotSaved < ActiveRecordError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class RecordNotDestroyed < ActiveRecordError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class ReadOnlyRecord < ActiveRecordError
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class Rollback < ActiveRecordError
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class StaleObjectError < ActiveRecordError
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class ConnectionNotEstablished < ActiveRecordError
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class StatementInvalid < ActiveRecordError
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class WrappedDatabaseException < StatementInvalid
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class RecordNotUnique < WrappedDatabaseException
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Active Model ships as its own gem, but `activerecord` depends on it unconditionally, so a project
|
|
71
|
+
# that enables this plugin always has it. There is no `rigor-activemodel`, and inventing one to hold
|
|
72
|
+
# eleven empty modules would be a plugin whose whole content is this file.
|
|
73
|
+
module ActiveModel
|
|
74
|
+
module Model
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
module API
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
module Validations
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module Serialization
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
module Conversion
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
module Naming
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
module Attributes
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
module Dirty
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
module SecurePassword
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
module Callbacks
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
module Type
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class ValidationError < StandardError
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Arel ships INSIDE the activerecord gem, so this plugin is its owner by the "the row lives in the
|
|
112
|
+
# plugin that owns the gem" rule. `Arel.sql` is the one member the corpus calls; the signature is
|
|
113
|
+
# written to accept anything because its extra binds are positional-or-keyword and an arity claim
|
|
114
|
+
# here would be a false `call.wrong-arity` on a shape this file has no reason to police.
|
|
115
|
+
module Arel
|
|
116
|
+
module Nodes
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
module Predications
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def self.sql: (untyped, *untyped, **untyped) -> untyped
|
|
123
|
+
end
|
|
@@ -37,6 +37,7 @@ module Rigor
|
|
|
37
37
|
class Activestorage < Rigor::Plugin::Base
|
|
38
38
|
manifest(
|
|
39
39
|
id: "activestorage",
|
|
40
|
+
target_gems: ["activestorage"],
|
|
40
41
|
# Bumped 2026-09-02 (#621) — attachment owners are keyed by their de-rooted name and a reopened
|
|
41
42
|
# class's attachments are UNIONed rather than clobbered, so a cached 0.1.0 index can be missing the
|
|
42
43
|
# attachments the merge restores.
|
|
@@ -42,9 +42,13 @@ module Rigor
|
|
|
42
42
|
class ActivesupportCoreExt < Rigor::Plugin::Base
|
|
43
43
|
manifest(
|
|
44
44
|
id: "activesupport-core-ext",
|
|
45
|
+
target_gems: ["activesupport"],
|
|
46
|
+
# Bumped 2026-09-10 (#534 item 7) — `ActiveSupport::Concern` is declared, so `extend
|
|
47
|
+
# ActiveSupport::Concern` resolves instead of reading as an unresolved constant.
|
|
48
|
+
#
|
|
45
49
|
# Bumped 2026-09-02 (#632) — `ActiveSupport::Duration`'s reader surface (`ago`/`to_i`/`iso8601`/…)
|
|
46
50
|
# is now declared, open_receivers-protected; see the class comment above.
|
|
47
|
-
version: "0.
|
|
51
|
+
version: "0.5.0",
|
|
48
52
|
description: "RBS bundle for the most-frequently-flagged ActiveSupport core_ext extensions, " \
|
|
49
53
|
"plus the `ActiveSupport::Duration` type of the numeric time multipliers and its " \
|
|
50
54
|
"reader surface.",
|
|
@@ -55,7 +59,10 @@ module Rigor
|
|
|
55
59
|
# `call.undefined-method` fire against it. Distinct from the RECEIVER-side FP the multiplier
|
|
56
60
|
# `dynamic_return` gate guards (`Time#day` vs `Duration#day`, in the class comment above) — this is
|
|
57
61
|
# the class's OWN unenumerable member set.
|
|
58
|
-
|
|
62
|
+
# `ActiveSupport::Concern` (#534 item 7) is here for the same reason at a different scale: the
|
|
63
|
+
# declaration names `included` / `prepended` / `class_methods` so `extend ActiveSupport::Concern`
|
|
64
|
+
# resolves, and everything else the module really responds to must stay lenient.
|
|
65
|
+
open_receivers: ["ActiveSupport::Duration", "ActiveSupport::Concern"],
|
|
59
66
|
# ADR-103 WD10 (#387) — the IMPURE half of ActiveSupport: the clock, the notification bus and
|
|
60
67
|
# `CurrentAttributes`. The `%a{pure}` sweep over the predicate surface is issue #388 and lands in
|
|
61
68
|
# `sig/`, not here. See {Effects}.
|
|
@@ -163,6 +163,34 @@ class Object
|
|
|
163
163
|
# exception to the try-shaped-dispatch rule above (design note § 11.2).
|
|
164
164
|
%a{pure}
|
|
165
165
|
def in?: (untyped) -> bool
|
|
166
|
+
|
|
167
|
+
# Issue #673 — `core_ext/object/to_query`, `core_ext/object/duplicable` and
|
|
168
|
+
# `core_ext/object/instance_variables` all hang off `Object`, so every
|
|
169
|
+
# RBS-known receiver reaches them; declaring them only on `Hash` (as
|
|
170
|
+
# `to_param` / `to_query` were) or only on `NilClass` (as `duplicable?` /
|
|
171
|
+
# `instance_values` were) left `"x".to_param`, `1.duplicable?` and friends a
|
|
172
|
+
# `call.undefined-method` on ordinary Rails code.
|
|
173
|
+
#
|
|
174
|
+
# `to_param` is `to_s`; `to_query(key)` interpolates two `CGI.escape` results.
|
|
175
|
+
# `key` is `untyped` because the body only calls `to_param` on it, and both
|
|
176
|
+
# `:name` and `"user[name]"` are ordinary call sites. Neither is `%a{pure}`:
|
|
177
|
+
# both dispatch `to_param` on a value whose own override is invisible here,
|
|
178
|
+
# the same reason `Hash#to_query` below carries no envelope.
|
|
179
|
+
def to_param: () -> String
|
|
180
|
+
def to_query: (untyped key) -> String
|
|
181
|
+
|
|
182
|
+
# `bool` and not `true`: `Object#duplicable?` answers true, but the
|
|
183
|
+
# `Singleton` (and, on a Ruby where methods are not duplicable, `Method` /
|
|
184
|
+
# `UnboundMethod`) overrides answer false. `-> true` on the universal
|
|
185
|
+
# receiver would make every `if x.duplicable?` an always-truthy branch —
|
|
186
|
+
# a false positive on the only shape anybody writes.
|
|
187
|
+
%a{pure}
|
|
188
|
+
def duplicable?: () -> bool
|
|
189
|
+
|
|
190
|
+
# Keys without the leading `@` for `instance_values`, with it for
|
|
191
|
+
# `instance_variable_names`.
|
|
192
|
+
def instance_values: () -> Hash[String, untyped]
|
|
193
|
+
def instance_variable_names: () -> Array[String]
|
|
166
194
|
end
|
|
167
195
|
|
|
168
196
|
# `nil.blank?` / `nil.present?` / `nil.try` are the most frequent
|
|
@@ -185,6 +213,13 @@ class NilClass
|
|
|
185
213
|
%a{pure}
|
|
186
214
|
def duplicable?: () -> true
|
|
187
215
|
def instance_values: () -> Hash[String, untyped]
|
|
216
|
+
# `core_ext/object/to_query` overrides `to_param` on the three singleton
|
|
217
|
+
# values to return `self` rather than `Object#to_param`'s `to_s`, so the
|
|
218
|
+
# `Object` row above would carry a wrong return here. `NilClass#to_query`
|
|
219
|
+
# is its own method (`CGI.escape(key.to_param)`), still a String.
|
|
220
|
+
%a{pure}
|
|
221
|
+
def to_param: () -> nil
|
|
222
|
+
def to_query: (untyped key) -> String
|
|
188
223
|
end
|
|
189
224
|
|
|
190
225
|
class TrueClass
|
|
@@ -192,6 +227,8 @@ class TrueClass
|
|
|
192
227
|
def blank?: () -> false
|
|
193
228
|
%a{pure}
|
|
194
229
|
def present?: () -> true
|
|
230
|
+
%a{pure}
|
|
231
|
+
def to_param: () -> true
|
|
195
232
|
end
|
|
196
233
|
|
|
197
234
|
class FalseClass
|
|
@@ -199,6 +236,8 @@ class FalseClass
|
|
|
199
236
|
def blank?: () -> true
|
|
200
237
|
%a{pure}
|
|
201
238
|
def present?: () -> false
|
|
239
|
+
%a{pure}
|
|
240
|
+
def to_param: () -> false
|
|
202
241
|
end
|
|
203
242
|
|
|
204
243
|
# ---------------------------------------------------------------
|
|
@@ -486,6 +525,22 @@ end
|
|
|
486
525
|
# purpose — some are already covered by the separate `dynamic_return` rule for `+`/`-`/`*` below, the rest
|
|
487
526
|
# are simply outside this issue's scope and remain silently `Dynamic` rather than a guess.
|
|
488
527
|
module ActiveSupport
|
|
528
|
+
# Issue #534 item 7 — `ActiveSupport::Concern` was 87 unresolved constant sites on mastodon, essentially
|
|
529
|
+
# all of them the `extend ActiveSupport::Concern` line at the top of a concern module. Naming it is a
|
|
530
|
+
# constant-resolution fix and nothing more: the three members are declared because `extend`ing a module
|
|
531
|
+
# puts them on the extender's singleton, and leaving them out of a now-RBS-known module is what would
|
|
532
|
+
# turn `included do … end` into a false `call.undefined-method` in every concern in the project. The
|
|
533
|
+
# blocks are `(?) -> untyped` because their body is evaluated against the INCLUDING class, which this
|
|
534
|
+
# signature has no way to name.
|
|
535
|
+
#
|
|
536
|
+
# `Concern` is listed in the manifest's `open_receivers:` for the rest of its surface (`append_features`,
|
|
537
|
+
# `class_methods`' generated `ClassMethods` constant, …).
|
|
538
|
+
module Concern
|
|
539
|
+
def included: (?untyped base) ?{ (?) -> untyped } -> untyped
|
|
540
|
+
def prepended: (?untyped base) ?{ (?) -> untyped } -> untyped
|
|
541
|
+
def class_methods: () { (?) -> untyped } -> untyped
|
|
542
|
+
end
|
|
543
|
+
|
|
489
544
|
class Duration
|
|
490
545
|
# The `ago` family (issue #659). `since(time = ::Time.current)` and `ago(time = ::Time.current)` are
|
|
491
546
|
# the only two real methods; `from_now` / `after` are `alias`es of the first and `until` / `before`
|
|
@@ -501,13 +556,17 @@ module ActiveSupport
|
|
|
501
556
|
# help there: it protects a Duration receiver, and the diagnostic lands on the `Time` the call
|
|
502
557
|
# RETURNS. #658 declared that surface and #670 the `Date` / `DateTime` twins, so the chains resolve.
|
|
503
558
|
#
|
|
504
|
-
# `() ->
|
|
505
|
-
#
|
|
506
|
-
#
|
|
507
|
-
#
|
|
508
|
-
#
|
|
509
|
-
#
|
|
510
|
-
#
|
|
559
|
+
# `() -> TimeWithZone` since #673, where the class these actually build under a zone (verified by
|
|
560
|
+
# calling them) was finally declared — as a `::Time` SUBCLASS, so nothing a `Time` return bought is
|
|
561
|
+
# given up: every `Time` instance method still resolves on the result, every `Time`-typed parameter
|
|
562
|
+
# still accepts it, and the four TWZ-only readers (`time_zone`, `time`, `period`, `comparable_time`)
|
|
563
|
+
# stop being a false `call.undefined-method` on `1.hour.ago`. It was `() -> Time` before that, on the
|
|
564
|
+
# ground that Rails overrides `TimeWithZone#is_a?` to answer true for `::Time` — true, and the
|
|
565
|
+
# subclass is the same claim made where the analyser can also see the extra readers. A
|
|
566
|
+
# `Time | TimeWithZone` UNION was measured and rejected: it draws no `undefined-method`, but types
|
|
567
|
+
# every downstream call `Dynamic[top]` (`iso8601`, `beginning_of_day`, `to_date`, `- 1.day`), which is
|
|
568
|
+
# worth no more than leaving them undeclared while still firing a parameter mismatch on the
|
|
569
|
+
# TimeWithZone arm.
|
|
511
570
|
#
|
|
512
571
|
# The with-an-argument form stays `(untyped) -> untyped` on purpose, and this is not caution: the
|
|
513
572
|
# runtime return depends on the argument's class AND on whether the duration carries a date-scale
|
|
@@ -520,17 +579,17 @@ module ActiveSupport
|
|
|
520
579
|
# what stops `created_at > 7.days.ago` folding to a constant. They are already attributed `nondet.time`
|
|
521
580
|
# in `lib/rigor/plugin/activesupport_core_ext/effects.rb` — annotating them here would duplicate an
|
|
522
581
|
# envelope the manifest already owns, and risk the two disagreeing.
|
|
523
|
-
def ago: () ->
|
|
582
|
+
def ago: () -> TimeWithZone
|
|
524
583
|
| (untyped time) -> untyped
|
|
525
|
-
def until: () ->
|
|
584
|
+
def until: () -> TimeWithZone
|
|
526
585
|
| (untyped time) -> untyped
|
|
527
|
-
def before: () ->
|
|
586
|
+
def before: () -> TimeWithZone
|
|
528
587
|
| (untyped time) -> untyped
|
|
529
|
-
def since: () ->
|
|
588
|
+
def since: () -> TimeWithZone
|
|
530
589
|
| (untyped time) -> untyped
|
|
531
|
-
def from_now: () ->
|
|
590
|
+
def from_now: () -> TimeWithZone
|
|
532
591
|
| (untyped time) -> untyped
|
|
533
|
-
def after: () ->
|
|
592
|
+
def after: () -> TimeWithZone
|
|
534
593
|
| (untyped time) -> untyped
|
|
535
594
|
|
|
536
595
|
# `to_i` returns `@value.to_i` — always Integer, whether `@value` itself is Integer (`1.day`) or Float
|
|
@@ -574,6 +633,48 @@ module ActiveSupport
|
|
|
574
633
|
%a{pure}
|
|
575
634
|
def parts: () -> Hash[Symbol, Integer | Float]
|
|
576
635
|
end
|
|
636
|
+
|
|
637
|
+
# ---------------------------------------------------------------
|
|
638
|
+
# ActiveSupport::TimeWithZone — issue #673
|
|
639
|
+
# ---------------------------------------------------------------
|
|
640
|
+
#
|
|
641
|
+
# `Time.current` and the `Duration#ago` family above really answer a `TimeWithZone` under a zone, and
|
|
642
|
+
# this class was not modelled at all, so the four readers a TWZ has and a `Time` does not —
|
|
643
|
+
# `time_zone`, `time`, `period`, `comparable_time` — were a false `call.undefined-method` on every
|
|
644
|
+
# `Time.current` receiver. Declaring them on `Time` instead would have been wrong: a plain `Time`
|
|
645
|
+
# genuinely lacks all four, and the diagnostic would stop firing where it is correct.
|
|
646
|
+
#
|
|
647
|
+
# `< ::Time` rather than a `Time | TimeWithZone` union at the producers. The union was measured during
|
|
648
|
+
# #632 and rejected — it types every downstream call `Dynamic[top]`. Inheritance is both cheaper and
|
|
649
|
+
# more honest: Rails overrides `TimeWithZone#is_a?` to answer true for `::Time`, so `Time` is the class
|
|
650
|
+
# Rails itself tells callers a TWZ is, and TWZ's `method_missing` forwards every name it does not
|
|
651
|
+
# define to the wrapped `::Time`, which is exactly what inheriting `Time`'s surface models. A TWZ
|
|
652
|
+
# therefore satisfies every `Time`-typed parameter, and chains keep resolving instead of collapsing.
|
|
653
|
+
#
|
|
654
|
+
# The cost, taken deliberately: `Time.current` answers a plain `Time` when `Time.zone` is nil, so
|
|
655
|
+
# `Time.current.time_zone` in a zone-less activesupport-only project is a real `NoMethodError` this
|
|
656
|
+
# now types. Rails sets `config.time_zone` ("UTC") by default, so the shape is rare, and the false
|
|
657
|
+
# positive it replaces fired on correct Rails code.
|
|
658
|
+
class TimeWithZone < ::Time
|
|
659
|
+
# `Time`'s own `initialize` takes the `(year, month, …)` parts; inheriting it would make
|
|
660
|
+
# `TimeWithZone.new(utc, zone)` — how Rails constructs one — a `call.argument-type-mismatch`.
|
|
661
|
+
# `untyped` because the zone and period carriers (`ActiveSupport::TimeZone`,
|
|
662
|
+
# `TZInfo::TimezonePeriod`) are not declared anywhere in this bundle.
|
|
663
|
+
def initialize: (untyped utc_time, untyped time_zone, ?untyped local_time, ?untyped period) -> void
|
|
664
|
+
|
|
665
|
+
# The `attr_reader`; an `ActiveSupport::TimeZone`, undeclared here.
|
|
666
|
+
%a{pure}
|
|
667
|
+
def time_zone: () -> untyped
|
|
668
|
+
|
|
669
|
+
# The wall-clock `::Time` in the zone. `utc` / `getutc` / `getgm` / `gmtime` are `Time`'s own names
|
|
670
|
+
# and stay inherited; `comparable_time` is the alias of `utc` that `Time` does not carry. Neither is
|
|
671
|
+
# `%a{pure}`: both memoise into an ivar on first call.
|
|
672
|
+
def time: () -> ::Time
|
|
673
|
+
def comparable_time: () -> ::Time
|
|
674
|
+
|
|
675
|
+
# A `TZInfo::TimezonePeriod`, undeclared here. Memoised like `time`, so not `%a{pure}`.
|
|
676
|
+
def period: () -> untyped
|
|
677
|
+
end
|
|
577
678
|
end
|
|
578
679
|
|
|
579
680
|
# ---------------------------------------------------------------
|
|
@@ -584,7 +685,11 @@ class Time
|
|
|
584
685
|
# NOT %a{pure}: already attributed nondet.time/global.read/global.write
|
|
585
686
|
# by `lib/rigor/plugin/activesupport_core_ext/effects.rb` (issue #387).
|
|
586
687
|
# Duplicating an RBS envelope here risks the two disagreeing.
|
|
587
|
-
|
|
688
|
+
#
|
|
689
|
+
# `Time.zone ? Time.zone.now : Time.now` — an `ActiveSupport::TimeWithZone` under a zone, which since
|
|
690
|
+
# #673 is declared as a `::Time` subclass so the four TWZ-only readers resolve without the rest of the
|
|
691
|
+
# chain losing its `Time` typing. See that class's own note for the zone-less cost.
|
|
692
|
+
def self.current: () -> ActiveSupport::TimeWithZone
|
|
588
693
|
def self.zone: () -> untyped # ActiveSupport::TimeZone | nil
|
|
589
694
|
def self.zone=: (String | Symbol | untyped) -> untyped
|
|
590
695
|
|
|
@@ -40,24 +40,26 @@ module Rigor
|
|
|
40
40
|
|
|
41
41
|
module_function
|
|
42
42
|
|
|
43
|
-
# @param paths
|
|
44
|
-
# @param
|
|
43
|
+
# @param paths — absolute paths to `.rb` files the project's `paths:` resolves to.
|
|
44
|
+
# @param io_boundary — the plugin's {Rigor::Plugin::IoBoundary}; every source read goes
|
|
45
|
+
# through it (#630) so each scanned file is a recorded cache dependency.
|
|
46
|
+
# @param type_aliases — the ADR-9 `:dry_type_aliases` fact published by
|
|
45
47
|
# `rigor-dry-types` when loaded. Used to resolve `value(Types::Email)` references to their
|
|
46
48
|
# underlying class. Empty when the plugin isn't loaded.
|
|
47
|
-
# @return
|
|
49
|
+
# @return frozen per-schema typed-key
|
|
48
50
|
# table. Empty when no recognisable schema declaration is found.
|
|
49
|
-
def scan(paths:, type_aliases: {})
|
|
51
|
+
def scan(paths:, io_boundary:, type_aliases: {})
|
|
50
52
|
table = {}
|
|
51
53
|
paths.each do |path|
|
|
52
|
-
scan_file(path, type_aliases).each do |schema_const, shape|
|
|
54
|
+
scan_file(path, io_boundary, type_aliases).each do |schema_const, shape|
|
|
53
55
|
table[schema_const] ||= shape
|
|
54
56
|
end
|
|
55
57
|
end
|
|
56
58
|
table.freeze
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
def scan_file(path, type_aliases)
|
|
60
|
-
source =
|
|
61
|
+
def scan_file(path, io_boundary, type_aliases)
|
|
62
|
+
source = io_boundary.read_file(path)
|
|
61
63
|
parse_result = Prism.parse(source, filepath: path)
|
|
62
64
|
return {} unless parse_result.errors.empty?
|
|
63
65
|
|
|
@@ -78,6 +78,7 @@ module Rigor
|
|
|
78
78
|
class DrySchema < Rigor::Plugin::Base
|
|
79
79
|
manifest(
|
|
80
80
|
id: "dry-schema",
|
|
81
|
+
target_gems: ["dry-schema"],
|
|
81
82
|
version: "0.1.0",
|
|
82
83
|
description: "Recognises `Dry::Schema.{Params,JSON,define} { ... }` declarations " \
|
|
83
84
|
"and publishes the per-schema typed-key table.",
|
|
@@ -130,7 +131,8 @@ module Rigor
|
|
|
130
131
|
# `paths:`, parse errors degrade silently.
|
|
131
132
|
def prepare(services)
|
|
132
133
|
type_aliases = services.fact_store.read(plugin_id: "dry-types", name: :dry_type_aliases) || {}
|
|
133
|
-
table = SchemaScanner.scan(paths: scannable_paths(services),
|
|
134
|
+
table = SchemaScanner.scan(paths: scannable_paths(services), io_boundary: io_boundary,
|
|
135
|
+
type_aliases: type_aliases)
|
|
134
136
|
return if table.empty?
|
|
135
137
|
|
|
136
138
|
services.fact_store.publish(
|
|
@@ -164,11 +166,13 @@ module Rigor
|
|
|
164
166
|
shapes[name] = entry.nil? ? nil : ResultShape.build(entry)
|
|
165
167
|
end
|
|
166
168
|
|
|
169
|
+
# ADR-45 WD1b (#613 / #630) — the classification probes go through the boundary, so an entry that
|
|
170
|
+
# is not there yet (or stops being a directory) is a recorded dependency of the scan's input set.
|
|
167
171
|
def scannable_paths(services)
|
|
168
172
|
@scannable_paths ||= services.configuration.paths.flat_map do |entry|
|
|
169
|
-
if
|
|
173
|
+
if io_boundary.directory?(entry)
|
|
170
174
|
Dir.glob(File.join(entry, "**", "*.rb"), sort: true)
|
|
171
|
-
elsif
|
|
175
|
+
elsif io_boundary.file?(entry) && entry.end_with?(".rb")
|
|
172
176
|
[entry]
|
|
173
177
|
else
|
|
174
178
|
[]
|
|
@@ -51,6 +51,7 @@ module Rigor
|
|
|
51
51
|
class DryStruct < Rigor::Plugin::Base
|
|
52
52
|
manifest(
|
|
53
53
|
id: "dry-struct",
|
|
54
|
+
target_gems: ["dry-struct"],
|
|
54
55
|
version: "0.2.0",
|
|
55
56
|
description: "Recognises dry-struct `attribute :name, T` DSL via ADR-16 Tier C; " \
|
|
56
57
|
"promotes the reader's return type through ADR-18's `returns_from_arg:` " \
|
|
@@ -42,11 +42,13 @@ module Rigor
|
|
|
42
42
|
|
|
43
43
|
module_function
|
|
44
44
|
|
|
45
|
-
# @param paths
|
|
46
|
-
# @
|
|
45
|
+
# @param paths — absolute paths to `.rb` files the project's `paths:` resolves to.
|
|
46
|
+
# @param io_boundary — the plugin's {Rigor::Plugin::IoBoundary}; every source read goes
|
|
47
|
+
# through it (#630) so each scanned file is a recorded cache dependency.
|
|
48
|
+
# @return frozen `{aliased_name => underlying_class_name}` map. Empty
|
|
47
49
|
# when no `include Dry.Types()` declaration is found.
|
|
48
|
-
def scan(paths:)
|
|
49
|
-
results = paths.flat_map { |path| scan_file(path) }
|
|
50
|
+
def scan(paths:, io_boundary:)
|
|
51
|
+
results = paths.flat_map { |path| scan_file(path, io_boundary) }
|
|
50
52
|
modules = results.map { |r| r[:module_name] }.uniq
|
|
51
53
|
return {}.freeze if modules.empty?
|
|
52
54
|
|
|
@@ -75,8 +77,8 @@ module Rigor
|
|
|
75
77
|
end
|
|
76
78
|
private_class_method :canonical_table
|
|
77
79
|
|
|
78
|
-
def scan_file(path)
|
|
79
|
-
source =
|
|
80
|
+
def scan_file(path, io_boundary)
|
|
81
|
+
source = io_boundary.read_file(path)
|
|
80
82
|
parse_result = Prism.parse(source, filepath: path)
|
|
81
83
|
return [] unless parse_result.errors.empty?
|
|
82
84
|
|
|
@@ -58,6 +58,7 @@ module Rigor
|
|
|
58
58
|
class DryTypes < Rigor::Plugin::Base
|
|
59
59
|
manifest(
|
|
60
60
|
id: "dry-types",
|
|
61
|
+
target_gems: ["dry-types"],
|
|
61
62
|
version: "0.1.0",
|
|
62
63
|
description: "Recognises `module X; include Dry.Types(); end` and publishes the alias table.",
|
|
63
64
|
produces: [:dry_type_aliases]
|
|
@@ -74,10 +75,11 @@ module Rigor
|
|
|
74
75
|
# `watch:` covers exactly the files the scan reads: one glob per project `paths:` entry, co-extensive
|
|
75
76
|
# with {#scannable_paths}. A {Cache::Descriptor::GlobEntry} digests every file matching its glob, so a
|
|
76
77
|
# content edit, a file addition, and a file removal anywhere under those paths all move the digest and
|
|
77
|
-
# invalidate the entry.
|
|
78
|
-
#
|
|
78
|
+
# invalidate the entry. Since #630 the scan ALSO reads each file through the `IoBoundary`, so the
|
|
79
|
+
# descriptor carries a per-file row beside the globs: the globs alone already covered the scan's
|
|
80
|
+
# input, and the per-file rows are what a subset or incremental run has to invalidate on.
|
|
79
81
|
producer :dry_type_aliases, watch: -> { alias_watch_globs } do |_params|
|
|
80
|
-
AliasScanner.scan(paths: scannable_paths)
|
|
82
|
+
AliasScanner.scan(paths: scannable_paths, io_boundary: io_boundary)
|
|
81
83
|
end
|
|
82
84
|
|
|
83
85
|
# Builds the (cached) alias table and publishes it via the ADR-9 fact store. `producer_value` runs the
|
|
@@ -102,11 +104,13 @@ module Rigor
|
|
|
102
104
|
# Resolves the project's `paths:` to a flat list of `.rb` files the scanner walks. Mirrors
|
|
103
105
|
# `Analysis::Runner`'s `expand_paths` floor; we don't need the runner's full exclude/sort surface
|
|
104
106
|
# because the alias table is a union — any duplicate scan is a no-op.
|
|
107
|
+
# ADR-45 WD1b (#613 / #630) — the classification probes go through the boundary, so an entry that
|
|
108
|
+
# is not there yet (or stops being a directory) is a recorded dependency of the scan's input set.
|
|
105
109
|
def scannable_paths
|
|
106
110
|
@scannable_paths ||= services.configuration.paths.flat_map do |entry|
|
|
107
|
-
if
|
|
111
|
+
if io_boundary.directory?(entry)
|
|
108
112
|
Dir.glob(File.join(entry, "**", "*.rb"), sort: true)
|
|
109
|
-
elsif
|
|
113
|
+
elsif io_boundary.file?(entry) && entry.end_with?(".rb")
|
|
110
114
|
[entry]
|
|
111
115
|
else
|
|
112
116
|
[]
|
|
@@ -122,9 +126,9 @@ module Rigor
|
|
|
122
126
|
# would read is watched, so any edit / addition / removal under those paths invalidates the cache.
|
|
123
127
|
def alias_watch_globs
|
|
124
128
|
services.configuration.paths.filter_map do |entry|
|
|
125
|
-
if
|
|
129
|
+
if io_boundary.directory?(entry)
|
|
126
130
|
[entry, "**/*.rb"]
|
|
127
|
-
elsif
|
|
131
|
+
elsif io_boundary.file?(entry) && entry.end_with?(".rb")
|
|
128
132
|
[File.dirname(entry), File.basename(entry)]
|
|
129
133
|
end
|
|
130
134
|
end
|
|
@@ -22,17 +22,19 @@ module Rigor
|
|
|
22
22
|
|
|
23
23
|
module_function
|
|
24
24
|
|
|
25
|
-
# @param paths
|
|
26
|
-
# @
|
|
25
|
+
# @param paths — absolute paths to `.rb` files the project's `paths:` resolves to.
|
|
26
|
+
# @param io_boundary — the plugin's {Rigor::Plugin::IoBoundary}; every source read goes
|
|
27
|
+
# through it (#630) so each scanned file is a recorded cache dependency.
|
|
28
|
+
# @return frozen, sorted list of recognized contract class FQNs (e.g.
|
|
27
29
|
# `["App::NewUserContract", "Types::EmailContract"]`).
|
|
28
|
-
def scan(paths:)
|
|
30
|
+
def scan(paths:, io_boundary:)
|
|
29
31
|
contracts = []
|
|
30
|
-
paths.each { |path| contracts.concat(scan_file(path)) }
|
|
32
|
+
paths.each { |path| contracts.concat(scan_file(path, io_boundary)) }
|
|
31
33
|
contracts.uniq.sort.freeze
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
def scan_file(path)
|
|
35
|
-
source =
|
|
36
|
+
def scan_file(path, io_boundary)
|
|
37
|
+
source = io_boundary.read_file(path)
|
|
36
38
|
parse_result = Prism.parse(source, filepath: path)
|
|
37
39
|
return [] unless parse_result.errors.empty?
|
|
38
40
|
|
|
@@ -50,24 +52,24 @@ module Rigor
|
|
|
50
52
|
# not a degrade-gracefully fallback, because this plugin ships no required/optional walker of
|
|
51
53
|
# its own.
|
|
52
54
|
#
|
|
53
|
-
# @return
|
|
55
|
+
# @return contract FQN => `{params: <shape>}` and/or
|
|
54
56
|
# `{json: <shape>}` (only the recognised key(s) are present; a contract with neither present
|
|
55
57
|
# contributes nothing). Each `<shape>` is exactly {DrySchema::SchemaScanner.collect_schema_shape}'s
|
|
56
58
|
# `{required:, optional:, unmodelled:}` return.
|
|
57
|
-
def scan_schema_blocks(paths:, type_aliases: {})
|
|
59
|
+
def scan_schema_blocks(paths:, io_boundary:, type_aliases: {})
|
|
58
60
|
return {} unless Rigor::Plugin.registered_for("dry-schema")
|
|
59
61
|
|
|
60
62
|
table = {}
|
|
61
63
|
paths.each do |path|
|
|
62
|
-
scan_file_schema_blocks(path, type_aliases).each do |fqn, shapes|
|
|
64
|
+
scan_file_schema_blocks(path, io_boundary, type_aliases).each do |fqn, shapes|
|
|
63
65
|
table[fqn] ||= shapes
|
|
64
66
|
end
|
|
65
67
|
end
|
|
66
68
|
table.freeze
|
|
67
69
|
end
|
|
68
70
|
|
|
69
|
-
def scan_file_schema_blocks(path, type_aliases)
|
|
70
|
-
source =
|
|
71
|
+
def scan_file_schema_blocks(path, io_boundary, type_aliases)
|
|
72
|
+
source = io_boundary.read_file(path)
|
|
71
73
|
parse_result = Prism.parse(source, filepath: path)
|
|
72
74
|
return {} unless parse_result.errors.empty?
|
|
73
75
|
|
|
@@ -44,6 +44,7 @@ module Rigor
|
|
|
44
44
|
class DryValidation < Rigor::Plugin::Base
|
|
45
45
|
manifest(
|
|
46
46
|
id: "dry-validation",
|
|
47
|
+
target_gems: ["dry-validation"],
|
|
47
48
|
version: "0.1.0",
|
|
48
49
|
description: "Recognises `class T < Dry::Validation::Contract` subclasses, publishes the " \
|
|
49
50
|
"contract FQN set, and (with rigor-dry-schema loaded) refines each contract's " \
|
|
@@ -94,7 +95,7 @@ module Rigor
|
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
def prepare(services)
|
|
97
|
-
contracts = ContractScanner.scan(paths: scannable_paths(services))
|
|
98
|
+
contracts = ContractScanner.scan(paths: scannable_paths(services), io_boundary: io_boundary)
|
|
98
99
|
unless contracts.empty?
|
|
99
100
|
services.fact_store.publish(
|
|
100
101
|
plugin_id: manifest.id,
|
|
@@ -104,7 +105,9 @@ module Rigor
|
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
type_aliases = services.fact_store.read(plugin_id: "dry-types", name: :dry_type_aliases) || {}
|
|
107
|
-
params_table = ContractScanner.scan_schema_blocks(paths: scannable_paths(services),
|
|
108
|
+
params_table = ContractScanner.scan_schema_blocks(paths: scannable_paths(services),
|
|
109
|
+
io_boundary: io_boundary,
|
|
110
|
+
type_aliases: type_aliases)
|
|
108
111
|
return if params_table.empty?
|
|
109
112
|
|
|
110
113
|
services.fact_store.publish(
|
|
@@ -137,11 +140,13 @@ module Rigor
|
|
|
137
140
|
shapes[name] = row.nil? ? nil : ParamsShape.build(row)
|
|
138
141
|
end
|
|
139
142
|
|
|
143
|
+
# ADR-45 WD1b (#613 / #630) — the classification probes go through the boundary, so an entry that
|
|
144
|
+
# is not there yet (or stops being a directory) is a recorded dependency of the scan's input set.
|
|
140
145
|
def scannable_paths(services)
|
|
141
146
|
@scannable_paths ||= services.configuration.paths.flat_map do |entry|
|
|
142
|
-
if
|
|
147
|
+
if io_boundary.directory?(entry)
|
|
143
148
|
Dir.glob(File.join(entry, "**", "*.rb"), sort: true)
|
|
144
|
-
elsif
|
|
149
|
+
elsif io_boundary.file?(entry) && entry.end_with?(".rb")
|
|
145
150
|
[entry]
|
|
146
151
|
else
|
|
147
152
|
[]
|