Skip to main content

cuenv_codegen/
formatter.rs

1//! Code formatting integration
2//!
3//! This module provides formatting capabilities for various programming languages.
4
5use crate::codegen::FormatConfig;
6use crate::{CodegenError, Result};
7
8/// Language-specific code formatter
9#[derive(Debug)]
10pub struct Formatter;
11
12impl Formatter {
13    /// Create a new formatter
14    #[must_use]
15    pub const fn new() -> Self {
16        Self
17    }
18
19    /// Format code content based on language and configuration
20    ///
21    /// # Errors
22    ///
23    /// Returns an error if formatting fails
24    pub fn format(&self, content: &str, language: &str, config: &FormatConfig) -> Result<String> {
25        match language {
26            "json" => self.format_json(content, config),
27            "typescript" | "javascript" => {
28                // For now, return content as-is
29                // In a full implementation, we'd integrate with prettier or biome
30                Ok(content.to_string())
31            }
32            "rust" => {
33                // For now, return content as-is
34                // In a full implementation, we'd integrate with rustfmt
35                Ok(content.to_string())
36            }
37            _ => Ok(content.to_string()),
38        }
39    }
40
41    /// Format JSON content
42    #[allow(clippy::unused_self)] // Will use self for formatting state in future
43    fn format_json(&self, content: &str, config: &FormatConfig) -> Result<String> {
44        let value: serde_json::Value = serde_json::from_str(content)?;
45
46        let indent_size = config.indent_size.unwrap_or(2);
47        let indent_char = if config.indent == "tab" { '\t' } else { ' ' };
48
49        let formatted = if indent_char == '\t' {
50            serde_json::to_string_pretty(&value)?
51        } else {
52            let mut buf = Vec::new();
53            let indent = vec![b' '; indent_size];
54            let formatter = serde_json::ser::PrettyFormatter::with_indent(indent.as_slice());
55            let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
56            serde::Serialize::serialize(&value, &mut ser)
57                .map_err(|e| CodegenError::Formatting(e.to_string()))?;
58            String::from_utf8(buf).map_err(|e| CodegenError::Formatting(e.to_string()))?
59        };
60
61        Ok(formatted)
62    }
63}
64
65impl Default for Formatter {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_formatter_new() {
77        let fmt = Formatter::new();
78        // Just verify it can be created
79        let _debug = format!("{fmt:?}");
80    }
81
82    #[test]
83    fn test_formatter_default() {
84        let fmt = Formatter;
85        // Default should be equivalent to new()
86        let config = FormatConfig::default();
87        let result = fmt.format("test", "unknown", &config);
88        assert!(result.is_ok());
89    }
90
91    #[test]
92    fn test_format_json() {
93        let fmt = Formatter::new();
94        let input = r#"{"name":"test","value":123}"#;
95        let config = FormatConfig {
96            indent: "space".to_string(),
97            indent_size: Some(2),
98            ..Default::default()
99        };
100
101        let result = fmt.format(input, "json", &config);
102        assert!(result.is_ok());
103
104        let output = result.unwrap();
105        assert!(output.contains("  ")); // Should have 2-space indentation
106        assert!(output.contains("\"name\": \"test\""));
107    }
108
109    #[test]
110    fn test_format_json_with_tabs() {
111        let fmt = Formatter::new();
112        let input = r#"{"key":"value"}"#;
113        let config = FormatConfig {
114            indent: "tab".to_string(),
115            indent_size: None,
116            ..Default::default()
117        };
118
119        let result = fmt.format(input, "json", &config);
120        assert!(result.is_ok());
121        // Tab formatting uses serde_json default pretty print
122        let output = result.unwrap();
123        assert!(output.contains("\"key\":"));
124    }
125
126    #[test]
127    fn test_format_json_with_custom_indent_size() {
128        let fmt = Formatter::new();
129        let input = r#"{"a":"b"}"#;
130        let config = FormatConfig {
131            indent: "space".to_string(),
132            indent_size: Some(4),
133            ..Default::default()
134        };
135
136        let result = fmt.format(input, "json", &config);
137        assert!(result.is_ok());
138        let output = result.unwrap();
139        // Should have 4-space indentation
140        assert!(output.contains("    \"a\""));
141    }
142
143    #[test]
144    fn test_format_json_invalid() {
145        let fmt = Formatter::new();
146        let input = "{ not valid json }";
147        let config = FormatConfig::default();
148
149        let result = fmt.format(input, "json", &config);
150        assert!(result.is_err());
151    }
152
153    #[test]
154    fn test_format_typescript_passthrough() {
155        let fmt = Formatter::new();
156        let input = "const x = 1;";
157        let config = FormatConfig::default();
158
159        let result = fmt.format(input, "typescript", &config);
160        assert!(result.is_ok());
161        assert_eq!(result.unwrap(), input);
162    }
163
164    #[test]
165    fn test_format_javascript_passthrough() {
166        let fmt = Formatter::new();
167        let input = "function foo() { return 42; }";
168        let config = FormatConfig::default();
169
170        let result = fmt.format(input, "javascript", &config);
171        assert!(result.is_ok());
172        assert_eq!(result.unwrap(), input);
173    }
174
175    #[test]
176    fn test_format_rust_passthrough() {
177        let fmt = Formatter::new();
178        let input = "fn main() { println!(\"hello\"); }";
179        let config = FormatConfig::default();
180
181        let result = fmt.format(input, "rust", &config);
182        assert!(result.is_ok());
183        assert_eq!(result.unwrap(), input);
184    }
185
186    #[test]
187    fn test_format_unknown_language() {
188        let formatter = Formatter::new();
189        let input = "some content";
190        let config = FormatConfig::default();
191
192        let result = formatter.format(input, "unknown", &config);
193        assert!(result.is_ok());
194        assert_eq!(result.unwrap(), input);
195    }
196
197    #[test]
198    fn test_format_json_nested_structure() {
199        let fmt = Formatter::new();
200        let input = r#"{"outer":{"inner":{"deep":"value"}}}"#;
201        let config = FormatConfig {
202            indent: "space".to_string(),
203            indent_size: Some(2),
204            ..Default::default()
205        };
206
207        let result = fmt.format(input, "json", &config);
208        assert!(result.is_ok());
209        let output = result.unwrap();
210        assert!(output.contains("outer"));
211        assert!(output.contains("inner"));
212        assert!(output.contains("deep"));
213    }
214
215    #[test]
216    fn test_format_json_array() {
217        let fmt = Formatter::new();
218        let input = r"[1,2,3]";
219        let config = FormatConfig::default();
220
221        let result = fmt.format(input, "json", &config);
222        assert!(result.is_ok());
223        let output = result.unwrap();
224        assert!(output.contains('1'));
225        assert!(output.contains('2'));
226        assert!(output.contains('3'));
227    }
228}