rigortype 0.3.9 → 0.4.0
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 +2 -2
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
- data/docs/handbook/03-narrowing.md +95 -10
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/07-rbs-and-extended.md +16 -11
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/manual/02-cli-reference.md +63 -2
- data/docs/manual/03-configuration.md +7 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/16-rbs-extended-annotations.md +40 -7
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +243 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +110 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +1 -1
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
- data/lib/rigor/analysis/check_rules.rb +520 -49
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_session.rb +36 -4
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
- data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +20 -11
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
- data/lib/rigor/analysis/runner.rb +278 -20
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +53 -10
- data/lib/rigor/bleeding_edge.rb +0 -2
- data/lib/rigor/builtins/hkt_builtins.rb +1 -0
- data/lib/rigor/builtins/imported_refinements.rb +4 -0
- data/lib/rigor/builtins/regex_refinement.rb +17 -9
- data/lib/rigor/builtins/static_return_refinements.rb +2 -0
- data/lib/rigor/cache/descriptor.rb +6 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/incremental_snapshot.rb +33 -1
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_descriptor.rb +37 -7
- data/lib/rigor/cache/store.rb +0 -1
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/plugin_command.rb +3 -3
- data/lib/rigor/cli/prism_colorizer.rb +0 -1
- data/lib/rigor/cli/sig_gen_command.rb +210 -29
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +0 -1
- data/lib/rigor/cli/type_of_command.rb +19 -5
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/configuration/severity_profile.rb +19 -3
- data/lib/rigor/configuration.rb +66 -3
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +11 -2
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +9 -2
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/effect_table.rb +11 -3
- data/lib/rigor/effects/envelope_check.rb +1 -1
- data/lib/rigor/effects/envelope_index.rb +15 -0
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/identity.rb +16 -0
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +21 -0
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +43 -31
- data/lib/rigor/effects/propagator.rb +295 -16
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +121 -72
- data/lib/rigor/effects/signature_sources.rb +1 -1
- data/lib/rigor/effects/snapshot.rb +2 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +385 -30
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/lockfile_resolver.rb +17 -0
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_loader.rb +399 -133
- data/lib/rigor/environment.rb +103 -38
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +139 -9
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +73 -27
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +2 -1
- data/lib/rigor/inference/budget_trace.rb +2 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +387 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/element_read_widening.rb +22 -10
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +1532 -496
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/index_write_widening.rb +16 -3
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +167 -17
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +214 -13
- data/lib/rigor/inference/method_parameter_binder.rb +8 -3
- data/lib/rigor/inference/multi_target_binder.rb +340 -52
- data/lib/rigor/inference/mutation_rejoin.rb +4 -1
- data/lib/rigor/inference/mutation_widening.rb +70 -48
- data/lib/rigor/inference/narrowing.rb +540 -120
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +152 -9
- data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +7 -2
- data/lib/rigor/inference/project_patched_scanner.rb +7 -3
- data/lib/rigor/inference/receiver_alias.rb +90 -1
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +15 -11
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +4615 -551
- data/lib/rigor/inference/statement_evaluator.rb +3176 -490
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +44 -7
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
- data/lib/rigor/language_server/hover_renderer.rb +3 -3
- data/lib/rigor/language_server/project_context.rb +5 -3
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +168 -5
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +1 -1
- data/lib/rigor/plugin/effect_attribution.rb +58 -4
- data/lib/rigor/plugin/loader.rb +2 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/manifest.rb +71 -10
- data/lib/rigor/plugin/registry.rb +35 -1
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +3 -1
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +1 -2
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +19 -6
- data/lib/rigor/reflection.rb +72 -105
- data/lib/rigor/scope/discovery_index.rb +135 -2
- data/lib/rigor/scope.rb +859 -49
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +558 -31
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +124 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +166 -35
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +1 -1
- data/lib/rigor/source/node_walker.rb +0 -3
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/combinator.rb +88 -12
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/hash_shape.rb +1 -1
- data/lib/rigor/type/refined.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +12 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +6 -4
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +1 -1
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +50 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +7 -6
- data/sig/rigor/scope.rbs +135 -20
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +6 -0
- data/sig/rigor.rbs +38 -20
- data/skills/rigor-ask/SKILL.md +8 -5
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +9 -7
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +33 -8
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +4 -6
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
- data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +108 -1
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "controller_scan"
|
|
6
|
+
require_relative "erb_compiler"
|
|
7
|
+
require_relative "view_assigns"
|
|
8
|
+
require_relative "view_units"
|
|
9
|
+
|
|
10
|
+
module Rigor
|
|
11
|
+
module Plugin
|
|
12
|
+
class Actionpack < Rigor::Plugin::Base
|
|
13
|
+
# #1047 — the locals a render site passes, indexed by the template they are passed TO.
|
|
14
|
+
#
|
|
15
|
+
# #393 seeded a template's `locals:` from the Rails 7.1 strict-locals comment only, so a partial
|
|
16
|
+
# without one read its own parameters as method calls on the view context. That absence has a
|
|
17
|
+
# measured cost rather than a theoretical one: redmine's `app/views/common/_other.html.erb` opens
|
|
18
|
+
# with the standard optional-local preamble
|
|
19
|
+
#
|
|
20
|
+
# <% path = nil unless defined? path %>
|
|
21
|
+
#
|
|
22
|
+
# and the compiled Ruby really does assign nil there, because nothing told the unit that `path` is
|
|
23
|
+
# bound — so the flow rules folded three live branches and `flow.` had to join `call.` in the
|
|
24
|
+
# plugin's default suppressed set for that reason alone
|
|
25
|
+
# (`docs/notes/20260917-erb-template-units.md` § 4). Tracing the render site is what lets the flow
|
|
26
|
+
# rules back into a view.
|
|
27
|
+
#
|
|
28
|
+
# ## Why over-binding is the safe direction and under-binding is not
|
|
29
|
+
#
|
|
30
|
+
# A name this index seeds that the render site did not really pass costs nothing: the local binds
|
|
31
|
+
# `Dynamic`, `defined?` reads it as bound, and no rule folds anything. A name it MISSES is the
|
|
32
|
+
# false positive above. So the union across render sites is deliberately generous — a name bound at
|
|
33
|
+
# only some of a partial's render sites is seeded anyway, typed `Dynamic`, never left absent — and
|
|
34
|
+
# every shape below that cannot be settled from literals contributes a NAME with no type rather
|
|
35
|
+
# than nothing.
|
|
36
|
+
#
|
|
37
|
+
# The one thing that is not generous is the TYPE. A type is a claim the engine acts on, so a name
|
|
38
|
+
# keeps a concrete type only when every site that passes it passes the same one; anything else is
|
|
39
|
+
# {ViewUnits::UNKNOWN_LOCAL}, which the engine binds `Dynamic[top]` (ADR-5).
|
|
40
|
+
#
|
|
41
|
+
# ## What a render site is read for
|
|
42
|
+
#
|
|
43
|
+
# Both sides of the render, because both bind the same thing:
|
|
44
|
+
#
|
|
45
|
+
# - a **controller** site, through {Analyzer.render_target_for} — `render partial: "card",
|
|
46
|
+
# locals: { user: @user }`;
|
|
47
|
+
# - a **template** site, in the compiled Ruby of every view — the same keyword form, plus the
|
|
48
|
+
# positional-hash form `render "card", user: @user`, which is locals in a view and OPTIONS in a
|
|
49
|
+
# controller (`ActionView::Helpers::RenderingHelper#render(options, locals)` against
|
|
50
|
+
# `ActionController::Base#render(options)`). Reading the controller form as locals would seed a
|
|
51
|
+
# `status` local for every `render :show, status: :ok` in the corpus.
|
|
52
|
+
#
|
|
53
|
+
# `collection:` and `object:` bind a local named after the partial, or after `as:` when given, and a
|
|
54
|
+
# collection additionally binds the `_counter` and `_iteration` companions Rails provides.
|
|
55
|
+
#
|
|
56
|
+
# ## The compiled-source cache
|
|
57
|
+
#
|
|
58
|
+
# Building the index compiles every template in the project, and `#template_units_for_file` is about
|
|
59
|
+
# to compile each of them again. So the builder keeps what it compiled, keyed by path and guarded by
|
|
60
|
+
# the template's own scrubbed bytes, and the hook reuses it — a full run does exactly the compile
|
|
61
|
+
# work it did before this feature, one pass instead of two. An editor buffer's bytes differ from the
|
|
62
|
+
# file's, so its template falls through to its own compile; the index itself is built once per
|
|
63
|
+
# plugin instance and revalidated once per collection pass against {Builder.fingerprint}, so an
|
|
64
|
+
# unsaved buffer's render sites are NOT read — its partials keep the locals the saved file passes
|
|
65
|
+
# until the save.
|
|
66
|
+
class RenderLocals
|
|
67
|
+
# What a name is seeded with when no site settled a type for it, or when two disagreed.
|
|
68
|
+
UNKNOWN = ViewUnits::UNKNOWN_LOCAL
|
|
69
|
+
|
|
70
|
+
# Finders and constructors whose Rails implementation raises rather than returning nil — the same
|
|
71
|
+
# closed set {ViewAssigns::NON_NIL_PRODUCERS} names, and for the same reason: a nil-free nominal
|
|
72
|
+
# standing in for a runtime nil licenses folds that report correct code.
|
|
73
|
+
NON_NIL_PRODUCERS = ViewAssigns::NON_NIL_PRODUCERS
|
|
74
|
+
|
|
75
|
+
# `{ "common/_other" => { "path" => UNKNOWN, … } }`, keyed by logical name with the format
|
|
76
|
+
# stripped — a partial's locals do not depend on which format rendered it.
|
|
77
|
+
attr_reader :by_template
|
|
78
|
+
|
|
79
|
+
# The {Builder.fingerprint} of the sources this index was built from, taken BEFORE they were read —
|
|
80
|
+
# so an edit that lands during the build reads as a change on the next pass rather than being
|
|
81
|
+
# recorded as already seen. nil for {.empty}, which therefore never matches a real fingerprint.
|
|
82
|
+
attr_reader :fingerprint
|
|
83
|
+
|
|
84
|
+
# Every method name a project helper `def`s — see {ViewUnits.self_declared_locals}.
|
|
85
|
+
attr_reader :helper_methods
|
|
86
|
+
|
|
87
|
+
def initialize(by_template, compiled = {}, fingerprint: nil, helper_methods: {})
|
|
88
|
+
@by_template = by_template.freeze
|
|
89
|
+
@compiled = compiled.freeze
|
|
90
|
+
@fingerprint = fingerprint
|
|
91
|
+
@helper_methods = helper_methods.freeze
|
|
92
|
+
freeze
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.empty
|
|
96
|
+
new({})
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# `common/_other.html` → the names every site that renders `common/_other` passed.
|
|
100
|
+
def seeds_for(logical_name)
|
|
101
|
+
@by_template.fetch(logical_name.to_s.sub(%r{\.[^./]+\z}, ""), {})
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# The `[compiled, line_map, transform_id, parse_ok]` this index already computed for `path`, or nil
|
|
105
|
+
# when it read different bytes (an editor buffer) or never saw the file. The bytes are compared
|
|
106
|
+
# rather than trusted: a unit compiled from the file on disk must never stand in for one the editor
|
|
107
|
+
# is holding.
|
|
108
|
+
def compiled_for(path, text)
|
|
109
|
+
entry = @compiled[path.to_s]
|
|
110
|
+
return nil if entry.nil? || entry.first != text
|
|
111
|
+
|
|
112
|
+
entry.drop(1)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Builds the index from the controller sources and the templates the plugin claims.
|
|
116
|
+
class Builder
|
|
117
|
+
def initialize(io_boundary:, controller_search_paths:, view_search_paths:, view_assigns:,
|
|
118
|
+
helper_search_paths: [])
|
|
119
|
+
@io_boundary = io_boundary
|
|
120
|
+
@controller_search_paths = controller_search_paths
|
|
121
|
+
@view_search_paths = view_search_paths
|
|
122
|
+
@helper_search_paths = helper_search_paths
|
|
123
|
+
@view_assigns = view_assigns
|
|
124
|
+
# `{ target => [ { name => type_name_or_nil }, … ] }` — one hash per render site, merged only
|
|
125
|
+
# once every site has been seen, because "bound at some sites" is a property of the whole set.
|
|
126
|
+
@sites = Hash.new { |hash, key| hash[key] = [] }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def build
|
|
130
|
+
fingerprint = self.class.fingerprint(
|
|
131
|
+
io_boundary: @io_boundary, controller_search_paths: @controller_search_paths,
|
|
132
|
+
view_search_paths: @view_search_paths, helper_search_paths: @helper_search_paths
|
|
133
|
+
)
|
|
134
|
+
harvest_controllers
|
|
135
|
+
compiled = harvest_templates
|
|
136
|
+
RenderLocals.new(@sites.transform_values { |sites| merge(sites) }, compiled,
|
|
137
|
+
fingerprint: fingerprint, helper_methods: harvest_helper_methods)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# What the index was built from, as one String: every controller, helper and template path with
|
|
141
|
+
# its size and mtime. A glob catches a file appearing or vanishing and a stat catches an edit;
|
|
142
|
+
# neither reads a byte, so revalidating costs one `stat` per file, once per collection pass.
|
|
143
|
+
#
|
|
144
|
+
# There is deliberately no content-digest fallback, unlike the collector's ADR-87 pack: a
|
|
145
|
+
# byte-identical `touch` moves the fingerprint and rebuilds this index while the collector still
|
|
146
|
+
# carries the units. That costs one rebuild and never a wrong answer — the rebuilt index is the
|
|
147
|
+
# index the carried units were seeded from — so a digest per file per pass is not worth buying.
|
|
148
|
+
def self.fingerprint(io_boundary:, controller_search_paths:, view_search_paths:, helper_search_paths: [])
|
|
149
|
+
files = source_files(io_boundary, controller_search_paths, "*.rb") +
|
|
150
|
+
source_files(io_boundary, helper_search_paths, "*.rb") +
|
|
151
|
+
source_files(io_boundary, view_search_paths, "*.erb")
|
|
152
|
+
files.map do |path|
|
|
153
|
+
stat = File.stat(path)
|
|
154
|
+
"#{path}\0#{stat.size}\0#{stat.mtime.to_r}"
|
|
155
|
+
rescue SystemCallError
|
|
156
|
+
"#{path}\0gone"
|
|
157
|
+
end.join("\n")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Project-relative paths, the spelling `#template_units_for_file` is handed and the spelling the
|
|
161
|
+
# compiled cache is keyed by.
|
|
162
|
+
def self.source_files(io_boundary, roots, pattern)
|
|
163
|
+
root_prefix = "#{Dir.pwd}#{File::SEPARATOR}"
|
|
164
|
+
roots.flat_map do |root|
|
|
165
|
+
absolute = File.expand_path(root)
|
|
166
|
+
next [] unless io_boundary.directory?(absolute)
|
|
167
|
+
|
|
168
|
+
Dir.glob(File.join(absolute, "**", pattern)).map { |path| path.delete_prefix(root_prefix) }
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
# Two sites that agree on a name and its type keep the type; anything else — a disagreement, a
|
|
175
|
+
# site that settled no type, or a site that did not pass the name at all — is `Dynamic`.
|
|
176
|
+
def merge(sites)
|
|
177
|
+
names = sites.flat_map(&:keys).uniq
|
|
178
|
+
names.to_h do |name|
|
|
179
|
+
types = sites.map { |site| site.key?(name) ? site[name] : :absent }.uniq
|
|
180
|
+
[name, types.length == 1 && types.first.is_a?(String) ? types.first : UNKNOWN]
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
HELPER_DEF = /^[ \t]*def[ \t]+(?:self\.)?([a-z_][A-Za-z0-9_]*[?!]?)/
|
|
185
|
+
private_constant :HELPER_DEF
|
|
186
|
+
|
|
187
|
+
# `{ "current_user" => true }` for every `def` in a project helper. A line scan rather than a
|
|
188
|
+
# parse: the answer only has to be a superset of the names a helper can answer, and a name read
|
|
189
|
+
# out of a comment or a heredoc merely leaves one template's `defined?` test unseeded — which is
|
|
190
|
+
# what #393 shipped for every name.
|
|
191
|
+
def harvest_helper_methods
|
|
192
|
+
source_files(@helper_search_paths, "*.rb").each_with_object({}) do |path, names|
|
|
193
|
+
read(path)&.scan(HELPER_DEF)&.each { |(name)| names[name] = true }
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def harvest_controllers
|
|
198
|
+
source_files(@controller_search_paths, "*.rb").each do |path|
|
|
199
|
+
tree = parse_file(path)
|
|
200
|
+
next if tree.nil?
|
|
201
|
+
|
|
202
|
+
ControllerScan.each_controller(tree, []) do |node, namespace|
|
|
203
|
+
harvest_controller(node, namespace)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def harvest_controller(node, namespace)
|
|
209
|
+
segments = namespace + ControllerScan.constant_segments(node.constant_path)
|
|
210
|
+
prefix = ControllerScan.controller_path(segments)
|
|
211
|
+
return if prefix.nil?
|
|
212
|
+
|
|
213
|
+
ControllerScan.method_bodies(node).each do |action, body|
|
|
214
|
+
ivars = @view_assigns.seeds_for("#{prefix}/#{action}")
|
|
215
|
+
ControllerScan.each_render(body) do |call|
|
|
216
|
+
record(controller_target(call, prefix), call, ivars, view_side: false)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# {Analyzer.render_target_for} already answers this for a controller: `render :edit`,
|
|
222
|
+
# `render "admin/shared/form"`, `render partial: "card"`. Its `[:template, name]` /
|
|
223
|
+
# `[:partial, name]` pair reduces to the name, because the locals of a template and of a partial
|
|
224
|
+
# are seeded the same way.
|
|
225
|
+
def controller_target(call, prefix)
|
|
226
|
+
Analyzer.render_target_for(call, prefix)&.last
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Every claimed template, compiled once. Returns the `{ path => [text, compiled, map, id, ok] }`
|
|
230
|
+
# cache `#template_units_for_file` reuses.
|
|
231
|
+
def harvest_templates
|
|
232
|
+
source_files(@view_search_paths, "*.erb").each_with_object({}) do |path, compiled|
|
|
233
|
+
text = read(path)
|
|
234
|
+
next if text.nil?
|
|
235
|
+
|
|
236
|
+
text = ErbCompiler.scrub(text)
|
|
237
|
+
source, line_map, transform = ErbCompiler.compile(text)
|
|
238
|
+
result = Prism.parse(source)
|
|
239
|
+
compiled[path] = [text, source, line_map, transform, result.errors.empty?]
|
|
240
|
+
next unless result.errors.empty?
|
|
241
|
+
|
|
242
|
+
harvest_template(path, result.value)
|
|
243
|
+
end
|
|
244
|
+
rescue ErbCompiler::Unmappable
|
|
245
|
+
{}
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def harvest_template(path, tree)
|
|
249
|
+
name = ViewUnits.logical_name(path, @view_search_paths).sub(%r{\.[^./]+\z}, "")
|
|
250
|
+
directory = name.include?("/") ? File.dirname(name) : ""
|
|
251
|
+
ivars = @view_assigns.seeds_for(name)
|
|
252
|
+
ControllerScan.each_render(tree) do |call|
|
|
253
|
+
record(template_target(call, directory), call, ivars, view_side: true)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# The view-side reading of a render target, which differs from the controller's in exactly one
|
|
258
|
+
# place: a bare positional name is a PARTIAL here. `template:` is the escape hatch an author uses
|
|
259
|
+
# when they mean otherwise, and `layout:` in a view names a partial too — the same reading
|
|
260
|
+
# {Effects::CalleeRule.rails_render_partial} takes, for the same reason.
|
|
261
|
+
def template_target(call, directory)
|
|
262
|
+
explicit = string_option(call, "template")
|
|
263
|
+
return qualify(explicit, directory) if explicit
|
|
264
|
+
|
|
265
|
+
partial = string_option(call, "partial") || string_option(call, "layout")
|
|
266
|
+
return partialize(qualify(partial, directory)) if partial
|
|
267
|
+
|
|
268
|
+
positional = (call.arguments&.arguments || []).grep_v(Prism::KeywordHashNode).first
|
|
269
|
+
return nil unless positional.is_a?(Prism::StringNode) || positional.is_a?(Prism::SymbolNode)
|
|
270
|
+
|
|
271
|
+
partialize(qualify(positional.unescaped, directory))
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def record(target, call, ivars, view_side:)
|
|
275
|
+
return if target.nil? || target.empty?
|
|
276
|
+
|
|
277
|
+
locals = locals_at(call, target, ivars, view_side: view_side)
|
|
278
|
+
@sites[target] << locals
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# The names one render site binds, and the type of each where the site settled one.
|
|
282
|
+
def locals_at(call, target, ivars, view_side:)
|
|
283
|
+
arguments = call.arguments&.arguments || []
|
|
284
|
+
options = arguments.find { |argument| argument.is_a?(Prism::KeywordHashNode) }
|
|
285
|
+
locals = {}
|
|
286
|
+
# `render "card", user: @user` — the view's second positional argument IS the locals hash, and
|
|
287
|
+
# the controller's is options. Read alongside an explicit `locals:` rather than instead of it,
|
|
288
|
+
# because over-binding is the safe direction.
|
|
289
|
+
collect_pairs(options, locals, ivars) if view_side && !arguments.first.is_a?(Prism::KeywordHashNode)
|
|
290
|
+
collect_pairs(option(call, "locals"), locals, ivars)
|
|
291
|
+
collect_object_locals(call, target, locals, ivars)
|
|
292
|
+
locals
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# `locals: { user: @user }` and its hashrocket spelling. A key that is not a literal symbol or
|
|
296
|
+
# string — a computed key, a `**splat` — names nothing this can seed, and is skipped rather than
|
|
297
|
+
# guessed at.
|
|
298
|
+
def collect_pairs(hash, locals, ivars)
|
|
299
|
+
return unless hash.is_a?(Prism::HashNode) || hash.is_a?(Prism::KeywordHashNode)
|
|
300
|
+
|
|
301
|
+
hash.elements.each do |element|
|
|
302
|
+
next unless element.is_a?(Prism::AssocNode)
|
|
303
|
+
|
|
304
|
+
name = literal_name(element.key)
|
|
305
|
+
next if name.nil? || !name.match?(/\A[a-z_][A-Za-z0-9_]*\z/)
|
|
306
|
+
|
|
307
|
+
locals[name] = value_type(element.value, ivars)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# `object:` binds one local named after the partial; `collection:` binds the same name plus the
|
|
312
|
+
# `_counter` and `_iteration` companions Rails provides, and never a type — the local is the
|
|
313
|
+
# ELEMENT, and nothing at the site names the element's class.
|
|
314
|
+
def collect_object_locals(call, target, locals, ivars)
|
|
315
|
+
base = literal_name(option(call, "as")) || File.basename(target).delete_prefix("_")
|
|
316
|
+
return unless base.match?(/\A[a-z_][A-Za-z0-9_]*\z/)
|
|
317
|
+
|
|
318
|
+
object = option(call, "object")
|
|
319
|
+
collection = option(call, "collection")
|
|
320
|
+
locals[base] = value_type(object, ivars) if object
|
|
321
|
+
return if collection.nil?
|
|
322
|
+
|
|
323
|
+
locals[base] = nil
|
|
324
|
+
locals["#{base}_counter"] = nil
|
|
325
|
+
locals["#{base}_iteration"] = nil
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# The two shapes a render site settles a type from, and nothing else: a non-nil-able finder or
|
|
329
|
+
# constructor on a constant (`User.find(params[:id])`), and an ivar the rendering unit's own
|
|
330
|
+
# seeds already typed. Anything else is a name with no type.
|
|
331
|
+
def value_type(node, ivars)
|
|
332
|
+
case node
|
|
333
|
+
when Prism::InstanceVariableReadNode then ivars[node.name.to_s]
|
|
334
|
+
when Prism::CallNode then produced_type(node)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def produced_type(call)
|
|
339
|
+
return nil unless NON_NIL_PRODUCERS.include?(call.name)
|
|
340
|
+
|
|
341
|
+
receiver = call.receiver
|
|
342
|
+
return nil unless receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)
|
|
343
|
+
|
|
344
|
+
name = ControllerScan.constant_segments(receiver).join("::")
|
|
345
|
+
name.empty? ? nil : name
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def option(call, key)
|
|
349
|
+
hash = call.arguments&.arguments&.find { |argument| argument.is_a?(Prism::KeywordHashNode) }
|
|
350
|
+
pair = hash&.elements&.find do |element|
|
|
351
|
+
element.is_a?(Prism::AssocNode) && literal_name(element.key) == key
|
|
352
|
+
end
|
|
353
|
+
pair&.value
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def string_option(call, key)
|
|
357
|
+
value = option(call, key)
|
|
358
|
+
value.is_a?(Prism::StringNode) || value.is_a?(Prism::SymbolNode) ? value.unescaped : nil
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def literal_name(node)
|
|
362
|
+
node.unescaped if node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def qualify(name, directory)
|
|
366
|
+
return nil if name.nil? || name.empty?
|
|
367
|
+
return name if name.include?("/") || directory.empty?
|
|
368
|
+
|
|
369
|
+
"#{directory}/#{name}"
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def partialize(name)
|
|
373
|
+
return nil if name.nil?
|
|
374
|
+
|
|
375
|
+
directory, separator, base = name.rpartition("/")
|
|
376
|
+
return base.start_with?("_") ? name : "_#{name}" if separator.empty?
|
|
377
|
+
|
|
378
|
+
base.start_with?("_") ? name : "#{directory}/_#{base}"
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def source_files(roots, pattern)
|
|
382
|
+
self.class.source_files(@io_boundary, roots, pattern)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def read(path)
|
|
386
|
+
@io_boundary.read_file(File.expand_path(path))
|
|
387
|
+
rescue Plugin::AccessDeniedError, Errno::ENOENT
|
|
388
|
+
nil
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def parse_file(path)
|
|
392
|
+
contents = read(path)
|
|
393
|
+
return nil if contents.nil?
|
|
394
|
+
|
|
395
|
+
result = Prism.parse(contents)
|
|
396
|
+
result.errors.empty? ? result.value : nil
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|