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
|
@@ -3,44 +3,32 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
5
|
class Activerecord < Rigor::Plugin::Base
|
|
6
|
-
# Maps a discovered ActiveRecord model class name to its
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# trips it through the standard pair.
|
|
6
|
+
# Maps a discovered ActiveRecord model class name to its resolved table name and the column set the
|
|
7
|
+
# schema attaches to that table. Marshal-clean; the cache producer round-trips it through the
|
|
8
|
+
# standard pair.
|
|
10
9
|
#
|
|
11
10
|
# Construction is two-phase by design:
|
|
12
|
-
# 1. {ModelDiscoverer} walks the project source for model
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# 2. The plugin combines those rows with the parsed
|
|
16
|
-
# {SchemaTable} to produce this index.
|
|
11
|
+
# 1. {ModelDiscoverer} walks the project source for model class declarations (direct base-class
|
|
12
|
+
# children plus transitive STI subclasses) and yields a row per model.
|
|
13
|
+
# 2. The plugin combines those rows with the parsed {SchemaTable} to produce this index.
|
|
17
14
|
#
|
|
18
|
-
# `table_name_override` is non-nil when the source contained
|
|
19
|
-
#
|
|
20
|
-
# derives from {Inflector.tableize}.
|
|
15
|
+
# `table_name_override` is non-nil when the source contained `self.table_name = "..."`. When nil,
|
|
16
|
+
# the table name derives from {Inflector.tableize}.
|
|
21
17
|
#
|
|
22
|
-
# Single-table-inheritance subclasses (`class Admin < User`)
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
# declared associations / enums / aliases / scopes /
|
|
26
|
-
# validations / callbacks.
|
|
18
|
+
# Single-table-inheritance subclasses (`class Admin < User`) carry an `sti_parent:` pointer; their
|
|
19
|
+
# {Entry} resolves its table from the root model and inherits the chain's declared associations /
|
|
20
|
+
# enums / aliases / scopes / validations / callbacks.
|
|
27
21
|
class ModelIndex
|
|
28
|
-
# `associations` is a frozen `Array<Hash>` where each
|
|
29
|
-
#
|
|
22
|
+
# `associations` is a frozen `Array<Hash>` where each row carries `{ name:, kind:, target:,
|
|
23
|
+
# nullable: }`:
|
|
30
24
|
#
|
|
31
|
-
# - `name` — String, the association method name as
|
|
32
|
-
#
|
|
33
|
-
# - `
|
|
34
|
-
# or `:collection` (`has_many`).
|
|
35
|
-
# - `target` — String, the target class name resolved
|
|
36
|
-
# either from an explicit `class_name:`
|
|
25
|
+
# - `name` — String, the association method name as the user invokes it (`"posts"`).
|
|
26
|
+
# - `kind` — `:singular` (`belongs_to` / `has_one`) or `:collection` (`has_many`).
|
|
27
|
+
# - `target` — String, the target class name resolved either from an explicit `class_name:`
|
|
37
28
|
# option or via {Inflector.classify}.
|
|
38
|
-
# - `nullable` — Boolean; whether a `:singular` accessor
|
|
39
|
-
#
|
|
40
|
-
# `
|
|
41
|
-
# since Rails 5) unless `optional: true` /
|
|
42
|
-
# `required: false`. Meaningless for
|
|
43
|
-
# `:collection` rows.
|
|
29
|
+
# - `nullable` — Boolean; whether a `:singular` accessor can return `nil`. `has_one` → `true`;
|
|
30
|
+
# `belongs_to` → `false` (required by default since Rails 5) unless `optional: true`
|
|
31
|
+
# / `required: false`. Meaningless for `:collection` rows.
|
|
44
32
|
Entry = Struct.new(:class_name, :table_name, :columns, :associations,
|
|
45
33
|
:enums, :scopes, :validations, :callbacks, :aliases,
|
|
46
34
|
keyword_init: true) do
|
|
@@ -71,8 +59,7 @@ module Rigor
|
|
|
71
59
|
# `scopes` is `Array<scope_name>`.
|
|
72
60
|
def scope?(name) = scopes.include?(name.to_s)
|
|
73
61
|
|
|
74
|
-
# `validations` is `Array<attribute_name>` covering
|
|
75
|
-
# both `validates :name, ...` and the
|
|
62
|
+
# `validations` is `Array<attribute_name>` covering both `validates :name, ...` and the
|
|
76
63
|
# `validates_*_of :name, ...` shorthand families.
|
|
77
64
|
def validation?(name) = validations.include?(name.to_s)
|
|
78
65
|
def validated_attributes = validations
|
|
@@ -80,8 +67,8 @@ module Rigor
|
|
|
80
67
|
# `callbacks` is `Array<{ name:, callback: }>`.
|
|
81
68
|
def callback_targets = callbacks.map { |c| c[:name] }
|
|
82
69
|
|
|
83
|
-
# `aliases` is `Hash<alias_name => target_attribute>`
|
|
84
|
-
#
|
|
70
|
+
# `aliases` is `Hash<alias_name => target_attribute>` populated from `alias_attribute`
|
|
71
|
+
# declarations.
|
|
85
72
|
def alias?(name) = aliases.key?(name.to_s)
|
|
86
73
|
def resolve_alias(name) = aliases[name.to_s]
|
|
87
74
|
end
|
|
@@ -101,22 +88,20 @@ module Rigor
|
|
|
101
88
|
def class_names = entries.keys
|
|
102
89
|
def empty? = entries.empty?
|
|
103
90
|
|
|
104
|
-
def self.build(model_rows:, schema_table:)
|
|
91
|
+
def self.build(model_rows:, schema_table:, type_override_columns: nil)
|
|
105
92
|
rows_by_name = model_rows.to_h { |row| [row.fetch(:class_name), row] }
|
|
93
|
+
overrides = type_override_columns || []
|
|
106
94
|
|
|
107
95
|
entries = model_rows.each_with_object({}) do |row, acc|
|
|
108
96
|
class_name = row.fetch(:class_name)
|
|
109
|
-
# The STI ancestry chain, root → self. For a plain
|
|
110
|
-
# (non-STI) model this is just `[row]`.
|
|
97
|
+
# The STI ancestry chain, root → self. For a plain (non-STI) model this is just `[row]`.
|
|
111
98
|
chain = sti_chain(row, rows_by_name)
|
|
112
99
|
table_name = sti_table_name(chain)
|
|
113
|
-
columns = schema_table.columns_for(table_name) || []
|
|
100
|
+
columns = apply_type_overrides(schema_table.columns_for(table_name) || [], overrides)
|
|
114
101
|
|
|
115
|
-
# STI children inherit their ancestors' declared
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
# `where(<parent-association>: ...)` on the child
|
|
119
|
-
# would surface as a false `unknown-column`.
|
|
102
|
+
# STI children inherit their ancestors' declared associations / enums / aliases / scopes /
|
|
103
|
+
# validations / callbacks. Without the merge a `where(<parent-association>: ...)` on the
|
|
104
|
+
# child would surface as a false `unknown-column`.
|
|
120
105
|
acc[class_name] = Entry.new(
|
|
121
106
|
class_name: class_name,
|
|
122
107
|
table_name: table_name,
|
|
@@ -132,8 +117,25 @@ module Rigor
|
|
|
132
117
|
new(entries.freeze)
|
|
133
118
|
end
|
|
134
119
|
|
|
135
|
-
#
|
|
136
|
-
#
|
|
120
|
+
# Remaps every type-overridden column's `ruby_type` to `"Object"` so instance-side column narrowing
|
|
121
|
+
# declines to narrow it (the `"Object"` case in `ruby_type_to_type`). A `serialize` / `mount_uploader`
|
|
122
|
+
# / custom-`attribute` column reads as a rich object at runtime, not its SQL scalar, so narrowing it
|
|
123
|
+
# to e.g. `String` false-positives on the object's methods (`note.position.diff_refs`). The column
|
|
124
|
+
# stays in the set, so `where(col: ...)` existence validation is unaffected — only its value type.
|
|
125
|
+
def self.apply_type_overrides(columns, overrides)
|
|
126
|
+
return columns if overrides.empty?
|
|
127
|
+
|
|
128
|
+
columns.map do |column|
|
|
129
|
+
next column unless overrides.include?(column.name)
|
|
130
|
+
|
|
131
|
+
SchemaTable::Column.new(
|
|
132
|
+
name: column.name, type: column.type, ruby_type: "Object", array: column.array
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# The STI ancestry chain for a row, ordered root → self. Walks `sti_parent` pointers, guarding
|
|
138
|
+
# against a cycle.
|
|
137
139
|
def self.sti_chain(row, rows_by_name, seen = [])
|
|
138
140
|
class_name = row.fetch(:class_name)
|
|
139
141
|
parent_name = row[:sti_parent]
|
|
@@ -145,9 +147,8 @@ module Rigor
|
|
|
145
147
|
sti_chain(parent, rows_by_name, seen + [class_name]) + [row]
|
|
146
148
|
end
|
|
147
149
|
|
|
148
|
-
# The effective table name for an STI chain: the nearest
|
|
149
|
-
#
|
|
150
|
-
# root, else the name inflected from the root class.
|
|
150
|
+
# The effective table name for an STI chain: the nearest explicit `self.table_name =` override
|
|
151
|
+
# walking leaf → root, else the name inflected from the root class.
|
|
151
152
|
def self.sti_table_name(chain)
|
|
152
153
|
chain.reverse_each do |row|
|
|
153
154
|
override = row[:table_name_override]
|
|
@@ -156,18 +157,16 @@ module Rigor
|
|
|
156
157
|
Rigor::Plugin::Inflector.tableize(strip_leading_namespace(chain.first.fetch(:class_name)))
|
|
157
158
|
end
|
|
158
159
|
|
|
159
|
-
# Dedups association-style rows by `:name`, keeping the
|
|
160
|
-
#
|
|
161
|
-
# inherited ancestor row.
|
|
160
|
+
# Dedups association-style rows by `:name`, keeping the LAST occurrence so a child redeclaration
|
|
161
|
+
# overrides the inherited ancestor row.
|
|
162
162
|
def self.merge_named_rows(rows)
|
|
163
163
|
seen = {}
|
|
164
164
|
rows.each { |row| seen[row[:name]] = row }
|
|
165
165
|
seen.values.map(&:freeze).freeze
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
-
# `Hash<column => Array<value>>` merged across the STI
|
|
169
|
-
#
|
|
170
|
-
# overrides the ancestor's value list.
|
|
168
|
+
# `Hash<column => Array<value>>` merged across the STI chain; a child redeclaration of the same
|
|
169
|
+
# enum column overrides the ancestor's value list.
|
|
171
170
|
def self.merge_enums(chain)
|
|
172
171
|
chain.each_with_object({}) do |row, acc|
|
|
173
172
|
(row[:enums] || {}).each { |col, values| acc[col] = Array(values).map(&:freeze).freeze }
|
|
@@ -181,10 +180,8 @@ module Rigor
|
|
|
181
180
|
end.freeze
|
|
182
181
|
end
|
|
183
182
|
|
|
184
|
-
# `::User` → `User`. The discoverer might prefix with
|
|
185
|
-
#
|
|
186
|
-
# resolved the path; the table-name derivation uses the
|
|
187
|
-
# short form regardless.
|
|
183
|
+
# `::User` → `User`. The discoverer might prefix with `::` for top-level constants depending on
|
|
184
|
+
# how it resolved the path; the table-name derivation uses the short form regardless.
|
|
188
185
|
def self.strip_leading_namespace(name)
|
|
189
186
|
name.start_with?("::") ? name[2..] : name
|
|
190
187
|
end
|
|
@@ -21,30 +21,23 @@ module Rigor
|
|
|
21
21
|
# end
|
|
22
22
|
# end
|
|
23
23
|
#
|
|
24
|
-
# `t.references "x"` becomes an `x_id` integer column
|
|
25
|
-
#
|
|
26
|
-
# column
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# whose type symbol is the method name — unknown types degrade
|
|
32
|
-
# to `Object` per `SchemaTable.ruby_type_for` rather than being
|
|
33
|
-
# dropped. Only the structural calls in {NON_COLUMN_METHODS}
|
|
34
|
-
# (indexes, constraints, foreign keys) are skipped.
|
|
24
|
+
# `t.references "x"` becomes an `x_id` integer column (foreign-key indices and constraints are
|
|
25
|
+
# ignored — only the column shape matters for type inference); add a `polymorphic: true` option and
|
|
26
|
+
# an `x_type` string column is emitted too. `t.timestamps` adds `created_at` and `updated_at`
|
|
27
|
+
# datetime columns. `t.column "x", :type` is the generic column form. Any other `t.<method> "name"`
|
|
28
|
+
# call is treated as a column whose type symbol is the method name — unknown types degrade to
|
|
29
|
+
# `Object` per `SchemaTable.ruby_type_for` rather than being dropped. Only the structural calls in
|
|
30
|
+
# {NON_COLUMN_METHODS} (indexes, constraints, foreign keys) are skipped.
|
|
35
31
|
#
|
|
36
|
-
# Designed for the Prism interpretation pattern from
|
|
37
|
-
#
|
|
32
|
+
# Designed for the Prism interpretation pattern from rigor-lisp-eval — recursive descent on the AST,
|
|
33
|
+
# no eval.
|
|
38
34
|
class SchemaParser
|
|
39
35
|
TIMESTAMPS_COLUMNS = %w[created_at updated_at].freeze
|
|
40
36
|
|
|
41
|
-
# Structural `t.<method>` calls inside a `create_table`
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# so a real column is never silently dropped — a dropped
|
|
46
|
-
# column turns every query against it into a false
|
|
47
|
-
# `unknown-column` diagnostic.
|
|
37
|
+
# Structural `t.<method>` calls inside a `create_table` block that declare indexes / constraints
|
|
38
|
+
# rather than columns. Everything NOT in this set that carries a literal column name is treated as
|
|
39
|
+
# a column declaration so a real column is never silently dropped — a dropped column turns every
|
|
40
|
+
# query against it into a false `unknown-column` diagnostic.
|
|
48
41
|
NON_COLUMN_METHODS = %i[
|
|
49
42
|
index check_constraint exclusion_constraint
|
|
50
43
|
unique_constraint foreign_key primary_keys
|
|
@@ -114,10 +107,9 @@ module Rigor
|
|
|
114
107
|
false
|
|
115
108
|
end
|
|
116
109
|
|
|
117
|
-
# Walks the block body collecting `t.<method>(...)` calls.
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
# parser falls back silently.
|
|
110
|
+
# Walks the block body collecting `t.<method>(...)` calls. Skips nested blocks (e.g. inside
|
|
111
|
+
# `if`-conditioned columns) only at the top level — for richer schema constructs the parser falls
|
|
112
|
+
# back silently.
|
|
121
113
|
def collect_column_calls(node, &)
|
|
122
114
|
return if node.nil?
|
|
123
115
|
|
|
@@ -139,13 +131,11 @@ module Rigor
|
|
|
139
131
|
when :column
|
|
140
132
|
parse_generic_column(call_node)
|
|
141
133
|
else
|
|
142
|
-
# Structural DSL call (index / constraint / FK) —
|
|
143
|
-
# not a column.
|
|
134
|
+
# Structural DSL call (index / constraint / FK) — not a column.
|
|
144
135
|
return nil if NON_COLUMN_METHODS.include?(method)
|
|
145
136
|
|
|
146
|
-
# Any other `t.<method> "name"` is a column. The
|
|
147
|
-
#
|
|
148
|
-
# degrade to `Object` rather than dropping the column.
|
|
137
|
+
# Any other `t.<method> "name"` is a column. The method name is the type symbol; unknown
|
|
138
|
+
# types degrade to `Object` rather than dropping the column.
|
|
149
139
|
parse_typed_column(method, call_node)
|
|
150
140
|
end
|
|
151
141
|
end
|
|
@@ -162,11 +152,9 @@ module Rigor
|
|
|
162
152
|
)
|
|
163
153
|
end
|
|
164
154
|
|
|
165
|
-
# `t.references "x"` adds an `x_id` integer column.
|
|
166
|
-
# `
|
|
167
|
-
#
|
|
168
|
-
# `where(x_type: ...)` on the polymorphic owner surfaces
|
|
169
|
-
# as a false `unknown-column`.
|
|
155
|
+
# `t.references "x"` adds an `x_id` integer column. `t.references "x", polymorphic: true`
|
|
156
|
+
# additionally adds an `x_type` string column — without it every `where(x_type: ...)` on the
|
|
157
|
+
# polymorphic owner surfaces as a false `unknown-column`.
|
|
170
158
|
def parse_references_column(call_node)
|
|
171
159
|
name = string_argument(call_node, 0)
|
|
172
160
|
return nil if name.nil?
|
|
@@ -184,10 +172,9 @@ module Rigor
|
|
|
184
172
|
keyword_true?(call_node, :polymorphic)
|
|
185
173
|
end
|
|
186
174
|
|
|
187
|
-
# Returns true iff `call_node` has a `name: true` keyword
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
# column) and `t.references "x", polymorphic: true`.
|
|
175
|
+
# Returns true iff `call_node` has a `name: true` keyword argument. Used to detect schema
|
|
176
|
+
# modifiers like `t.bigint "status_ids", array: true` (Postgres array column) and `t.references
|
|
177
|
+
# "x", polymorphic: true`.
|
|
191
178
|
def keyword_true?(call_node, name)
|
|
192
179
|
return false if call_node.arguments.nil?
|
|
193
180
|
|
|
@@ -204,9 +191,8 @@ module Rigor
|
|
|
204
191
|
false
|
|
205
192
|
end
|
|
206
193
|
|
|
207
|
-
# `t.column "name", "type"` / `t.column "name", :type` —
|
|
208
|
-
# the
|
|
209
|
-
# second argument; an absent type degrades to `:string`.
|
|
194
|
+
# `t.column "name", "type"` / `t.column "name", :type` — the explicit generic column form. The
|
|
195
|
+
# type lives in the second argument; an absent type degrades to `:string`.
|
|
210
196
|
def parse_generic_column(call_node)
|
|
211
197
|
name = string_argument(call_node, 0)
|
|
212
198
|
return nil if name.nil?
|
|
@@ -3,18 +3,15 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
5
|
class Activerecord < Rigor::Plugin::Base
|
|
6
|
-
# Parsed `db/schema.rb`. Maps each table name to its column
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# without a custom serialize / deserialize pair.
|
|
6
|
+
# Parsed `db/schema.rb`. Maps each table name to its column set; each column carries its declared
|
|
7
|
+
# type. Marshal-clean by construction so the cache producer can round-trip it without a custom
|
|
8
|
+
# serialize / deserialize pair.
|
|
10
9
|
#
|
|
11
|
-
# The mapping from Rails column types to Ruby class names is
|
|
12
|
-
#
|
|
13
|
-
# `:
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# ltree, hstore, custom) fall back to `Object` so the
|
|
17
|
-
# plugin stays silent rather than guessing.
|
|
10
|
+
# The mapping from Rails column types to Ruby class names is deliberately conservative —
|
|
11
|
+
# `:string`/`:text` → `String`, `:integer`/`:bigint` → `Integer`, `:boolean` → `bool`,
|
|
12
|
+
# `:datetime`/`:timestamp` → `Time`, `:date` → `Date`, `:decimal`/`:float` → `Float`. Exotic types
|
|
13
|
+
# (json, jsonb, ltree, hstore, custom) fall back to `Object` so the plugin stays silent rather than
|
|
14
|
+
# guessing.
|
|
18
15
|
class SchemaTable
|
|
19
16
|
Column = Struct.new(:name, :type, :ruby_type, :array, keyword_init: true) do
|
|
20
17
|
def to_h = { name: name, type: type, ruby_type: ruby_type, array: array }
|
|
@@ -40,21 +37,17 @@ module Rigor
|
|
|
40
37
|
binary: "String",
|
|
41
38
|
json: "Object",
|
|
42
39
|
jsonb: "Object",
|
|
43
|
-
# PostgreSQL-flavoured string-ish column types Rails
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# them here keeps the column out of the "unknown type
|
|
47
|
-
# → dropped column → false unknown-column" path.
|
|
40
|
+
# PostgreSQL-flavoured string-ish column types Rails schemas commonly carry. `uuid` / `citext` /
|
|
41
|
+
# `inet` all behave as `String` for query purposes; mapping them here keeps the column out of
|
|
42
|
+
# the "unknown type → dropped column → false unknown-column" path.
|
|
48
43
|
uuid: "String",
|
|
49
44
|
citext: "String",
|
|
50
45
|
inet: "String"
|
|
51
46
|
}.freeze
|
|
52
47
|
|
|
53
|
-
# Implicit columns that every Rails table has unless the
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
# implicit `id` column from the parser, but most apps
|
|
57
|
-
# never disable it.
|
|
48
|
+
# Implicit columns that every Rails table has unless the schema explicitly opts out. The plugin
|
|
49
|
+
# assumes these exist; users who run `create_table id: false` get no implicit `id` column from
|
|
50
|
+
# the parser, but most apps never disable it.
|
|
58
51
|
IMPLICIT_COLUMNS = [
|
|
59
52
|
Column.new(name: "id", type: :integer, ruby_type: "Integer").freeze
|
|
60
53
|
].freeze
|
|
@@ -86,9 +79,8 @@ module Rigor
|
|
|
86
79
|
|
|
87
80
|
def table_names = tables.keys
|
|
88
81
|
|
|
89
|
-
# Maps a Rails column type symbol to its Ruby class name.
|
|
90
|
-
#
|
|
91
|
-
# that as "do not narrow" (silent on unknowns).
|
|
82
|
+
# Maps a Rails column type symbol to its Ruby class name. Returns "Object" for unknown types —
|
|
83
|
+
# the analyzer treats that as "do not narrow" (silent on unknowns).
|
|
92
84
|
def self.ruby_type_for(column_type)
|
|
93
85
|
RUBY_TYPE_MAPPING.fetch(column_type.to_sym, "Object")
|
|
94
86
|
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "schema_table"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
class Activerecord < Rigor::Plugin::Base
|
|
8
|
+
# Parses a PostgreSQL `db/structure.sql` (the `schema_format = :sql` schema dump) into the same
|
|
9
|
+
# {SchemaTable} the Ruby-DSL {SchemaParser} produces, so a project that commits `structure.sql`
|
|
10
|
+
# instead of `schema.rb` is no longer inert (GitLab-class apps use `structure.sql`; without this the
|
|
11
|
+
# plugin skips every AR call check and — worse — degrades ordinary relation chains to `Array`,
|
|
12
|
+
# cascading false diagnostics).
|
|
13
|
+
#
|
|
14
|
+
# The dump is regular enough to read line-by-line without a SQL parser. `pg_dump` emits, per table:
|
|
15
|
+
#
|
|
16
|
+
# CREATE TABLE namespaces (
|
|
17
|
+
# id bigint NOT NULL,
|
|
18
|
+
# name character varying NOT NULL,
|
|
19
|
+
# visibility_level integer DEFAULT 20 NOT NULL,
|
|
20
|
+
# tag_ids bigint[],
|
|
21
|
+
# created_at timestamp without time zone
|
|
22
|
+
# );
|
|
23
|
+
#
|
|
24
|
+
# Load-bearing regularities this relies on: column names / types are lowercase, column modifiers
|
|
25
|
+
# (`NOT NULL`, `DEFAULT …`, `GENERATED …`, `COLLATE …`) are UPPERCASE, one column per line, the body
|
|
26
|
+
# closes on a line starting with `)`. So the type is the run of tokens before the first
|
|
27
|
+
# uppercase-initial token, and a table-level constraint line (`CONSTRAINT …`, `PRIMARY KEY …`) is
|
|
28
|
+
# recognised by its uppercase leading keyword and skipped. Anything it cannot map (custom enum,
|
|
29
|
+
# `tsvector`, `ltree`) degrades to `Object` via {SchemaTable.ruby_type_for} — never a dropped column,
|
|
30
|
+
# which would turn every query on it into a false `unknown-column`.
|
|
31
|
+
class StructureSqlParser
|
|
32
|
+
# Normalised PostgreSQL type (lowercased, length/precision + `[]` stripped) → the Rails column-type
|
|
33
|
+
# symbol {SchemaTable.ruby_type_for} already understands. Unmapped types fall through to their own
|
|
34
|
+
# symbol, which `ruby_type_for` maps to `Object` (the safe "do not narrow" default).
|
|
35
|
+
PG_TYPE_TO_RAILS = {
|
|
36
|
+
"bigint" => :bigint, "int8" => :bigint, "bigserial" => :bigint,
|
|
37
|
+
"integer" => :integer, "int" => :integer, "int4" => :integer, "serial" => :integer,
|
|
38
|
+
"smallint" => :integer, "int2" => :integer, "smallserial" => :integer,
|
|
39
|
+
"boolean" => :boolean, "bool" => :boolean,
|
|
40
|
+
"text" => :text,
|
|
41
|
+
"character varying" => :string, "varchar" => :string,
|
|
42
|
+
"character" => :string, "char" => :string, "name" => :string, "citext" => :citext,
|
|
43
|
+
"timestamp without time zone" => :datetime, "timestamp with time zone" => :datetime,
|
|
44
|
+
"timestamp" => :datetime, "timestamptz" => :datetime,
|
|
45
|
+
"date" => :date,
|
|
46
|
+
"time without time zone" => :time, "time with time zone" => :time, "time" => :time,
|
|
47
|
+
"numeric" => :decimal, "decimal" => :decimal, "money" => :decimal,
|
|
48
|
+
"double precision" => :float, "real" => :float, "float8" => :float, "float4" => :float,
|
|
49
|
+
"bytea" => :binary,
|
|
50
|
+
"json" => :json, "jsonb" => :jsonb,
|
|
51
|
+
"uuid" => :uuid, "inet" => :inet, "cidr" => :string, "macaddr" => :string
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
# Uppercase leading keywords that mark a table-level constraint line rather than a column.
|
|
55
|
+
CONSTRAINT_KEYWORDS = %w[CONSTRAINT PRIMARY UNIQUE CHECK FOREIGN EXCLUDE LIKE PARTITION].freeze
|
|
56
|
+
|
|
57
|
+
# @param source [String] contents of `db/structure.sql`
|
|
58
|
+
# @return [SchemaTable]
|
|
59
|
+
def self.parse(source)
|
|
60
|
+
new.parse(source)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def parse(source)
|
|
64
|
+
tables = {}
|
|
65
|
+
each_create_table(source.to_s) do |table_name, body_lines|
|
|
66
|
+
columns = parse_columns(body_lines)
|
|
67
|
+
tables[table_name] = columns unless columns.empty?
|
|
68
|
+
end
|
|
69
|
+
SchemaTable.new(tables.freeze)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# Yields `[table_name, body_lines]` for every `CREATE TABLE … (` … `)` block. Body lines are the raw
|
|
75
|
+
# lines between the opening `(` line and the closing `)` line.
|
|
76
|
+
def each_create_table(source)
|
|
77
|
+
lines = source.lines
|
|
78
|
+
index = 0
|
|
79
|
+
while index < lines.length
|
|
80
|
+
match = lines[index].match(/\ACREATE (?:UNLOGGED |TEMPORARY )?TABLE (?:IF NOT EXISTS )?(\S+)\s*\(/)
|
|
81
|
+
unless match
|
|
82
|
+
index += 1
|
|
83
|
+
next
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
table_name = normalize_table_name(match[1])
|
|
87
|
+
index += 1
|
|
88
|
+
body = []
|
|
89
|
+
while index < lines.length && lines[index] !~ /\A\s*\)/
|
|
90
|
+
body << lines[index]
|
|
91
|
+
index += 1
|
|
92
|
+
end
|
|
93
|
+
yield(table_name, body) if table_name
|
|
94
|
+
index += 1 # step past the closing `)` line
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Strips quotes and a leading schema qualifier. Only the default `public` schema (or an unqualified
|
|
99
|
+
# name) is accepted; other schemas (`gitlab_partitions_dynamic.*`, etc.) are partitions of a base
|
|
100
|
+
# table already emitted unqualified, so returning nil skips them without losing a real table.
|
|
101
|
+
def normalize_table_name(raw)
|
|
102
|
+
name = raw.delete('"')
|
|
103
|
+
if name.include?(".")
|
|
104
|
+
schema, table = name.split(".", 2)
|
|
105
|
+
return nil unless schema == "public"
|
|
106
|
+
|
|
107
|
+
name = table
|
|
108
|
+
end
|
|
109
|
+
name.empty? ? nil : name
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def parse_columns(body_lines)
|
|
113
|
+
columns = {}
|
|
114
|
+
# A column's `DEFAULT` may carry a multi-line single-quoted string (a serialized YAML default,
|
|
115
|
+
# `DEFAULT '--- []\n'::character varying`). The column name + type sit before the `DEFAULT`, so
|
|
116
|
+
# the first physical line still parses correctly; the continuation lines must be skipped or they
|
|
117
|
+
# read as bogus columns (`'::character varying`). A line with an ODD count of single quotes opens
|
|
118
|
+
# (or closes) such a string — PG doubles a literal quote (`''`), so escapes stay even and only a
|
|
119
|
+
# genuinely unterminated string toggles the state.
|
|
120
|
+
in_string = false
|
|
121
|
+
body_lines.each do |raw|
|
|
122
|
+
if in_string
|
|
123
|
+
in_string = false if raw.count("'").odd?
|
|
124
|
+
next
|
|
125
|
+
end
|
|
126
|
+
in_string = true if raw.count("'").odd?
|
|
127
|
+
|
|
128
|
+
line = raw.strip.chomp(",")
|
|
129
|
+
next if line.empty?
|
|
130
|
+
|
|
131
|
+
name, rest = line.split(/\s+/, 2)
|
|
132
|
+
next if name.nil? || rest.nil?
|
|
133
|
+
next if CONSTRAINT_KEYWORDS.include?(name) # a table-level constraint, not a column
|
|
134
|
+
|
|
135
|
+
column = build_column(name.delete('"'), rest)
|
|
136
|
+
columns[column.name] = column if column
|
|
137
|
+
end
|
|
138
|
+
columns.freeze
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def build_column(name, rest)
|
|
142
|
+
type_str, array = extract_type(rest)
|
|
143
|
+
return nil if type_str.empty?
|
|
144
|
+
|
|
145
|
+
rails_type = PG_TYPE_TO_RAILS.fetch(type_str, type_str.to_sym)
|
|
146
|
+
SchemaTable::Column.new(
|
|
147
|
+
name: name,
|
|
148
|
+
type: rails_type,
|
|
149
|
+
ruby_type: SchemaTable.ruby_type_for(rails_type),
|
|
150
|
+
array: array
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# The type is every token before the first uppercase-initial token (a column modifier such as
|
|
155
|
+
# `NOT`, `NULL`, `DEFAULT`, `GENERATED`, `COLLATE`). A trailing `[]` marks a Postgres array column;
|
|
156
|
+
# a `(length[,scale])` specifier is dropped. Returns `[normalized_type, array?]`.
|
|
157
|
+
def extract_type(rest)
|
|
158
|
+
type_tokens = []
|
|
159
|
+
rest.split(/\s+/).each do |token|
|
|
160
|
+
break if token.match?(/\A[A-Z]/)
|
|
161
|
+
|
|
162
|
+
type_tokens << token
|
|
163
|
+
end
|
|
164
|
+
type_str = type_tokens.join(" ")
|
|
165
|
+
array = type_str.include?("[]")
|
|
166
|
+
type_str = type_str.gsub("[]", "").gsub(/\([0-9,\s]*\)/, "").strip.downcase
|
|
167
|
+
[type_str, array]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|