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, security_meta, serialize_explain_programmatic_json,
18};
19
20#[must_use]
22pub 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_error(
29 &fallow_api::unknown_explain_error(issue_type).message,
30 2,
31 output,
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_error_with_style(
42 &error.message,
43 error.exit_code,
44 output,
45 json_style,
46 ),
47 },
48 OutputFormat::Human => print_explain_human(rule, &guide),
49 OutputFormat::Compact => print_explain_compact(rule),
50 OutputFormat::Markdown => print_explain_markdown(rule, &guide),
51 OutputFormat::Sarif
52 | OutputFormat::CodeClimate
53 | OutputFormat::PrCommentGithub
54 | OutputFormat::PrCommentGitlab
55 | OutputFormat::ReviewGithub
56 | OutputFormat::ReviewGitlab
57 | OutputFormat::Badge
58 | OutputFormat::GithubAnnotations
59 | OutputFormat::GithubSummary => crate::error::emit_error(
60 "explain supports human, compact, markdown, and json output",
61 2,
62 output,
63 ),
64 }
65}
66
67fn render_explain_json(
68 issue_type: &str,
69 json_style: crate::json_style::JsonStyle,
70) -> Result<String, fallow_api::ProgrammaticError> {
71 let value = serialize_explain_programmatic_json(
72 issue_type,
73 crate::output_runtime::current_root_envelope_mode(),
74 crate::output_runtime::telemetry_analysis_run_id().as_deref(),
75 )?;
76 json_style.serialize(&value).map_err(|error| {
77 fallow_api::ProgrammaticError::new(format!("JSON serialization error: {error}"), 2)
78 .with_code("json_serialization")
79 })
80}
81
82fn print_explain_human(rule: &RuleDef, guide: &RuleGuide) -> ExitCode {
83 println!("{}", rule.name.bold());
84 println!("{}", rule.id.dimmed());
85 println!();
86 println!("{}", rule.short);
87 println!();
88 println!("{}", "Why it matters".bold());
89 println!("{}", rule.full);
90 println!();
91 println!("{}", "Example".bold());
92 println!("{}", guide.example);
93 println!();
94 println!("{}", "How to fix".bold());
95 println!("{}", guide.how_to_fix);
96 println!();
97 println!("{} {}", "Docs:".dimmed(), rule_docs_url(rule).dimmed());
98 ExitCode::SUCCESS
99}
100
101fn print_explain_compact(rule: &RuleDef) -> ExitCode {
102 println!("explain:{}:{}:{}", rule.id, rule.short, rule_docs_url(rule));
103 ExitCode::SUCCESS
104}
105
106fn print_explain_markdown(rule: &RuleDef, guide: &RuleGuide) -> ExitCode {
107 println!("# {}", rule.name);
108 println!();
109 println!("`{}`", rule.id);
110 println!();
111 println!("{}", rule.short);
112 println!();
113 println!("## Why it matters");
114 println!();
115 println!("{}", rule.full);
116 println!();
117 println!("## Example");
118 println!();
119 println!("{}", guide.example);
120 println!();
121 println!("## How to fix");
122 println!();
123 println!("{}", guide.how_to_fix);
124 println!();
125 println!("[Docs]({})", rule_docs_url(rule));
126 ExitCode::SUCCESS
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn explain_json_respects_explicit_style() {
135 let compact = render_explain_json("unused-export", crate::json_style::JsonStyle::Compact)
136 .expect("compact explain JSON should serialize");
137 let pretty = render_explain_json("unused-export", crate::json_style::JsonStyle::Pretty)
138 .expect("pretty explain JSON should serialize");
139
140 assert!(
141 !compact.contains('\n'),
142 "compact JSON must stay on one line"
143 );
144 assert!(pretty.contains("\n \""), "pretty JSON must be indented");
145 assert_eq!(
146 serde_json::from_str::<serde_json::Value>(&compact).unwrap(),
147 serde_json::from_str::<serde_json::Value>(&pretty).unwrap(),
148 );
149 }
150}