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::current_root_envelope_mode(),
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}