Skip to main content

cuenv_codegen/
config.rs

1//! Formatter configuration generation
2//!
3//! This module handles automatic generation of formatter configuration files
4//! (biome.json, .prettierrc, rustfmt.toml, etc.) based on CUE schema format settings.
5
6use crate::codegen::FormatConfig;
7use serde_json::json;
8
9/// Generate biome.json configuration from format config
10#[must_use]
11pub fn generate_biome_config(format: &FormatConfig) -> serde_json::Value {
12    json!({
13        "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
14        "formatter": {
15            "enabled": true,
16            "indentStyle": format.indent,
17            "indentSize": format.indent_size.unwrap_or(2),
18            "lineWidth": format.line_width.unwrap_or(100)
19        },
20        "linter": {
21            "enabled": true
22        },
23        "javascript": {
24            "formatter": {
25                "quoteStyle": format.quotes.as_deref().unwrap_or("double"),
26                "trailingComma": format.trailing_comma.as_deref().unwrap_or("all"),
27                "semicolons": if format.semicolons.unwrap_or(true) { "always" } else { "asNeeded" }
28            }
29        }
30    })
31}
32
33/// Generate .prettierrc configuration from format config
34#[must_use]
35pub fn generate_prettier_config(format: &FormatConfig) -> serde_json::Value {
36    json!({
37        "useTabs": format.indent == "tab",
38        "tabWidth": format.indent_size.unwrap_or(2),
39        "printWidth": format.line_width.unwrap_or(100),
40        "singleQuote": format.quotes.as_deref() == Some("single"),
41        "trailingComma": format.trailing_comma.as_deref().unwrap_or("all"),
42        "semi": format.semicolons.unwrap_or(true)
43    })
44}
45
46/// Generate rustfmt.toml configuration from format config
47#[must_use]
48pub fn generate_rustfmt_config(format: &FormatConfig) -> String {
49    let edition = "2021"; // Default to 2021 edition
50    let max_width = format.line_width.unwrap_or(100);
51    let hard_tabs = format.indent == "tab";
52    let tab_spaces = format.indent_size.unwrap_or(4);
53
54    format!(
55        r#"edition = "{edition}"
56max_width = {max_width}
57hard_tabs = {hard_tabs}
58tab_spaces = {tab_spaces}
59use_small_heuristics = "Default"
60"#
61    )
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn test_generate_biome_config() {
70        let format = FormatConfig {
71            indent: "space".to_string(),
72            indent_size: Some(2),
73            line_width: Some(100),
74            quotes: Some("single".to_string()),
75            semicolons: Some(false),
76            trailing_comma: Some("es5".to_string()),
77        };
78
79        let config = generate_biome_config(&format);
80        assert_eq!(config["formatter"]["indentStyle"], "space");
81        assert_eq!(config["formatter"]["indentSize"], 2);
82        assert_eq!(config["javascript"]["formatter"]["quoteStyle"], "single");
83    }
84
85    #[test]
86    fn test_generate_biome_config_with_defaults() {
87        let format = FormatConfig::default();
88        let config = generate_biome_config(&format);
89
90        // Should use default values for optional fields
91        assert_eq!(config["formatter"]["indentSize"], 2);
92        assert_eq!(config["formatter"]["lineWidth"], 100);
93        assert_eq!(config["javascript"]["formatter"]["quoteStyle"], "double");
94        assert_eq!(config["javascript"]["formatter"]["trailingComma"], "all");
95        assert_eq!(config["javascript"]["formatter"]["semicolons"], "always");
96    }
97
98    #[test]
99    fn test_generate_biome_config_with_tabs() {
100        let format = FormatConfig {
101            indent: "tab".to_string(),
102            indent_size: Some(4),
103            line_width: Some(120),
104            quotes: Some("double".to_string()),
105            semicolons: Some(true),
106            trailing_comma: Some("none".to_string()),
107        };
108
109        let config = generate_biome_config(&format);
110        assert_eq!(config["formatter"]["indentStyle"], "tab");
111        assert_eq!(config["formatter"]["indentSize"], 4);
112        assert_eq!(config["formatter"]["lineWidth"], 120);
113        assert_eq!(config["javascript"]["formatter"]["semicolons"], "always");
114    }
115
116    #[test]
117    fn test_generate_biome_config_semicolons_as_needed() {
118        let format = FormatConfig {
119            indent: "space".to_string(),
120            semicolons: Some(false),
121            ..Default::default()
122        };
123
124        let config = generate_biome_config(&format);
125        assert_eq!(config["javascript"]["formatter"]["semicolons"], "asNeeded");
126    }
127
128    #[test]
129    fn test_generate_biome_config_linter_enabled() {
130        let format = FormatConfig::default();
131        let config = generate_biome_config(&format);
132        assert_eq!(config["linter"]["enabled"], true);
133        assert_eq!(config["formatter"]["enabled"], true);
134    }
135
136    #[test]
137    fn test_generate_biome_config_schema() {
138        let format = FormatConfig::default();
139        let config = generate_biome_config(&format);
140        assert_eq!(
141            config["$schema"],
142            "https://biomejs.dev/schemas/1.4.1/schema.json"
143        );
144    }
145
146    #[test]
147    fn test_generate_prettier_config() {
148        let format = FormatConfig {
149            indent: "tab".to_string(),
150            indent_size: Some(4),
151            line_width: Some(120),
152            quotes: Some("single".to_string()),
153            semicolons: Some(false),
154            trailing_comma: Some("none".to_string()),
155        };
156
157        let config = generate_prettier_config(&format);
158        assert_eq!(config["useTabs"], true);
159        assert_eq!(config["tabWidth"], 4);
160        assert_eq!(config["singleQuote"], true);
161        assert_eq!(config["semi"], false);
162    }
163
164    #[test]
165    fn test_generate_prettier_config_with_defaults() {
166        let format = FormatConfig::default();
167        let config = generate_prettier_config(&format);
168
169        assert_eq!(config["useTabs"], false); // "space" != "tab"
170        assert_eq!(config["tabWidth"], 2);
171        assert_eq!(config["printWidth"], 100);
172        assert_eq!(config["singleQuote"], false); // None != Some("single")
173        assert_eq!(config["trailingComma"], "all");
174        assert_eq!(config["semi"], true);
175    }
176
177    #[test]
178    fn test_generate_prettier_config_double_quotes() {
179        let format = FormatConfig {
180            indent: "space".to_string(),
181            quotes: Some("double".to_string()),
182            ..Default::default()
183        };
184
185        let config = generate_prettier_config(&format);
186        assert_eq!(config["singleQuote"], false);
187    }
188
189    #[test]
190    fn test_generate_prettier_config_spaces() {
191        let format = FormatConfig {
192            indent: "space".to_string(),
193            indent_size: Some(2),
194            ..Default::default()
195        };
196
197        let config = generate_prettier_config(&format);
198        assert_eq!(config["useTabs"], false);
199        assert_eq!(config["tabWidth"], 2);
200    }
201
202    #[test]
203    fn test_generate_rustfmt_config() {
204        let format = FormatConfig {
205            indent: "space".to_string(),
206            indent_size: Some(4),
207            line_width: Some(100),
208            ..Default::default()
209        };
210
211        let config = generate_rustfmt_config(&format);
212        assert!(config.contains("edition = \"2021\""));
213        assert!(config.contains("max_width = 100"));
214        assert!(config.contains("hard_tabs = false"));
215        assert!(config.contains("tab_spaces = 4"));
216    }
217
218    #[test]
219    fn test_generate_rustfmt_config_with_tabs() {
220        let format = FormatConfig {
221            indent: "tab".to_string(),
222            indent_size: Some(4),
223            line_width: Some(80),
224            ..Default::default()
225        };
226
227        let config = generate_rustfmt_config(&format);
228        assert!(config.contains("hard_tabs = true"));
229        assert!(config.contains("max_width = 80"));
230    }
231
232    #[test]
233    fn test_generate_rustfmt_config_with_defaults() {
234        let format = FormatConfig::default();
235        let config = generate_rustfmt_config(&format);
236
237        assert!(config.contains("edition = \"2021\""));
238        assert!(config.contains("max_width = 100"));
239        assert!(config.contains("hard_tabs = false"));
240        assert!(config.contains("tab_spaces = 2")); // Default indent_size is 2
241        assert!(config.contains("use_small_heuristics = \"Default\""));
242    }
243
244    #[test]
245    fn test_generate_rustfmt_config_no_indent_size() {
246        let format = FormatConfig {
247            indent: "space".to_string(),
248            indent_size: None,
249            line_width: None,
250            ..Default::default()
251        };
252
253        let config = generate_rustfmt_config(&format);
254        // Should use defaults
255        assert!(config.contains("tab_spaces = 4")); // Default for rustfmt is 4
256        assert!(config.contains("max_width = 100"));
257    }
258
259    #[test]
260    fn test_generate_rustfmt_config_format() {
261        let format = FormatConfig {
262            indent: "space".to_string(),
263            indent_size: Some(4),
264            line_width: Some(120),
265            ..Default::default()
266        };
267
268        let config = generate_rustfmt_config(&format);
269        // Verify the config is valid TOML-like format
270        let lines: Vec<&str> = config.lines().collect();
271        assert!(lines.iter().any(|l| l.starts_with("edition = ")));
272        assert!(lines.iter().any(|l| l.starts_with("max_width = ")));
273        assert!(lines.iter().any(|l| l.starts_with("hard_tabs = ")));
274        assert!(lines.iter().any(|l| l.starts_with("tab_spaces = ")));
275        assert!(
276            lines
277                .iter()
278                .any(|l| l.starts_with("use_small_heuristics = "))
279        );
280    }
281}