1use crate::error::Result;
2use std::path::Path;
3use std::process::Command;
4
5pub enum Formatter {
6 Prettier,
7 Biome,
8}
9
10pub struct FormatterManager;
11
12impl FormatterManager {
13 pub fn detect_formatter() -> Option<Formatter> {
14 Self::detect_formatter_from_dir(Path::new("."))
15 }
16
17 pub fn detect_formatter_from_dir(dir: &Path) -> Option<Formatter> {
18 if Self::has_prettier_in_dir(dir) {
20 return Some(Formatter::Prettier);
21 }
22
23 if Self::has_biome_in_dir(dir) {
25 return Some(Formatter::Biome);
26 }
27
28 None
29 }
30
31 fn has_prettier_in_dir(dir: &Path) -> bool {
32 let package_json = dir.join("package.json");
34 if package_json.exists() {
35 if let Ok(content) = std::fs::read_to_string(&package_json) {
36 if content.contains("prettier") {
37 return true;
38 }
39 }
40 }
41
42 dir.join(".prettierrc").exists()
44 || dir.join(".prettierrc.json").exists()
45 || dir.join(".prettierrc.js").exists()
46 || dir.join("prettier.config.js").exists()
47 }
48
49 fn has_biome_in_dir(dir: &Path) -> bool {
50 dir.join("biome.json").exists() || dir.join("biome.jsonc").exists()
52 }
53
54 pub fn format_files(files: &[std::path::PathBuf], formatter: Formatter) -> Result<()> {
55 match formatter {
56 Formatter::Prettier => Self::format_with_prettier(files),
57 Formatter::Biome => Self::format_with_biome(files),
58 }
59 }
60
61 fn format_with_prettier(files: &[std::path::PathBuf]) -> Result<()> {
62 let file_paths: Vec<String> = files
63 .iter()
64 .filter_map(|p| p.to_str().map(|s| s.to_string()))
65 .collect();
66
67 if file_paths.is_empty() {
68 return Ok(());
69 }
70
71 let output = if file_paths.len() > 50 {
73 Command::new("npx")
75 .arg("prettier")
76 .arg("--write")
77 .arg("src/**/*.ts")
78 .output()
79 } else {
80 Command::new("npx")
81 .arg("prettier")
82 .arg("--write")
83 .args(&file_paths)
84 .output()
85 };
86
87 match output {
88 Ok(output) => {
89 if !output.status.success() {
90 let stderr = String::from_utf8_lossy(&output.stderr);
91 let stdout = String::from_utf8_lossy(&output.stdout);
92 eprintln!("Warning: Prettier exited with error:");
93 eprintln!(" stderr: {}", stderr);
94 if !stdout.is_empty() {
95 eprintln!(" stdout: {}", stdout);
96 }
97 }
98 Ok(())
99 }
100 Err(e) => {
101 eprintln!("Warning: Failed to run prettier: {}", e);
103 Ok(())
104 }
105 }
106 }
107
108 fn format_with_biome(files: &[std::path::PathBuf]) -> Result<()> {
109 let file_paths: Vec<String> = files
110 .iter()
111 .filter_map(|p| p.to_str().map(|s| s.to_string()))
112 .collect();
113
114 if file_paths.is_empty() {
115 return Ok(());
116 }
117
118 let output = Command::new("npx")
119 .arg("@biomejs/biome")
120 .arg("format")
121 .arg("--write")
122 .args(&file_paths)
123 .output();
124
125 match output {
126 Ok(_) => Ok(()),
127 Err(e) => {
128 eprintln!("Warning: Failed to run biome: {}", e);
130 Ok(())
131 }
132 }
133 }
134}