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