Skip to main content

cargo_quality/
formatter.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use std::process::Command;
5
6use masterror::AppResult;
7
8use crate::error::IoError;
9
10/// Rustfmt configuration settings.
11///
12/// This structure holds the hardcoded quality standards for Rust code
13/// formatting. All settings are based on project conventions and ensure
14/// consistent formatting across all codebases without requiring local
15/// .rustfmt.toml files.
16#[derive(Debug, Clone)]
17pub struct RustfmtConfig {
18    pub trailing_comma:               &'static str,
19    pub brace_style:                  &'static str,
20    pub struct_field_align_threshold: u32,
21    pub wrap_comments:                bool,
22    pub format_code_in_doc_comments:  bool,
23    pub struct_lit_single_line:       bool,
24    pub max_width:                    u32,
25    pub imports_granularity:          &'static str,
26    pub group_imports:                &'static str,
27    pub reorder_imports:              bool,
28    pub unstable_features:            bool
29}
30
31impl Default for RustfmtConfig {
32    /// Creates the default configuration matching project quality standards.
33    ///
34    /// # Returns
35    ///
36    /// `RustfmtConfig` with hardcoded quality settings
37    ///
38    /// # Examples
39    ///
40    /// ```
41    /// use cargo_quality::formatter::RustfmtConfig;
42    /// let config = RustfmtConfig::default();
43    /// assert_eq!(config.max_width, 99);
44    /// ```
45    fn default() -> Self {
46        Self {
47            trailing_comma:               "Never",
48            brace_style:                  "SameLineWhere",
49            struct_field_align_threshold: 20,
50            wrap_comments:                true,
51            format_code_in_doc_comments:  true,
52            struct_lit_single_line:       false,
53            max_width:                    99,
54            imports_granularity:          "Crate",
55            group_imports:                "StdExternalCrate",
56            reorder_imports:              true,
57            unstable_features:            true
58        }
59    }
60}
61
62impl RustfmtConfig {
63    /// Converts configuration to rustfmt command-line arguments.
64    ///
65    /// Generates a vector of `--config key=value` arguments that can be
66    /// passed directly to `cargo +nightly fmt`.
67    ///
68    /// # Returns
69    ///
70    /// `Vec<String>` containing all configuration arguments
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use cargo_quality::formatter::RustfmtConfig;
76    /// let config = RustfmtConfig::default();
77    /// let args = config.to_args();
78    /// assert!(args.contains(&"--config".to_string()));
79    /// assert!(args.contains(&"max_width=99".to_string()));
80    /// ```
81    pub fn to_args(&self) -> Vec<String> {
82        vec![
83            "--config".to_string(),
84            format!("trailing_comma={}", self.trailing_comma),
85            "--config".to_string(),
86            format!("brace_style={}", self.brace_style),
87            "--config".to_string(),
88            format!(
89                "struct_field_align_threshold={}",
90                self.struct_field_align_threshold
91            ),
92            "--config".to_string(),
93            format!("wrap_comments={}", self.wrap_comments),
94            "--config".to_string(),
95            format!(
96                "format_code_in_doc_comments={}",
97                self.format_code_in_doc_comments
98            ),
99            "--config".to_string(),
100            format!("struct_lit_single_line={}", self.struct_lit_single_line),
101            "--config".to_string(),
102            format!("max_width={}", self.max_width),
103            "--config".to_string(),
104            format!("imports_granularity={}", self.imports_granularity),
105            "--config".to_string(),
106            format!("group_imports={}", self.group_imports),
107            "--config".to_string(),
108            format!("reorder_imports={}", self.reorder_imports),
109            "--config".to_string(),
110            format!("unstable_features={}", self.unstable_features),
111        ]
112    }
113}
114
115/// Runs cargo +nightly fmt with hardcoded quality configuration.
116///
117/// Executes rustfmt with project-defined quality standards, ignoring any
118/// local .rustfmt.toml files. This ensures consistent formatting across
119/// all projects without configuration file duplication.
120///
121/// # Returns
122///
123/// `AppResult<()>` - Ok if formatting succeeds, error otherwise
124///
125/// # Examples
126///
127/// ```no_run
128/// use cargo_quality::formatter::format_code;
129/// format_code().unwrap();
130/// ```
131pub fn format_code() -> AppResult<()> {
132    let config = RustfmtConfig::default();
133    let args = config.to_args();
134
135    let mut command = Command::new("cargo");
136    command.arg("+nightly").arg("fmt").arg("--");
137
138    for arg in args {
139        command.arg(arg);
140    }
141
142    let status = command.status().map_err(IoError::from)?;
143
144    if status.success() {
145        println!("Code formatted successfully");
146        Ok(())
147    } else {
148        Err(IoError::from(std::io::Error::other(format!(
149            "cargo fmt failed with status: {}",
150            status
151        )))
152        .into())
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_default_config() {
162        let config = RustfmtConfig::default();
163        assert_eq!(config.max_width, 99);
164        assert_eq!(config.trailing_comma, "Never");
165        assert_eq!(config.brace_style, "SameLineWhere");
166        assert_eq!(config.imports_granularity, "Crate");
167        assert_eq!(config.group_imports, "StdExternalCrate");
168        assert!(config.wrap_comments);
169        assert!(config.format_code_in_doc_comments);
170        assert!(!config.struct_lit_single_line);
171        assert!(config.reorder_imports);
172        assert!(config.unstable_features);
173    }
174
175    #[test]
176    fn test_config_to_args() {
177        let config = RustfmtConfig::default();
178        let args = config.to_args();
179
180        assert!(args.contains(&"--config".to_string()));
181        assert!(args.contains(&"max_width=99".to_string()));
182        assert!(args.contains(&"trailing_comma=Never".to_string()));
183        assert!(args.contains(&"brace_style=SameLineWhere".to_string()));
184        assert!(args.contains(&"imports_granularity=Crate".to_string()));
185        assert!(args.contains(&"group_imports=StdExternalCrate".to_string()));
186    }
187
188    #[test]
189    fn test_config_to_args_count() {
190        let config = RustfmtConfig::default();
191        let args = config.to_args();
192        assert_eq!(args.len(), 22);
193    }
194
195    #[test]
196    fn test_config_to_args_pairs() {
197        let config = RustfmtConfig::default();
198        let args = config.to_args();
199
200        for i in (0..args.len()).step_by(2) {
201            assert_eq!(args[i], "--config");
202            assert!(args[i + 1].contains('='));
203        }
204    }
205
206    #[test]
207    fn test_format_code_execution() {
208        let result = format_code();
209        assert!(result.is_ok() || result.is_err());
210    }
211}