use std::path::Path;
use std::process::Command;
pub fn detect_prettier_config(path: &Path) -> Option<String> {
let mut current = Some(path.to_path_buf());
while let Some(p) = current {
for config_file in &[
".prettierrc",
".prettierrc.json",
".prettierrc.js",
".prettierrc.cjs",
"prettier.config.js",
"prettier.config.cjs",
] {
let config_path = p.join(config_file);
if config_path.exists() {
return Some(config_path.to_string_lossy().to_string());
}
}
if let Ok(content) = std::fs::read_to_string(p.join("package.json"))
&& content.contains("prettier")
{
return Some("package.json".to_string());
}
current = p.parent().map(|p| p.to_path_buf());
}
None
}
pub fn prettier_available() -> bool {
Command::new("prettier")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
pub fn format_with_prettier(source: &str, path: &Path) -> Result<String, String> {
if !prettier_available() {
return Err("Prettier not available".to_string());
}
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("js");
let parser = match ext {
"js" | "jsx" | "mjs" => "babel",
"ts" | "tsx" => "typescript",
"json" => "json",
"md" => "markdown",
"css" | "scss" | "less" => "css",
"html" | "vue" | "svelte" => "html",
_ => "babel",
};
let mut cmd = Command::new("prettier");
cmd.arg("--parser").arg(parser).arg("--write").arg("-");
if detect_prettier_config(path).is_some() {
cmd.arg("--config").arg("auto");
}
let mut child = cmd
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.map_err(|e| e.to_string())?;
use std::io::Write;
if let Some(mut stdin) = child.stdin.take() {
stdin
.write_all(source.as_bytes())
.map_err(|e| e.to_string())?;
}
let output = child.wait_with_output().map_err(|e| e.to_string())?;
if output.status.success() {
String::from_utf8(output.stdout).map_err(|e| e.to_string())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}
pub fn format_with_fallback(source: &str) -> String {
source.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prettier_available() {
let available = prettier_available();
assert!(available || !available);
}
#[test]
fn test_detect_prettier_config_none() {
let config = detect_prettier_config(Path::new("/tmp/nonexistent/file.js"));
assert!(config.is_none() || config.is_some());
}
#[test]
fn test_format_with_fallback() {
let result = format_with_fallback("const x = 1;");
assert_eq!(result, "const x = 1;");
}
}