Skip to main content

fallow_cli/
explain.rs

1//! CLI rendering for explainable rule output.
2//!
3//! The rule registry and JSON contract live in `fallow-api` so embedders and
4//! MCP do not depend on the CLI crate. This module keeps terminal rendering and
5//! compatibility re-exports for existing CLI call sites.
6
7use std::process::ExitCode;
8
9use colored::Colorize;
10use fallow_config::OutputFormat;
11
12pub use fallow_api::{
13    CHECK_RULES, DUPES_RULES, FLAGS_RULES, HEALTH_RULES, RuleDef, RuleGuide, SECURITY_RULES,
14    coverage_analyze_meta, coverage_setup_meta, rule_by_id, rule_by_token, rule_docs_url,
15    rule_guide, security_meta, serialize_explain_programmatic_json,
16};
17
18/// Run the standalone explain subcommand.
19#[must_use]
20pub fn run_explain(issue_type: &str, output: OutputFormat) -> ExitCode {
21    let Some(rule) = rule_by_token(issue_type) else {
22        return crate::error::emit_error(
23            &fallow_api::unknown_explain_error(issue_type).message,
24            2,
25            output,
26        );
27    };
28    let guide = rule_guide(rule);
29    match output {
30        OutputFormat::Json => match serialize_explain_programmatic_json(
31            issue_type,
32            crate::output_runtime::current_root_envelope_mode(),
33            crate::output_runtime::telemetry_analysis_run_id().as_deref(),
34        ) {
35            Ok(value) => crate::report::emit_json(&value, "explain"),
36            Err(error) => crate::error::emit_error(&error.message, error.exit_code, output),
37        },
38        OutputFormat::Human => print_explain_human(rule, &guide),
39        OutputFormat::Compact => print_explain_compact(rule),
40        OutputFormat::Markdown => print_explain_markdown(rule, &guide),
41        OutputFormat::Sarif
42        | OutputFormat::CodeClimate
43        | OutputFormat::PrCommentGithub
44        | OutputFormat::PrCommentGitlab
45        | OutputFormat::ReviewGithub
46        | OutputFormat::ReviewGitlab
47        | OutputFormat::Badge
48        | OutputFormat::GithubAnnotations
49        | OutputFormat::GithubSummary => crate::error::emit_error(
50            "explain supports human, compact, markdown, and json output",
51            2,
52            output,
53        ),
54    }
55}
56
57fn print_explain_human(rule: &RuleDef, guide: &RuleGuide) -> ExitCode {
58    println!("{}", rule.name.bold());
59    println!("{}", rule.id.dimmed());
60    println!();
61    println!("{}", rule.short);
62    println!();
63    println!("{}", "Why it matters".bold());
64    println!("{}", rule.full);
65    println!();
66    println!("{}", "Example".bold());
67    println!("{}", guide.example);
68    println!();
69    println!("{}", "How to fix".bold());
70    println!("{}", guide.how_to_fix);
71    println!();
72    println!("{} {}", "Docs:".dimmed(), rule_docs_url(rule).dimmed());
73    ExitCode::SUCCESS
74}
75
76fn print_explain_compact(rule: &RuleDef) -> ExitCode {
77    println!("explain:{}:{}:{}", rule.id, rule.short, rule_docs_url(rule));
78    ExitCode::SUCCESS
79}
80
81fn print_explain_markdown(rule: &RuleDef, guide: &RuleGuide) -> ExitCode {
82    println!("# {}", rule.name);
83    println!();
84    println!("`{}`", rule.id);
85    println!();
86    println!("{}", rule.short);
87    println!();
88    println!("## Why it matters");
89    println!();
90    println!("{}", rule.full);
91    println!();
92    println!("## Example");
93    println!();
94    println!("{}", guide.example);
95    println!();
96    println!("## How to fix");
97    println!();
98    println!("{}", guide.how_to_fix);
99    println!();
100    println!("[Docs]({})", rule_docs_url(rule));
101    ExitCode::SUCCESS
102}