1use std::process::ExitCode;
8
9use colored::Colorize;
10use fallow_config::OutputFormat;
11
12use crate::report::sink::outln;
13
14pub use fallow_api::{
15 CHECK_RULES, DUPES_RULES, FLAGS_RULES, HEALTH_RULES, RuleDef, RuleGuide, SECURITY_RULES,
16 coverage_analyze_meta, coverage_setup_meta, rule_by_id, rule_by_token, rule_docs_url,
17 rule_guide, rule_severity_key, security_meta, serialize_explain_programmatic_json,
18};
19
20#[must_use]
22pub(crate) fn run_explain(
23 issue_type: &str,
24 output: OutputFormat,
25 json_style: crate::json_style::JsonStyle,
26) -> ExitCode {
27 let Some(rule) = rule_by_token(issue_type) else {
28 return crate::error::emit_programmatic_error(
29 &fallow_api::unknown_explain_error(issue_type),
30 output,
31 json_style,
32 );
33 };
34 let guide = rule_guide(rule);
35 match output {
36 OutputFormat::Json => match render_explain_json(issue_type, json_style) {
37 Ok(json) => {
38 outln!("{json}");
39 ExitCode::SUCCESS
40 }
41 Err(error) => crate::error::emit_programmatic_error(&error, output, json_style),
42 },
43 OutputFormat::Human => print_explain_human(rule, &guide),
44 OutputFormat::Compact => print_explain_compact(rule),
45 OutputFormat::Markdown => print_explain_markdown(rule, &guide),
46 OutputFormat::Sarif
47 | OutputFormat::CodeClimate
48 | OutputFormat::PrCommentGithub
49 | OutputFormat::PrCommentGitlab
50 | OutputFormat::ReviewGithub
51 | OutputFormat::ReviewGitlab
52 | OutputFormat::Badge
53 | OutputFormat::GithubAnnotations
54 | OutputFormat::GithubSummary => crate::error::emit_error(
55 "explain supports human, compact, markdown, and json output",
56 2,
57 output,
58 ),
59 }
60}
61
62fn render_explain_json(
63 issue_type: &str,
64 json_style: crate::json_style::JsonStyle,
65) -> Result<String, fallow_api::ProgrammaticError> {
66 let value = serialize_explain_programmatic_json(
67 issue_type,
68 crate::output_runtime::telemetry_analysis_run_id().as_deref(),
69 )?;
70 json_style.serialize(&value).map_err(|error| {
71 fallow_api::ProgrammaticError::new(format!("JSON serialization error: {error}"), 2)
72 .with_code("json_serialization")
73 })
74}
75
76fn print_explain_human(rule: &RuleDef, guide: &RuleGuide) -> ExitCode {
77 println!("{}", rule.name.bold());
78 println!("{}", rule.id.dimmed());
79 println!();
80 println!("{}", rule.short);
81 println!();
82 println!("{}", "Why it matters".bold());
83 println!("{}", rule.full);
84 println!();
85 println!("{}", "Example".bold());
86 println!("{}", guide.example);
87 println!();
88 println!("{}", "How to fix".bold());
89 println!("{}", guide.how_to_fix);
90 println!();
91 println!("{} {}", "Docs:".dimmed(), rule_docs_url(rule).dimmed());
92 ExitCode::SUCCESS
93}
94
95fn print_explain_compact(rule: &RuleDef) -> ExitCode {
96 println!("explain:{}:{}:{}", rule.id, rule.short, rule_docs_url(rule));
97 ExitCode::SUCCESS
98}
99
100fn print_explain_markdown(rule: &RuleDef, guide: &RuleGuide) -> ExitCode {
101 println!("# {}", rule.name);
102 println!();
103 println!("`{}`", rule.id);
104 println!();
105 println!("{}", rule.short);
106 println!();
107 println!("## Why it matters");
108 println!();
109 println!("{}", rule.full);
110 println!();
111 println!("## Example");
112 println!();
113 println!("{}", guide.example);
114 println!();
115 println!("## How to fix");
116 println!();
117 println!("{}", guide.how_to_fix);
118 println!();
119 println!("[Docs]({})", rule_docs_url(rule));
120 ExitCode::SUCCESS
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn explain_json_respects_explicit_style() {
129 let compact = render_explain_json("unused-export", crate::json_style::JsonStyle::Compact)
130 .expect("compact explain JSON should serialize");
131 let pretty = render_explain_json("unused-export", crate::json_style::JsonStyle::Pretty)
132 .expect("pretty explain JSON should serialize");
133
134 assert!(
135 !compact.contains('\n'),
136 "compact JSON must stay on one line"
137 );
138 assert!(pretty.contains("\n \""), "pretty JSON must be indented");
139 assert_eq!(
140 serde_json::from_str::<serde_json::Value>(&compact).unwrap(),
141 serde_json::from_str::<serde_json::Value>(&pretty).unwrap(),
142 );
143 }
144}