rigortype 0.2.9 → 0.3.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/docs/handbook/03-narrowing.md +2 -1
- data/docs/handbook/04-tuples-and-shapes.md +7 -3
- data/docs/handbook/06-classes.md +18 -9
- data/docs/handbook/08-understanding-errors.md +1 -0
- data/docs/handbook/11-sig-gen.md +11 -1
- data/docs/handbook/appendix-mypy.md +4 -3
- data/docs/install.md +14 -6
- data/docs/manual/01-installation.md +98 -4
- data/docs/manual/02-cli-reference.md +49 -12
- data/docs/manual/03-configuration.md +34 -2
- data/docs/manual/04-diagnostics.md +23 -0
- data/docs/manual/07-plugins.md +4 -4
- data/docs/manual/09-editor-integration.md +4 -4
- data/docs/manual/11-ci.md +51 -0
- data/docs/manual/12-caching.md +65 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -1
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +128 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +60 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +117 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +125 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +283 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +10 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +93 -0
- data/lib/rigor/analysis/check_rules.rb +557 -95
- data/lib/rigor/analysis/dependency_recorder.rb +93 -9
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +456 -51
- data/lib/rigor/analysis/path_expansion.rb +42 -0
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +188 -0
- data/lib/rigor/analysis/rule_catalog.rb +226 -1
- data/lib/rigor/analysis/run_cache_key.rb +68 -0
- data/lib/rigor/analysis/run_cache_probe.rb +72 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +181 -28
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +95 -49
- data/lib/rigor/analysis/runner/run_snapshots.rb +9 -2
- data/lib/rigor/analysis/runner.rb +343 -68
- data/lib/rigor/analysis/severity_stamp.rb +43 -0
- data/lib/rigor/analysis/worker_session.rb +30 -2
- data/lib/rigor/bleeding_edge.rb +37 -8
- data/lib/rigor/builtins/regex_refinement.rb +36 -1
- data/lib/rigor/cache/descriptor.rb +88 -28
- data/lib/rigor/cache/file_digest.rb +158 -0
- data/lib/rigor/cache/incremental_snapshot.rb +41 -5
- data/lib/rigor/cache/rbs_descriptor.rb +56 -10
- data/lib/rigor/cache/store.rb +33 -3
- data/lib/rigor/ci_detector.rb +90 -0
- data/lib/rigor/cli/annotate_command.rb +15 -9
- data/lib/rigor/cli/baseline_command.rb +4 -1
- data/lib/rigor/cli/check_command.rb +212 -27
- data/lib/rigor/cli/coverage_command.rb +6 -1
- data/lib/rigor/cli/docs_command.rb +0 -30
- data/lib/rigor/cli/doctor_command.rb +163 -23
- data/lib/rigor/cli/lsp_command.rb +5 -0
- data/lib/rigor/cli/mcp_command.rb +5 -0
- data/lib/rigor/cli/plugins_command.rb +36 -6
- data/lib/rigor/cli/plugins_renderer.rb +44 -12
- data/lib/rigor/cli/probe_environment.rb +85 -0
- data/lib/rigor/cli/sig_gen_command.rb +36 -9
- data/lib/rigor/cli/skill_command.rb +1 -32
- data/lib/rigor/cli/trace_command.rb +5 -6
- data/lib/rigor/cli/type_of_command.rb +9 -8
- data/lib/rigor/cli/type_scan_command.rb +8 -8
- data/lib/rigor/cli.rb +15 -4
- data/lib/rigor/config_audit.rb +48 -2
- data/lib/rigor/configuration/severity_profile.rb +31 -0
- data/lib/rigor/configuration.rb +156 -8
- data/lib/rigor/environment/default_libraries.rb +35 -0
- data/lib/rigor/environment/rbs_loader.rb +164 -5
- data/lib/rigor/environment.rb +19 -27
- data/lib/rigor/inference/budget_trace.rb +77 -2
- data/lib/rigor/inference/closure_escape_analyzer.rb +14 -1
- data/lib/rigor/inference/def_handle.rb +23 -0
- data/lib/rigor/inference/def_node_resolver.rb +90 -0
- data/lib/rigor/inference/def_return_typer.rb +2 -1
- data/lib/rigor/inference/expression_typer.rb +361 -113
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +30 -5
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +200 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +22 -16
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +33 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +11 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +42 -9
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +5 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +28 -35
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +40 -8
- data/lib/rigor/inference/method_dispatcher.rb +57 -1
- data/lib/rigor/inference/mutation_widening.rb +42 -8
- data/lib/rigor/inference/narrowing.rb +127 -17
- data/lib/rigor/inference/parameter_inference_collector.rb +15 -0
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +8 -1
- data/lib/rigor/inference/scope_indexer.rb +451 -36
- data/lib/rigor/inference/statement_evaluator.rb +64 -15
- data/lib/rigor/inference/struct_fold_safety.rb +48 -11
- data/lib/rigor/inference/synthetic_method_scanner.rb +15 -14
- data/lib/rigor/inference/void_origin.rb +25 -0
- data/lib/rigor/inference/void_tail_summary.rb +220 -0
- data/lib/rigor/language_server/completion_provider.rb +2 -1
- data/lib/rigor/language_server/document_symbol_provider.rb +2 -1
- data/lib/rigor/language_server/folding_range_provider.rb +2 -1
- data/lib/rigor/language_server/selection_range_provider.rb +2 -1
- data/lib/rigor/language_server/signature_help_provider.rb +2 -1
- data/lib/rigor/plugin/base.rb +9 -22
- data/lib/rigor/plugin/inflector.rb +12 -3
- data/lib/rigor/plugin/io_boundary.rb +5 -1
- data/lib/rigor/plugin/isolation.rb +81 -11
- data/lib/rigor/plugin/load_error.rb +10 -1
- data/lib/rigor/plugin/loader.rb +96 -14
- data/lib/rigor/plugin/node_rule_walk.rb +5 -3
- data/lib/rigor/plugin/registry.rb +32 -23
- data/lib/rigor/plugin.rb +26 -0
- data/lib/rigor/protection/mutator.rb +3 -2
- data/lib/rigor/reflection.rb +64 -0
- data/lib/rigor/runtime/jit.rb +128 -0
- data/lib/rigor/scope/discovery_index.rb +12 -2
- data/lib/rigor/scope.rb +122 -28
- data/lib/rigor/sig_gen/classification.rb +5 -1
- data/lib/rigor/sig_gen/generator.rb +38 -6
- data/lib/rigor/sig_gen/observation_collector.rb +4 -3
- data/lib/rigor/sig_gen/rbs_validity.rb +44 -0
- data/lib/rigor/sig_gen/renderer.rb +10 -0
- data/lib/rigor/sig_gen/write_result.rb +9 -4
- data/lib/rigor/sig_gen/writer.rb +24 -2
- data/lib/rigor/source/node_children.rb +116 -0
- data/lib/rigor/source/node_locator.rb +3 -1
- data/lib/rigor/source/node_walker.rb +4 -2
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/difference.rb +28 -24
- data/lib/rigor/type/hash_shape.rb +34 -10
- data/lib/rigor/version.rb +1 -1
- data/lib/rigortype.rb +24 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +5 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +3 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +4 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +4 -2
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +38 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -1
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +3 -1
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +10 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +11 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +3 -1
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -12
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +7 -5
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +83 -9
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +9 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +11 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +3 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +74 -14
- data/sig/prism_node_children.rbs +9 -0
- data/sig/rigor/inference.rbs +7 -0
- data/sig/rigor/plugin/base.rbs +2 -3
- data/sig/rigor/reflection.rbs +1 -0
- data/sig/rigor/scope.rbs +12 -1
- data/skills/rigor-ci-setup/SKILL.md +10 -0
- data/skills/rigor-editor-setup/SKILL.md +11 -0
- data/skills/rigor-next-steps/SKILL.md +23 -2
- data/skills/rigor-plugin-author/SKILL.md +3 -3
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +2 -2
- data/skills/rigor-plugin-review/SKILL.md +2 -2
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +5 -4
- data/skills/rigor-project-init/SKILL.md +29 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -1
- data/skills/rigor-project-init/references/02-configure.md +39 -5
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +20 -0
- data/skills/rigor-project-init/references/05-jit-performance.md +125 -0
- metadata +42 -24
- data/lib/rigor/cli/ci_detector.rb +0 -89
|
@@ -42,21 +42,43 @@ module Rigor
|
|
|
42
42
|
paths = @argv.empty? ? configuration.paths : @argv
|
|
43
43
|
|
|
44
44
|
observations = collect_observations(configuration, options)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
generator = SigGen::Generator.new(configuration: configuration, paths: paths,
|
|
46
|
+
observations: observations,
|
|
47
|
+
include_private: options.fetch(:include_private))
|
|
48
|
+
candidates = generator.run
|
|
48
49
|
mode = options.fetch(:mode).to_sym
|
|
49
50
|
|
|
50
|
-
if mode == :write
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
status = if mode == :write
|
|
52
|
+
dispatch_write(candidates, configuration, options)
|
|
53
|
+
else
|
|
54
|
+
dispatch_print_or_diff(candidates, mode, options)
|
|
55
|
+
0
|
|
56
|
+
end
|
|
57
|
+
report_unrenderable(generator.unrenderable)
|
|
58
|
+
status
|
|
56
59
|
end
|
|
57
60
|
|
|
58
61
|
private
|
|
59
62
|
|
|
63
|
+
# A method whose rendered RBS does not parse is a Rigor rendering defect, not a fact about the user's
|
|
64
|
+
# code — the generator skipped it (so the rest of the signatures are still usable and still valid), but
|
|
65
|
+
# staying silent would leave the user with a quietly incomplete `sig/` and us with an unreported bug.
|
|
66
|
+
# Reported on stderr so it never contaminates `--print` output being piped into a file.
|
|
67
|
+
def report_unrenderable(unrenderable)
|
|
68
|
+
return if unrenderable.empty?
|
|
69
|
+
|
|
70
|
+
@err.puts(
|
|
71
|
+
"rigor sig-gen: skipped #{unrenderable.size} method(s) whose generated RBS does not parse. " \
|
|
72
|
+
"This is a bug in Rigor's RBS rendering, not in your code — please report it. " \
|
|
73
|
+
"The remaining signatures are unaffected."
|
|
74
|
+
)
|
|
75
|
+
unrenderable.each do |method|
|
|
76
|
+
@err.puts(" #{method.path}: #{method.class_name}##{method.method_name}")
|
|
77
|
+
@err.puts(" rendered: #{method.rbs}")
|
|
78
|
+
@err.puts(" #{method.error}")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
60
82
|
def dispatch_print_or_diff(candidates, mode, options)
|
|
61
83
|
SigGen::Renderer.new(out: @out).render(
|
|
62
84
|
candidates: candidates,
|
|
@@ -66,6 +88,7 @@ module Rigor
|
|
|
66
88
|
)
|
|
67
89
|
end
|
|
68
90
|
|
|
91
|
+
# @return [Integer] exit status — non-zero when a file the user asked to write could not be written.
|
|
69
92
|
def dispatch_write(candidates, configuration, options)
|
|
70
93
|
layout_index = SigGen::LayoutIndex.new(signature_paths: configuration.signature_paths)
|
|
71
94
|
path_mapper = SigGen::PathMapper.new(configuration: configuration, layout_index: layout_index)
|
|
@@ -74,6 +97,10 @@ module Rigor
|
|
|
74
97
|
results = writer.write_all(candidates)
|
|
75
98
|
|
|
76
99
|
SigGen::Renderer.new(out: @out).render_write(results: results, format: options.fetch(:format))
|
|
100
|
+
# An assembled file that does not parse is refused rather than written. The user asked for a write and
|
|
101
|
+
# did not get one, so the command must not report success — a green `sig-gen --write` in CI would
|
|
102
|
+
# otherwise mean nothing.
|
|
103
|
+
results.any? { |result| result.action == :skipped_invalid_rbs } ? 1 : 0
|
|
77
104
|
end
|
|
78
105
|
|
|
79
106
|
# Slice 3 — collect call-site argument observations when `--params=observed` is set. When `--observe=PATH` is not
|
|
@@ -37,9 +37,7 @@ module Rigor
|
|
|
37
37
|
# the SKILL. Also spelled `describe`, and
|
|
38
38
|
# surfaced top-level as `rigor describe`.
|
|
39
39
|
#
|
|
40
|
-
#
|
|
41
|
-
# deprecation notice; they are removed in v0.3.0 (see docs/ROADMAP.md § "Scheduled CLI deprecations"). `describe` is
|
|
42
|
-
# a no-argument action, not a name-slot verb, so it stays first-class alongside `--describe`.
|
|
40
|
+
# `describe` is a no-argument action, not a name-slot verb, so it stays first-class alongside `--describe`.
|
|
43
41
|
class SkillCommand < Command
|
|
44
42
|
USAGE = <<~USAGE
|
|
45
43
|
Usage: rigor skill [<name>] [--full <name>] [--path <name>] [--list] [--describe]
|
|
@@ -60,30 +58,14 @@ module Rigor
|
|
|
60
58
|
rigor skill --full rigor-baseline-reduce
|
|
61
59
|
rigor skill --path rigor-baseline-reduce
|
|
62
60
|
rigor skill --describe (also: rigor describe)
|
|
63
|
-
|
|
64
|
-
Deprecated (removed in v0.3.0) — use the forms above:
|
|
65
|
-
rigor skill list -> rigor skill --list
|
|
66
|
-
rigor skill print <name> -> rigor skill <name>
|
|
67
|
-
rigor skill path <name> -> rigor skill --path <name>
|
|
68
61
|
USAGE
|
|
69
62
|
|
|
70
63
|
# The bundled skills live at `<gem_root>/skills/`. From `lib/rigor/cli/skill_command.rb` that is three directories
|
|
71
64
|
# up.
|
|
72
65
|
SKILLS_ROOT = File.expand_path("../../../skills", __dir__)
|
|
73
66
|
|
|
74
|
-
# The verb subcommands the flags superseded keep working with a stderr deprecation notice until this version drops
|
|
75
|
-
# them. Each maps to the canonical advice printed and the flag it rewrites to.
|
|
76
|
-
LEGACY_VERB_REMOVAL = "v0.3.0"
|
|
77
|
-
LEGACY_VERBS = {
|
|
78
|
-
"list" => { old: "list", advice: "--list", flag: "--list" },
|
|
79
|
-
"print" => { old: "print <name>", advice: "<name>", flag: "--print" },
|
|
80
|
-
"path" => { old: "path <name>", advice: "--path <name>", flag: "--path" }
|
|
81
|
-
}.freeze
|
|
82
|
-
|
|
83
67
|
# @return [Integer] CLI exit status.
|
|
84
68
|
def run
|
|
85
|
-
rewrite_legacy_verb!
|
|
86
|
-
|
|
87
69
|
case @argv.first
|
|
88
70
|
when nil
|
|
89
71
|
run_list
|
|
@@ -112,19 +94,6 @@ module Rigor
|
|
|
112
94
|
|
|
113
95
|
private
|
|
114
96
|
|
|
115
|
-
# Translate a deprecated verb spelling into its flag form, warning once on stderr, so the dispatch above only
|
|
116
|
-
# handles canonical forms.
|
|
117
|
-
def rewrite_legacy_verb!
|
|
118
|
-
spec = LEGACY_VERBS[@argv.first]
|
|
119
|
-
return unless spec
|
|
120
|
-
|
|
121
|
-
@err.puts(
|
|
122
|
-
"rigor skill: `#{spec.fetch(:old)}` is deprecated and will be removed in " \
|
|
123
|
-
"#{LEGACY_VERB_REMOVAL}; use `rigor skill #{spec.fetch(:advice)}` instead."
|
|
124
|
-
)
|
|
125
|
-
@argv[0] = spec.fetch(:flag)
|
|
126
|
-
end
|
|
127
|
-
|
|
128
97
|
def run_list
|
|
129
98
|
skills = discover_skills
|
|
130
99
|
if skills.empty?
|
|
@@ -12,6 +12,7 @@ require_relative "../inference/flow_tracer"
|
|
|
12
12
|
require_relative "../inference/scope_indexer"
|
|
13
13
|
require_relative "command"
|
|
14
14
|
require_relative "trace_renderer"
|
|
15
|
+
require_relative "probe_environment"
|
|
15
16
|
|
|
16
17
|
module Rigor
|
|
17
18
|
class CLI
|
|
@@ -90,13 +91,11 @@ module Rigor
|
|
|
90
91
|
0
|
|
91
92
|
end
|
|
92
93
|
|
|
93
|
-
# Mirrors the single-file path `rigor type-of` takes:
|
|
94
|
-
#
|
|
94
|
+
# Mirrors the single-file path `rigor type-of` takes: the plugin-aware environment (so a trace replays the
|
|
95
|
+
# same dispatch `rigor check` would, including plugin-synthesized RBS — see {ProbeEnvironment}), an empty
|
|
96
|
+
# seed scope, one statement-level evaluation of the whole program — but recorded under the FlowTracer.
|
|
95
97
|
def record_events(root, file, configuration)
|
|
96
|
-
environment =
|
|
97
|
-
libraries: configuration.libraries,
|
|
98
|
-
signature_paths: configuration.signature_paths
|
|
99
|
-
)
|
|
98
|
+
environment = ProbeEnvironment.build(configuration: configuration, source_files: [file])
|
|
100
99
|
scope = Scope.empty(environment: environment, source_path: file)
|
|
101
100
|
Inference::FlowTracer.record { scope.evaluate(root) }
|
|
102
101
|
end
|
|
@@ -13,6 +13,7 @@ require_relative "../inference/scope_indexer"
|
|
|
13
13
|
require_relative "type_of_renderer"
|
|
14
14
|
require_relative "command"
|
|
15
15
|
require_relative "options"
|
|
16
|
+
require_relative "probe_environment"
|
|
16
17
|
|
|
17
18
|
module Rigor
|
|
18
19
|
class CLI
|
|
@@ -92,14 +93,14 @@ module Rigor
|
|
|
92
93
|
0
|
|
93
94
|
end
|
|
94
95
|
|
|
95
|
-
# Builds
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
96
|
+
# Builds the plugin-aware environment relative to the probed file, so the reported type matches what `rigor
|
|
97
|
+
# check` computes for the same position — including types synthesized from inline RBS annotations by the
|
|
98
|
+
# ADR-93 auto-wired `rigor-rbs-inline` plugin (see {ProbeEnvironment} for the #162 misattribution this
|
|
99
|
+
# closes). The probed file is threaded as the synthesizer's `source_files:`. Project-RBS auto-detection
|
|
100
|
+
# roots at CWD today; future work will walk parent directories to find the enclosing `Gemfile`/`*.gemspec`
|
|
101
|
+
# so probes against files outside the current process's CWD still see the right `sig/` tree.
|
|
102
|
+
def project_environment(file, configuration)
|
|
103
|
+
ProbeEnvironment.build(configuration: configuration, source_files: [file])
|
|
103
104
|
end
|
|
104
105
|
|
|
105
106
|
def file_exists?(file)
|
|
@@ -11,6 +11,7 @@ require_relative "../scope"
|
|
|
11
11
|
require_relative "type_scan_renderer"
|
|
12
12
|
require_relative "type_scan_report"
|
|
13
13
|
require_relative "command"
|
|
14
|
+
require_relative "probe_environment"
|
|
14
15
|
|
|
15
16
|
module Rigor
|
|
16
17
|
class CLI
|
|
@@ -69,20 +70,19 @@ module Rigor
|
|
|
69
70
|
|
|
70
71
|
def scan_paths(paths, options)
|
|
71
72
|
configuration = Configuration.load(options.fetch(:config))
|
|
72
|
-
scope = Scope.empty(environment: project_environment(configuration))
|
|
73
|
+
scope = Scope.empty(environment: project_environment(configuration, paths))
|
|
73
74
|
scanner = Inference::CoverageScanner.new(scope: scope)
|
|
74
75
|
accumulator = ScanAccumulator.new
|
|
75
76
|
paths.each { |path| scan_one(path, scanner, accumulator, configuration) }
|
|
76
77
|
accumulator.to_report(paths, options)
|
|
77
78
|
end
|
|
78
79
|
|
|
79
|
-
# Builds
|
|
80
|
-
# `libraries:` / `signature_paths:` keys when present.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
80
|
+
# Builds the plugin-aware environment that auto-detects `<cwd>/sig` by default and honours the
|
|
81
|
+
# configuration's `libraries:` / `signature_paths:` keys when present. The scanned `paths` are threaded as
|
|
82
|
+
# the plugin `source_rbs_synthesizer` inputs so coverage reflects the same synthesized RBS `rigor check`
|
|
83
|
+
# sees (see {ProbeEnvironment}).
|
|
84
|
+
def project_environment(configuration, source_files)
|
|
85
|
+
ProbeEnvironment.build(configuration: configuration, source_files: source_files)
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def scan_one(path, scanner, accumulator, configuration)
|
data/lib/rigor/cli.rb
CHANGED
|
@@ -11,7 +11,7 @@ require_relative "analysis/diagnostic"
|
|
|
11
11
|
require_relative "analysis/result"
|
|
12
12
|
require_relative "cli/options"
|
|
13
13
|
require_relative "cli/diagnostic_formats"
|
|
14
|
-
require_relative "
|
|
14
|
+
require_relative "ci_detector"
|
|
15
15
|
|
|
16
16
|
module Rigor
|
|
17
17
|
# The CLI class is a dispatcher: each `run_*` method delegates to a command-specific class once the command grows
|
|
@@ -20,6 +20,16 @@ module Rigor
|
|
|
20
20
|
class CLI # rubocop:disable Metrics/ClassLength
|
|
21
21
|
EXIT_USAGE = 64
|
|
22
22
|
|
|
23
|
+
# The published location of `schemas/rigor-config.schema.json`, written into the `.rigor.yml` that
|
|
24
|
+
# `rigor init` generates so an editor validates the file as the user types.
|
|
25
|
+
#
|
|
26
|
+
# This MUST equal the schema's own `$id`, and it MUST resolve. Both are gated
|
|
27
|
+
# (`spec/rigor/config_schema_spec.rb`) because neither failure has a symptom: an unreachable schema
|
|
28
|
+
# is indistinguishable from a schema with no complaints, so a stale URL here silently disables
|
|
29
|
+
# validation for every project that ran `rigor init`. It pointed at a nonexistent org until
|
|
30
|
+
# 2026-07-17 and nothing noticed. See `docs/internal-spec/config.md`.
|
|
31
|
+
CONFIG_SCHEMA_URL = "https://github.com/rigortype/rigor/raw/master/schemas/rigor-config.schema.json"
|
|
32
|
+
|
|
23
33
|
HANDLERS = {
|
|
24
34
|
"check" => :run_check,
|
|
25
35
|
"init" => :run_init,
|
|
@@ -138,9 +148,10 @@ module Rigor
|
|
|
138
148
|
# most likely to want to edit.
|
|
139
149
|
def init_template
|
|
140
150
|
<<~YAML
|
|
141
|
-
# yaml-language-server: $schema
|
|
142
|
-
# Rigor configuration.
|
|
143
|
-
#
|
|
151
|
+
# yaml-language-server: $schema=#{CONFIG_SCHEMA_URL}
|
|
152
|
+
# Rigor configuration. The full key reference is
|
|
153
|
+
# https://github.com/rigortype/rigor/blob/master/docs/manual/03-configuration.md
|
|
154
|
+
# (or run `rigor docs configuration`).
|
|
144
155
|
#
|
|
145
156
|
# Keys you may want to edit:
|
|
146
157
|
# - target_ruby: minimum Ruby version your project targets.
|
data/lib/rigor/config_audit.rb
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "did_you_mean"
|
|
4
|
+
|
|
3
5
|
require_relative "signature_path_audit"
|
|
4
|
-
|
|
6
|
+
# ADR-87 WD4 — only the pure rule-id table (`ALL_RULES` / `RULE_FAMILIES`) is needed, so require the light
|
|
7
|
+
# `rule_ids.rb` rather than the engine-pulling `check_rules.rb`; keeps `config_audit` (loaded by every check)
|
|
8
|
+
# off the inference engine so the boot-slimming hit path stays engine-free.
|
|
9
|
+
require_relative "analysis/check_rules/rule_ids"
|
|
5
10
|
|
|
6
11
|
module Rigor
|
|
7
12
|
# Audits a loaded {Configuration} for the class of mistake where a configured value
|
|
@@ -36,12 +41,53 @@ module Rigor
|
|
|
36
41
|
# paths resolve against (the CLI's CWD), used only by the explicit-path checks.
|
|
37
42
|
# @return [Array<Warning>]
|
|
38
43
|
def self.warnings(configuration, project_root: Dir.pwd)
|
|
39
|
-
|
|
44
|
+
unknown_key_warnings(configuration) +
|
|
45
|
+
signature_path_warnings(configuration) +
|
|
40
46
|
library_warnings(configuration) +
|
|
41
47
|
rule_token_warnings(configuration) +
|
|
42
48
|
explicit_path_warnings(configuration, project_root)
|
|
43
49
|
end
|
|
44
50
|
|
|
51
|
+
# Top-level keys the loader does not own. The archetypal case is a typo — `excludee:` for
|
|
52
|
+
# `exclude:` — which the loader drops in silence, so the exclusion never applies and the run
|
|
53
|
+
# reports errors from the very files the user meant to skip, with nothing anywhere saying the key
|
|
54
|
+
# did nothing. That is exactly this module's class of mistake, and it was the one whole-key
|
|
55
|
+
# instance of it going unwarned.
|
|
56
|
+
#
|
|
57
|
+
# A reserved namespace ({Configuration::RESERVED_NAMESPACES}) is unknown *on purpose* — another
|
|
58
|
+
# implementation reads it from the same file — so it is exempt. {Configuration#unknown_keys}
|
|
59
|
+
# already applies that exemption; warning here would push users to delete the key their other
|
|
60
|
+
# tool needs.
|
|
61
|
+
#
|
|
62
|
+
# **Top level only, and deliberately.** A nested check would have to name each group's known keys,
|
|
63
|
+
# and DEFAULTS cannot supply them: `DEFAULTS["dependencies"]` carries `source_inference` and
|
|
64
|
+
# `budget_per_gem` but not `budget_overrun_strategy`, which is real (`Configuration::Dependencies`
|
|
65
|
+
# reads it), documented, and schema-declared — so a DEFAULTS-keyed nested check would flag a
|
|
66
|
+
# working config. `severity_overrides:` is an open map of rule ids besides. Nested unknown keys are
|
|
67
|
+
# the schema tier's job: every nested object in `schemas/rigor-config.schema.json` is
|
|
68
|
+
# `additionalProperties: false`, and `config_schema_spec` keeps it complete
|
|
69
|
+
# ([ADR-99](../../docs/adr/99-config-schema-authority.md), `docs/internal-spec/config.md`).
|
|
70
|
+
def self.unknown_key_warnings(configuration)
|
|
71
|
+
configuration.unknown_keys.map do |key|
|
|
72
|
+
suggestion = suggest_config_key(key)
|
|
73
|
+
hint = suggestion ? " Did you mean `#{suggestion}`?" : ""
|
|
74
|
+
Warning.new(
|
|
75
|
+
kind: :unknown_key,
|
|
76
|
+
message: "`#{key}` is not a recognized configuration key; it has no effect.#{hint}",
|
|
77
|
+
fields: { "key" => key, "suggestion" => suggestion }
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Nearest known key, or nil. Uses `DidYouMean::SpellChecker` — the engine behind Ruby's own
|
|
83
|
+
# `NoMethodError` hints, and the one `Plugin::Base.suggest` wraps. Called directly rather than
|
|
84
|
+
# through that helper because `plugin/base.rb` pulls Prism and the diagnostic model in, and
|
|
85
|
+
# ADR-87 WD4 keeps this file (loaded by every check) off that path; `did_you_mean` is stdlib.
|
|
86
|
+
def self.suggest_config_key(key)
|
|
87
|
+
DidYouMean::SpellChecker.new(dictionary: Configuration::KNOWN_KEYS).correct(key.to_s).first
|
|
88
|
+
end
|
|
89
|
+
private_class_method :suggest_config_key
|
|
90
|
+
|
|
45
91
|
# `signature_paths:` entries that resolve to nothing — delegated to {SignaturePathAudit},
|
|
46
92
|
# which mirrors the loader's `directory?` + recursive `**/*.rbs` acceptance test.
|
|
47
93
|
def self.signature_path_warnings(configuration)
|
|
@@ -37,11 +37,15 @@ module Rigor
|
|
|
37
37
|
"call.wrong-arity" => :error,
|
|
38
38
|
"call.argument-type-mismatch" => :warning,
|
|
39
39
|
"call.possible-nil-receiver" => :warning,
|
|
40
|
+
"call.raise-non-exception" => :warning,
|
|
40
41
|
"flow.always-raises" => :warning,
|
|
41
42
|
"flow.unreachable-branch" => :info,
|
|
42
43
|
"flow.dead-assignment" => :info,
|
|
43
44
|
"flow.always-truthy-condition" => :info,
|
|
44
45
|
"flow.unreachable-clause" => :info,
|
|
46
|
+
"flow.duplicate-hash-key" => :info,
|
|
47
|
+
"flow.return-in-ensure" => :info,
|
|
48
|
+
"flow.shadowed-rescue-clause" => :info,
|
|
45
49
|
"assert.type-mismatch" => :error,
|
|
46
50
|
"dump.type" => :info,
|
|
47
51
|
"def.return-type-mismatch" => :warning,
|
|
@@ -50,6 +54,16 @@ module Rigor
|
|
|
50
54
|
"def.override-return-widened" => :off,
|
|
51
55
|
"def.override-param-narrowed" => :off,
|
|
52
56
|
"def.ivar-write-mismatch" => :warning,
|
|
57
|
+
# ADR-8 companion (PHPStan IgnoreParseErrorRule-modelled): a broken suppression comment is
|
|
58
|
+
# equally bad in every profile — it silently fails to do what the author believes it does — so
|
|
59
|
+
# both rules stay :warning across all three profiles (including strict).
|
|
60
|
+
"suppression.unknown-rule" => :warning,
|
|
61
|
+
"suppression.empty" => :warning,
|
|
62
|
+
"suppression.unknown-marker" => :warning,
|
|
63
|
+
# ADR-100 — a new required diagnostic (ADR-50 WD1), so it is `:off` in every shipped profile and
|
|
64
|
+
# reaches a user only through the `use-of-void-value` bleeding-edge feature, which overrides this
|
|
65
|
+
# to `:warning`.
|
|
66
|
+
"static.value-use.void" => :off,
|
|
53
67
|
# Opt-in author assertion: you only see it if you wrote a
|
|
54
68
|
# `conforms-to` directive, so it stays a :warning even in
|
|
55
69
|
# lenient — it is never unsolicited noise.
|
|
@@ -62,6 +76,7 @@ module Rigor
|
|
|
62
76
|
"call.wrong-arity" => :error,
|
|
63
77
|
"call.argument-type-mismatch" => :error,
|
|
64
78
|
"call.possible-nil-receiver" => :error,
|
|
79
|
+
"call.raise-non-exception" => :error,
|
|
65
80
|
"flow.always-raises" => :error,
|
|
66
81
|
"flow.unreachable-branch" => :warning,
|
|
67
82
|
"flow.dead-assignment" => :warning,
|
|
@@ -71,6 +86,9 @@ module Rigor
|
|
|
71
86
|
# green; promote to :warning once Mastodon/GitLab/Redmine triage
|
|
72
87
|
# to zero net false positives.
|
|
73
88
|
"flow.unreachable-clause" => :info,
|
|
89
|
+
"flow.duplicate-hash-key" => :warning,
|
|
90
|
+
"flow.return-in-ensure" => :warning,
|
|
91
|
+
"flow.shadowed-rescue-clause" => :warning,
|
|
74
92
|
"assert.type-mismatch" => :error,
|
|
75
93
|
"dump.type" => :info,
|
|
76
94
|
"def.return-type-mismatch" => :warning,
|
|
@@ -79,6 +97,10 @@ module Rigor
|
|
|
79
97
|
"def.override-return-widened" => :warning,
|
|
80
98
|
"def.override-param-narrowed" => :warning,
|
|
81
99
|
"def.ivar-write-mismatch" => :warning,
|
|
100
|
+
"suppression.unknown-rule" => :warning,
|
|
101
|
+
"suppression.empty" => :warning,
|
|
102
|
+
"suppression.unknown-marker" => :warning,
|
|
103
|
+
"static.value-use.void" => :off,
|
|
82
104
|
"rbs_extended.unsatisfied-conformance" => :warning
|
|
83
105
|
}.freeze,
|
|
84
106
|
strict: {
|
|
@@ -88,6 +110,7 @@ module Rigor
|
|
|
88
110
|
"call.wrong-arity" => :error,
|
|
89
111
|
"call.argument-type-mismatch" => :error,
|
|
90
112
|
"call.possible-nil-receiver" => :error,
|
|
113
|
+
"call.raise-non-exception" => :error,
|
|
91
114
|
"flow.always-raises" => :error,
|
|
92
115
|
"flow.unreachable-branch" => :error,
|
|
93
116
|
"flow.dead-assignment" => :error,
|
|
@@ -96,6 +119,9 @@ module Rigor
|
|
|
96
119
|
# below its :error siblings) while it proves out — see the
|
|
97
120
|
# balanced-profile note above.
|
|
98
121
|
"flow.unreachable-clause" => :warning,
|
|
122
|
+
"flow.duplicate-hash-key" => :error,
|
|
123
|
+
"flow.return-in-ensure" => :error,
|
|
124
|
+
"flow.shadowed-rescue-clause" => :error,
|
|
99
125
|
"assert.type-mismatch" => :error,
|
|
100
126
|
"dump.type" => :error,
|
|
101
127
|
"def.return-type-mismatch" => :error,
|
|
@@ -104,6 +130,11 @@ module Rigor
|
|
|
104
130
|
"def.override-return-widened" => :error,
|
|
105
131
|
"def.override-param-narrowed" => :error,
|
|
106
132
|
"def.ivar-write-mismatch" => :error,
|
|
133
|
+
"suppression.unknown-rule" => :warning,
|
|
134
|
+
"suppression.empty" => :warning,
|
|
135
|
+
"suppression.unknown-marker" => :warning,
|
|
136
|
+
# `:off` even under strict: the gate is `bleeding_edge:`, not the profile (ADR-50 WD1 / ADR-100).
|
|
137
|
+
"static.value-use.void" => :off,
|
|
107
138
|
"rbs_extended.unsatisfied-conformance" => :error
|
|
108
139
|
}.freeze
|
|
109
140
|
}.freeze
|