rigortype 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/15-type-protection-coverage.md +6 -4
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +96 -151
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +19 -26
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +43 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +27 -17
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +176 -291
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +774 -1152
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +509 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -817
- data/lib/rigor/inference/statement_evaluator.rb +706 -1111
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/scope.rbs +6 -0
- metadata +2 -1
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Convenience entry point. `require "rigor-rails"` requires
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# patterns, ad-hoc scripts) do not have to list seven require
|
|
7
|
-
# lines.
|
|
3
|
+
# Convenience entry point. `require "rigor-rails"` requires every Tier 1+2 Rails ecosystem plugin in one go,
|
|
4
|
+
# so projects that prefer a single require statement (some `spec_helper` patterns, ad-hoc scripts) do not
|
|
5
|
+
# have to list seven require lines.
|
|
8
6
|
#
|
|
9
|
-
# Note: requiring this entry point does NOT mark every plugin as
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# users still control which plugins participate in analysis via
|
|
14
|
-
# `.rigor.yml`.
|
|
7
|
+
# Note: requiring this entry point does NOT mark every plugin as active. The Rigor plugin loader walks
|
|
8
|
+
# `.rigor.yml`'s `plugins:` list and instantiates only the plugins enumerated there. This is per ADR-12
|
|
9
|
+
# WD1's "Gemfile-convenience meta-gem" pattern — users still control which plugins participate in analysis
|
|
10
|
+
# via `.rigor.yml`.
|
|
15
11
|
#
|
|
16
|
-
# Sub-plugins ARE registered with `Rigor::Plugin` when this file
|
|
17
|
-
#
|
|
18
|
-
# call); the loader's lookup phase finds them by id when listed.
|
|
12
|
+
# Sub-plugins ARE registered with `Rigor::Plugin` when this file loads (each gem's entry point side-effects
|
|
13
|
+
# a `Plugin.register` call); the loader's lookup phase finds them by id when listed.
|
|
19
14
|
#
|
|
20
|
-
# Adding the gem to a project's Gemfile without listing any
|
|
21
|
-
#
|
|
22
|
-
# `Bundler.require`, but no plugin's `init` / `prepare` / hooks
|
|
23
|
-
# run.
|
|
15
|
+
# Adding the gem to a project's Gemfile without listing any plugin in `.rigor.yml` is harmless: the requires
|
|
16
|
+
# happen on `Bundler.require`, but no plugin's `init` / `prepare` / hooks run.
|
|
24
17
|
|
|
25
18
|
require "rigor-rails-routes"
|
|
26
19
|
require "rigor-rails-i18n"
|
|
@@ -6,85 +6,64 @@ require "prism"
|
|
|
6
6
|
module Rigor
|
|
7
7
|
module Plugin
|
|
8
8
|
class RailsI18n < Rigor::Plugin::Base
|
|
9
|
-
# Walks a parsed file's AST looking for `t(...)` /
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# key (variable, expression) are silently passed through —
|
|
13
|
-
# the plugin only validates what it can prove statically.
|
|
9
|
+
# Walks a parsed file's AST looking for `t(...)` / `I18n.t(...)` / `I18n.translate(...)` calls with a
|
|
10
|
+
# literal-string first argument. Calls with a non-literal key (variable, expression) are silently
|
|
11
|
+
# passed through — the plugin only validates what it can prove statically.
|
|
14
12
|
#
|
|
15
13
|
# ## What gets emitted per recognised call
|
|
16
14
|
#
|
|
17
|
-
# - `plugin.rails-i18n.translation-call` (info) names the
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# - `plugin.rails-i18n.
|
|
24
|
-
#
|
|
25
|
-
# from at least one. Suppressed when the call passes
|
|
26
|
-
# `default:` (the user has signalled they're aware of
|
|
27
|
-
# the partial coverage).
|
|
28
|
-
# - `plugin.rails-i18n.wrong-interpolation` (error) when
|
|
29
|
-
# the call's interpolation hash uses keys that don't
|
|
30
|
-
# match the value's `%{var}` placeholders, or omits a
|
|
31
|
-
# required placeholder.
|
|
15
|
+
# - `plugin.rails-i18n.translation-call` (info) names the key and the locales it resolves in.
|
|
16
|
+
# - `plugin.rails-i18n.unknown-key` (error) when the key is missing from every loaded locale; the
|
|
17
|
+
# message includes a did-you-mean suggestion drawn from the index.
|
|
18
|
+
# - `plugin.rails-i18n.missing-locale` (warning) when the key resolves in some configured locales but
|
|
19
|
+
# is absent from at least one. Suppressed when the call passes `default:` (the user has signalled
|
|
20
|
+
# they're aware of the partial coverage).
|
|
21
|
+
# - `plugin.rails-i18n.wrong-interpolation` (error) when the call's interpolation hash uses keys that
|
|
22
|
+
# don't match the value's `%{var}` placeholders, or omits a required placeholder.
|
|
32
23
|
module Analyzer
|
|
33
24
|
TRANSLATE_METHODS = %i[t translate].freeze
|
|
34
25
|
|
|
35
|
-
# Methods that are always I18n receivers (`I18n.t`,
|
|
36
|
-
# `::I18n.t`).
|
|
26
|
+
# Methods that are always I18n receivers (`I18n.t`, `::I18n.t`).
|
|
37
27
|
I18N_RECEIVER_NAMES = %w[I18n ::I18n].freeze
|
|
38
28
|
|
|
39
|
-
# Matches controller file paths so lazy keys (`.key`)
|
|
40
|
-
#
|
|
41
|
-
# Captures the path segment between `controllers/` and
|
|
29
|
+
# Matches controller file paths so lazy keys (`.key`) can be expanded to
|
|
30
|
+
# `<controller_scope>.<action>.<key>`. Captures the path segment between `controllers/` and
|
|
42
31
|
# `_controller.rb` (e.g. `users`, `admin/users`).
|
|
43
32
|
CONTROLLER_PATH_RE = %r{(?:^|/)controllers/(.+)_controller\.rb$}
|
|
44
33
|
|
|
45
|
-
# Matches Rails view-template file paths to derive the
|
|
46
|
-
#
|
|
47
|
-
# lazy `t('.key')` lookups inside a template.
|
|
34
|
+
# Matches Rails view-template file paths to derive the I18n "virtual path" — the scope that Rails
|
|
35
|
+
# uses for lazy `t('.key')` lookups inside a template.
|
|
48
36
|
#
|
|
49
|
-
# Captures the path segment between `views/` and the
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
# `_form.html.erb` as "form", not "_form".
|
|
37
|
+
# Captures the path segment between `views/` and the format+variant+handler suffix, then strips a
|
|
38
|
+
# leading underscore from each segment — Rails templates resolve `_form.html.erb` as "form", not
|
|
39
|
+
# "_form".
|
|
53
40
|
#
|
|
54
41
|
# app/views/setting/index.html.erb → setting.index
|
|
55
42
|
# app/views/admin/users/new.html.erb → admin.users.new
|
|
56
43
|
# app/views/home/index.html+mobile.erb → home.index
|
|
57
44
|
# app/views/users/_form.html.erb → users.form
|
|
58
45
|
#
|
|
59
|
-
# The view-scope lazy expansion replaces the action
|
|
60
|
-
# part with the view's virtual path:
|
|
46
|
+
# The view-scope lazy expansion replaces the action part with the view's virtual path:
|
|
61
47
|
# `<%= t('.title') %>` → `setting.index.title`
|
|
62
48
|
VIEW_SCOPE_RE = %r{views/(.+?)\.(?:\w+)(?:\+\w+)?\.\w+\z}
|
|
63
49
|
|
|
64
|
-
# Regex to extract lazy-key arguments from ERB / HTML
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
# leading dot on the key string. Captures only the key
|
|
68
|
-
# part after the dot.
|
|
50
|
+
# Regex to extract lazy-key arguments from ERB / HTML template content. Matches `t('.key')`,
|
|
51
|
+
# `t(".key")`, `I18n.t('.key')`, and `I18n.translate('.key')` with a leading dot on the key string.
|
|
52
|
+
# Captures only the key part after the dot.
|
|
69
53
|
LAZY_T_KEY_RE = /\b(?:I18n\.)?(?:t|translate)\s*\(\s*(?:"\.([^"\\]*)"|'\.([^'\\]*)')/
|
|
70
54
|
|
|
71
|
-
# Reserved option keys — these are recognised by I18n
|
|
72
|
-
#
|
|
55
|
+
# Reserved option keys — these are recognised by I18n itself and not treated as interpolation
|
|
56
|
+
# variables.
|
|
73
57
|
RESERVED_OPTION_KEYS = %i[
|
|
74
58
|
default scope locale count raise throw fallback
|
|
75
59
|
fallback_in_progress separator deep_interpolation
|
|
76
60
|
].to_set.freeze
|
|
77
61
|
|
|
78
|
-
# Key prefixes Rails / `rails-i18n` ship in every
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# used by `Settings::Date::Order` and date-of-birth
|
|
84
|
-
# selects, never authored project-side. Skip
|
|
85
|
-
# `unknown-key` for these; downstream interpolation
|
|
86
|
-
# checks have nothing to validate without a leaf entry
|
|
87
|
-
# so they decline silently too.
|
|
62
|
+
# Key prefixes Rails / `rails-i18n` ship in every locale by default. Projects whose own locale
|
|
63
|
+
# files don't redeclare them still get them at runtime (via the `rails-i18n` gem's bundled locale
|
|
64
|
+
# catalogues). `t('date.order')` is the canonical Mastodon case — used by `Settings::Date::Order`
|
|
65
|
+
# and date-of-birth selects, never authored project-side. Skip `unknown-key` for these; downstream
|
|
66
|
+
# interpolation checks have nothing to validate without a leaf entry so they decline silently too.
|
|
88
67
|
RAILS_SHIPPED_KEY_PREFIXES = %w[
|
|
89
68
|
date.
|
|
90
69
|
time.
|
|
@@ -101,9 +80,8 @@ module Rigor
|
|
|
101
80
|
activerecord.errors.models.
|
|
102
81
|
].freeze
|
|
103
82
|
|
|
104
|
-
# One translation-call observation. Carries no path/location —
|
|
105
|
-
#
|
|
106
|
-
# `Plugin::Base#diagnostic`.
|
|
83
|
+
# One translation-call observation. Carries no path/location — the caller (the `node_rule` block)
|
|
84
|
+
# positions it via `Plugin::Base#diagnostic`.
|
|
107
85
|
Violation = Struct.new(:rule, :severity, :message, keyword_init: true)
|
|
108
86
|
|
|
109
87
|
module_function
|
|
@@ -118,12 +96,10 @@ module Rigor
|
|
|
118
96
|
# @param locale_index [LocaleIndex]
|
|
119
97
|
# @param configured_locales [Array<String>]
|
|
120
98
|
# @return [Array<Diagnostic>]
|
|
121
|
-
# The translation violations for a single call node, or `[]`
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
# the node-rule `NodeContext`, and `controller_scope` from the
|
|
126
|
-
# file path.
|
|
99
|
+
# The translation violations for a single call node, or `[]` when it is not a recognised `t` /
|
|
100
|
+
# `translate` call with a literal key. ADR-37: the engine owns the walk; `action` (the enclosing
|
|
101
|
+
# method, for lazy `t('.key')` expansion) comes from the node-rule `NodeContext`, and
|
|
102
|
+
# `controller_scope` from the file path.
|
|
127
103
|
#
|
|
128
104
|
# @param call_node [Prism::Node]
|
|
129
105
|
# @param locale_index [LocaleIndex]
|
|
@@ -143,11 +119,11 @@ module Rigor
|
|
|
143
119
|
options = options_hash(call_node)
|
|
144
120
|
entry = locale_index.find(literal_key)
|
|
145
121
|
if entry.nil?
|
|
146
|
-
# CLDR pluralization namespace: `t('accounts.posts', count: n)`
|
|
147
|
-
#
|
|
122
|
+
# CLDR pluralization namespace: `t('accounts.posts', count: n)` resolves through a `.one` /
|
|
123
|
+
# `.other` child — not missing.
|
|
148
124
|
return [] if locale_index.pluralization_namespace?(literal_key)
|
|
149
|
-
# Rails / rails-i18n ship `date.order`, `time.am`, etc. in
|
|
150
|
-
#
|
|
125
|
+
# Rails / rails-i18n ship `date.order`, `time.am`, etc. in every locale at runtime even when
|
|
126
|
+
# project files omit them.
|
|
151
127
|
return [] if rails_shipped_key?(literal_key)
|
|
152
128
|
|
|
153
129
|
return [unknown_key_violation(literal_key, locale_index)]
|
|
@@ -162,9 +138,8 @@ module Rigor
|
|
|
162
138
|
violations
|
|
163
139
|
end
|
|
164
140
|
|
|
165
|
-
# Derives the Rails controller scope from the file path,
|
|
166
|
-
#
|
|
167
|
-
# Returns nil for non-controller paths.
|
|
141
|
+
# Derives the Rails controller scope from the file path, e.g.
|
|
142
|
+
# `app/controllers/admin/users_controller.rb` → `admin.users`. Returns nil for non-controller paths.
|
|
168
143
|
def controller_scope_from_path(path)
|
|
169
144
|
m = CONTROLLER_PATH_RE.match(path.to_s)
|
|
170
145
|
return nil unless m
|
|
@@ -198,12 +173,10 @@ module Rigor
|
|
|
198
173
|
violations
|
|
199
174
|
end
|
|
200
175
|
|
|
201
|
-
# Expands a lazy key (starting with `.`) to its full
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
# or without an enclosing action — these are silently
|
|
206
|
-
# skipped to avoid false positives.
|
|
176
|
+
# Expands a lazy key (starting with `.`) to its full dotted path using the controller scope and
|
|
177
|
+
# action name. Returns the raw key unchanged for absolute keys. Returns nil for lazy keys outside a
|
|
178
|
+
# controller context or without an enclosing action — these are silently skipped to avoid false
|
|
179
|
+
# positives.
|
|
207
180
|
def expand_key(raw_key, controller_scope:, action:)
|
|
208
181
|
return raw_key unless raw_key.start_with?(".")
|
|
209
182
|
return nil unless controller_scope && action
|
|
@@ -219,9 +192,8 @@ module Rigor
|
|
|
219
192
|
I18N_RECEIVER_NAMES.include?(receiver_name)
|
|
220
193
|
end
|
|
221
194
|
|
|
222
|
-
# Extracts the literal-string first argument when
|
|
223
|
-
#
|
|
224
|
-
# only literal keys are statically validated.
|
|
195
|
+
# Extracts the literal-string first argument when present. Returns nil for variable / expression
|
|
196
|
+
# keys — only literal keys are statically validated.
|
|
225
197
|
def literal_key_for(call_node)
|
|
226
198
|
args = call_node.arguments&.arguments || []
|
|
227
199
|
return nil if args.empty?
|
|
@@ -232,11 +204,9 @@ module Rigor
|
|
|
232
204
|
first.unescaped
|
|
233
205
|
end
|
|
234
206
|
|
|
235
|
-
# Pulls the interpolation hash from the call's
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
# options (`default:`, `scope:`, …) and interpolation
|
|
239
|
-
# variables. Returns:
|
|
207
|
+
# Pulls the interpolation hash from the call's arguments. The trailing `Hash` argument (or
|
|
208
|
+
# `Prism::KeywordHashNode`) carries both reserved I18n options (`default:`, `scope:`, …) and
|
|
209
|
+
# interpolation variables. Returns:
|
|
240
210
|
# {
|
|
241
211
|
# :has_default => bool,
|
|
242
212
|
# :all_keys => Set<Symbol> (every assoc key in the hash),
|
|
@@ -244,15 +214,11 @@ module Rigor
|
|
|
244
214
|
# :hash_node => Prism::Node (or nil)
|
|
245
215
|
# }
|
|
246
216
|
#
|
|
247
|
-
# Note: a reserved option key (e.g. `count:`) can
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
# `%{count}` placeholder) and the
|
|
253
|
-
# extra-placeholder set against `non_reserved` (so
|
|
254
|
-
# `default:` / `scope:` are never reported as
|
|
255
|
-
# extra interpolation arguments).
|
|
217
|
+
# Note: a reserved option key (e.g. `count:`) can also serve as an interpolation value when the
|
|
218
|
+
# locale's leaf string has `%{count}`. The analyzer therefore checks the missing-placeholder set
|
|
219
|
+
# against `all_keys` (so `count:` satisfies a `%{count}` placeholder) and the extra-placeholder set
|
|
220
|
+
# against `non_reserved` (so `default:` / `scope:` are never reported as extra interpolation
|
|
221
|
+
# arguments).
|
|
256
222
|
def options_hash(call_node)
|
|
257
223
|
args = call_node.arguments&.arguments || []
|
|
258
224
|
last = args.last
|
|
@@ -275,8 +241,8 @@ module Rigor
|
|
|
275
241
|
end
|
|
276
242
|
|
|
277
243
|
def collect_assoc_keys(hash_node)
|
|
278
|
-
# Both `Prism::HashNode` and `Prism::KeywordHashNode`
|
|
279
|
-
#
|
|
244
|
+
# Both `Prism::HashNode` and `Prism::KeywordHashNode` expose `#elements`, so a single path
|
|
245
|
+
# handles both.
|
|
280
246
|
hash_node.elements.filter_map do |element|
|
|
281
247
|
next nil unless element.is_a?(Prism::AssocNode)
|
|
282
248
|
|
|
@@ -3,23 +3,17 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
5
|
class RailsI18n < Rigor::Plugin::Base
|
|
6
|
-
# Frozen catalogue of every dotted key discovered across
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# `%{var}` interpolation placeholders observed in the
|
|
10
|
-
# leaf string.
|
|
6
|
+
# Frozen catalogue of every dotted key discovered across all loaded locale files. Each entry tracks
|
|
7
|
+
# the locales the key appears in and, for each locale, the set of `%{var}` interpolation placeholders
|
|
8
|
+
# observed in the leaf string.
|
|
11
9
|
#
|
|
12
|
-
# The catalogue is intentionally lossy: it only records
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# not retained — the analyzer doesn't need them and
|
|
16
|
-
# keeping them would bloat the cache slice.
|
|
10
|
+
# The catalogue is intentionally lossy: it only records the *presence* of each key per locale and the
|
|
11
|
+
# placeholder names. The actual translated values are not retained — the analyzer doesn't need them
|
|
12
|
+
# and keeping them would bloat the cache slice.
|
|
17
13
|
class LocaleIndex
|
|
18
|
-
# `placeholders` is a Hash: `locale_name => Set<String>`.
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# `value_kinds` is a Hash: `locale_name => Symbol`
|
|
22
|
-
# (`:string` / `:array` / `:hash`).
|
|
14
|
+
# `placeholders` is a Hash: `locale_name => Set<String>`. `array_value` is true when at least one
|
|
15
|
+
# locale's leaf is an Array (used by `l(time, format:)` and similar). `value_kinds` is a Hash:
|
|
16
|
+
# `locale_name => Symbol` (`:string` / `:array` / `:hash`).
|
|
23
17
|
Entry = Data.define(:dotted_key, :placeholders, :value_kinds) do
|
|
24
18
|
def locales
|
|
25
19
|
placeholders.keys
|
|
@@ -33,8 +27,7 @@ module Rigor
|
|
|
33
27
|
placeholders.fetch(locale.to_s) { Set.new }
|
|
34
28
|
end
|
|
35
29
|
|
|
36
|
-
# Union of placeholder names across all known
|
|
37
|
-
# locales — used by the analyzer when no specific
|
|
30
|
+
# Union of placeholder names across all known locales — used by the analyzer when no specific
|
|
38
31
|
# locale is in scope.
|
|
39
32
|
def all_placeholders
|
|
40
33
|
placeholders.values.reduce(Set.new) { |acc, set| acc | set }
|
|
@@ -44,8 +37,7 @@ module Rigor
|
|
|
44
37
|
attr_reader :entries, :locales
|
|
45
38
|
|
|
46
39
|
# @param entries [Array<Entry>]
|
|
47
|
-
# @param locales [Array<String>] all locale names
|
|
48
|
-
# that contributed at least one key.
|
|
40
|
+
# @param locales [Array<String>] all locale names that contributed at least one key.
|
|
49
41
|
def initialize(entries, locales:)
|
|
50
42
|
@entries = entries.freeze
|
|
51
43
|
@locales = locales.dup.freeze
|
|
@@ -62,19 +54,15 @@ module Rigor
|
|
|
62
54
|
@by_key.key?(dotted_key.to_s)
|
|
63
55
|
end
|
|
64
56
|
|
|
65
|
-
# CLDR plural form keys recognised by Ruby I18n. When a
|
|
66
|
-
#
|
|
67
|
-
# the
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# Mastodon hits this on `accounts.posts` /
|
|
71
|
-
# `accounts.following` / `accounts.followers` for the post,
|
|
72
|
-
# follow, and follower counts shown on the profile page.
|
|
57
|
+
# CLDR plural form keys recognised by Ruby I18n. When a locale defines `accounts.posts.one`,
|
|
58
|
+
# `accounts.posts.other`, the call `t('accounts.posts', count: n)` resolves into the matching
|
|
59
|
+
# plural sub-key — the parent `accounts.posts` is a **pluralization namespace**, not a missing key.
|
|
60
|
+
# Mastodon hits this on `accounts.posts` / `accounts.following` / `accounts.followers` for the
|
|
61
|
+
# post, follow, and follower counts shown on the profile page.
|
|
73
62
|
PLURAL_SUBKEYS = %w[zero one two few many other].freeze
|
|
74
63
|
|
|
75
|
-
# Returns true when the dotted key itself isn't a leaf in
|
|
76
|
-
#
|
|
77
|
-
# children exists.
|
|
64
|
+
# Returns true when the dotted key itself isn't a leaf in any locale, but at least one of its CLDR
|
|
65
|
+
# plural form children exists.
|
|
78
66
|
def pluralization_namespace?(dotted_key)
|
|
79
67
|
base = dotted_key.to_s
|
|
80
68
|
PLURAL_SUBKEYS.any? { |sub| @by_key.key?("#{base}.#{sub}") }
|
|
@@ -88,14 +76,12 @@ module Rigor
|
|
|
88
76
|
@entries.size
|
|
89
77
|
end
|
|
90
78
|
|
|
91
|
-
# All known dotted keys, sorted for stable did-you-mean
|
|
92
|
-
# output.
|
|
79
|
+
# All known dotted keys, sorted for stable did-you-mean output.
|
|
93
80
|
def keys
|
|
94
81
|
@by_key.keys.sort
|
|
95
82
|
end
|
|
96
83
|
|
|
97
|
-
# Returns the locales (set of strings) in which a key
|
|
98
|
-
# is *missing*, given the configured locale list.
|
|
84
|
+
# Returns the locales (set of strings) in which a key is *missing*, given the configured locale list.
|
|
99
85
|
def missing_locales_for(dotted_key, configured_locales:)
|
|
100
86
|
entry = find(dotted_key)
|
|
101
87
|
return configured_locales.to_set if entry.nil?
|
|
@@ -7,27 +7,21 @@ require_relative "locale_index"
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Plugin
|
|
9
9
|
class RailsI18n < Rigor::Plugin::Base
|
|
10
|
-
# Walks `locale_search_paths` for `.yml` / `.yaml`
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# nested hash into a flat `dotted_key => Entry` table.
|
|
10
|
+
# Walks `locale_search_paths` for `.yml` / `.yaml` files, reads each through the trusted IoBoundary,
|
|
11
|
+
# parses with `YAML.safe_load`, and folds the resulting nested hash into a flat `dotted_key => Entry`
|
|
12
|
+
# table.
|
|
14
13
|
#
|
|
15
|
-
# The top-level YAML key is the locale (`en:`, `ja:`,
|
|
16
|
-
# …).
|
|
17
|
-
#
|
|
18
|
-
# …). For each leaf string, `%{var}` placeholders are
|
|
19
|
-
# extracted via a simple regex.
|
|
14
|
+
# The top-level YAML key is the locale (`en:`, `ja:`, …). Anything underneath is recursively
|
|
15
|
+
# flattened into dotted keys (`users.welcome`, `errors.messages.blank`, …). For each leaf string,
|
|
16
|
+
# `%{var}` placeholders are extracted via a simple regex.
|
|
20
17
|
#
|
|
21
|
-
# Files that fail to parse are skipped with a load-error
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# sequence) are also skipped — the format is locale-keyed
|
|
25
|
-
# by convention.
|
|
18
|
+
# Files that fail to parse are skipped with a load-error diagnostic surfaced through the plugin's
|
|
19
|
+
# error channel. Non-Hash YAML roots (e.g. a top-level sequence) are also skipped — the format is
|
|
20
|
+
# locale-keyed by convention.
|
|
26
21
|
class LocaleLoader
|
|
27
22
|
PLACEHOLDER_RE = /%\{(?<name>[^}]+)\}/
|
|
28
23
|
|
|
29
|
-
# Errno classes that indicate "this file is not
|
|
30
|
-
# readable as a YAML locale" — swallowed so a single
|
|
24
|
+
# Errno classes that indicate "this file is not readable as a YAML locale" — swallowed so a single
|
|
31
25
|
# bad path doesn't take down the rest of the index.
|
|
32
26
|
IO_ERRORS = [Errno::ENOENT, Errno::EACCES, Errno::EISDIR].freeze
|
|
33
27
|
|
|
@@ -100,19 +94,15 @@ module Rigor
|
|
|
100
94
|
end.sort
|
|
101
95
|
end
|
|
102
96
|
|
|
103
|
-
# Recursively walks the per-locale subtree, yielding
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
# descendants — but every leaf scalar / array IS recorded.
|
|
97
|
+
# Recursively walks the per-locale subtree, yielding `[dotted_key, leaf_value]` for each leaf. Hash
|
|
98
|
+
# leaves are *not* recorded as entries themselves — only their descendants — but every leaf scalar
|
|
99
|
+
# / array IS recorded.
|
|
107
100
|
#
|
|
108
|
-
# `breadcrumbs` is a single mutable stack reused across the
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
# intermediate result Array at every level — millions of
|
|
114
|
-
# short-lived objects and the run's top allocation site. The
|
|
115
|
-
# dotted key is still materialised once per leaf (it has to
|
|
101
|
+
# `breadcrumbs` is a single mutable stack reused across the whole walk (push before recursing, pop
|
|
102
|
+
# after): for the 530-file / 14 MB Mastodon locale corpus the old
|
|
103
|
+
# `flat_map { flatten_tree(v, breadcrumbs + [k]) }` shape allocated a fresh breadcrumb Array at
|
|
104
|
+
# every node plus an intermediate result Array at every level — millions of short-lived objects and
|
|
105
|
+
# the run's top allocation site. The dotted key is still materialised once per leaf (it has to
|
|
116
106
|
# be); everything else is now allocation-free traversal.
|
|
117
107
|
def each_flattened(node, breadcrumbs, &)
|
|
118
108
|
if node.is_a?(Hash)
|
|
@@ -129,9 +119,8 @@ module Rigor
|
|
|
129
119
|
def extract_placeholders(value)
|
|
130
120
|
case value
|
|
131
121
|
when String
|
|
132
|
-
# Most locale leaves carry no `%{var}`; skip the scan +
|
|
133
|
-
#
|
|
134
|
-
# no `%{` yields an empty placeholder set either way.
|
|
122
|
+
# Most locale leaves carry no `%{var}`; skip the scan + flatten + to_set allocation trio for
|
|
123
|
+
# them. A string with no `%{` yields an empty placeholder set either way.
|
|
135
124
|
return Set.new unless value.include?("%{")
|
|
136
125
|
|
|
137
126
|
value.scan(PLACEHOLDER_RE).flatten.to_set
|