Skip to main content

mago_reporting/formatter/
mod.rs

1use std::io::Write;
2
3use mago_database::ReadDatabase;
4
5use crate::IssueCollection;
6use crate::Level;
7use crate::color::ColorChoice;
8use crate::error::ReportingError;
9
10pub mod ariadne;
11pub mod checkstyle;
12pub mod code_count;
13pub mod count;
14pub mod emacs;
15pub mod github;
16#[cfg(feature = "serde")]
17pub mod gitlab;
18#[cfg(feature = "serde")]
19pub mod json;
20pub mod medium;
21pub mod rich;
22#[cfg(feature = "serde")]
23pub mod sarif;
24pub mod short;
25pub mod utils;
26
27/// Configuration for formatters.
28#[derive(Debug, Clone)]
29pub struct FormatterConfig {
30    /// Choice for colorizing output.
31    pub color_choice: ColorChoice,
32    /// Whether to sort issues before formatting.
33    pub sort: bool,
34    /// Minimum report level (filter out lower severity issues).
35    pub minimum_level: Option<Level>,
36    /// Whether to filter to only fixable issues.
37    pub filter_fixable: bool,
38    /// Optional editor URL template for OSC 8 terminal hyperlinks.
39    ///
40    /// Supported placeholders: `%file%` (absolute path), `%line%`, `%column%`.
41    /// Example: `"phpstorm://open?file=%file%&line=%line%"`
42    pub editor_url: Option<String>,
43}
44
45/// Trait for formatting issues to a writer.
46pub trait Formatter {
47    /// Format issues and write them to the provided writer.
48    ///
49    /// # Arguments
50    ///
51    /// * `writer` - The writer to output formatted issues to
52    /// * `issues` - The collection of issues to format
53    /// * `database` - The read database for accessing source files
54    /// * `config` - Configuration for formatting behavior
55    ///
56    /// # Errors
57    ///
58    /// Returns an error if formatting or writing fails.
59    fn format(
60        &self,
61        writer: &mut dyn Write,
62        issues: &IssueCollection,
63        database: &ReadDatabase,
64        config: &FormatterConfig,
65    ) -> Result<(), ReportingError>;
66}
67
68/// The format to use for reporting.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumString, strum::VariantNames, Default)]
70#[strum(serialize_all = "kebab-case")]
71pub enum ReportingFormat {
72    /// Rich diagnostic format with full context.
73    #[default]
74    Rich,
75    /// Medium diagnostic format with balanced context.
76    Medium,
77    /// Short diagnostic format with minimal context.
78    Short,
79    /// Ariadne diagnostic format.
80    Ariadne,
81    /// GitHub Actions format.
82    Github,
83    /// GitLab Code Quality format.
84    #[cfg(feature = "serde")]
85    Gitlab,
86    /// JSON format.
87    #[cfg(feature = "serde")]
88    Json,
89    /// Issue count by severity.
90    Count,
91    /// Issue count by code.
92    CodeCount,
93    /// Checkstyle XML format.
94    Checkstyle,
95    /// Emacs compilation mode format.
96    Emacs,
97    /// SARIF format (Static Analysis Results Interchange Format).
98    #[cfg(feature = "serde")]
99    Sarif,
100}
101
102impl ReportingFormat {
103    #[must_use]
104    pub const fn requires_output_when_empty(self) -> bool {
105        match self {
106            Self::Checkstyle => true,
107            #[cfg(feature = "serde")]
108            Self::Gitlab | Self::Json | Self::Sarif => true,
109            _ => false,
110        }
111    }
112}
113
114/// Dispatch to the appropriate formatter based on the format type.
115///
116/// This function performs static dispatch using enum matching for optimal performance.
117pub(crate) fn dispatch_format(
118    format: ReportingFormat,
119    writer: &mut dyn Write,
120    issues: &IssueCollection,
121    database: &ReadDatabase,
122    config: &FormatterConfig,
123) -> Result<(), ReportingError> {
124    match format {
125        ReportingFormat::Rich => rich::RichFormatter.format(writer, issues, database, config),
126        ReportingFormat::Medium => medium::MediumFormatter.format(writer, issues, database, config),
127        ReportingFormat::Short => short::ShortFormatter.format(writer, issues, database, config),
128        ReportingFormat::Ariadne => ariadne::AriadneFormatter.format(writer, issues, database, config),
129        #[cfg(feature = "serde")]
130        ReportingFormat::Json => json::JsonFormatter.format(writer, issues, database, config),
131        ReportingFormat::Github => github::GithubFormatter.format(writer, issues, database, config),
132        #[cfg(feature = "serde")]
133        ReportingFormat::Gitlab => gitlab::GitlabFormatter.format(writer, issues, database, config),
134        ReportingFormat::Checkstyle => checkstyle::CheckstyleFormatter.format(writer, issues, database, config),
135        ReportingFormat::Emacs => emacs::EmacsFormatter.format(writer, issues, database, config),
136        ReportingFormat::Count => count::CountFormatter.format(writer, issues, database, config),
137        ReportingFormat::CodeCount => code_count::CodeCountFormatter.format(writer, issues, database, config),
138        #[cfg(feature = "serde")]
139        ReportingFormat::Sarif => sarif::SarifFormatter.format(writer, issues, database, config),
140    }
141}