Files
rtk-ai__rtk/src/prettier_cmd.rs
patrick szymkowiak 85b0b3eb0b fix: prettier false positive when not installed (#221) (#359)
* fix: prettier reports "All OK" when not installed (#221)

Empty or failed prettier output was incorrectly treated as "all files
formatted". Now detects empty output and non-zero exit code, shows the
actual error message instead of a false positive.

* test: add smoke tests for rewrite, verify, proxy, discover, diff, wc, smart, docker, json edge cases

Covers bug fixes #196, #344, #345, #346, #347 and previously untested
commands. Adds assert_fails helper. 118 assertions total (was 69).

* chore: update benchmark.sh with missing commands and fix paths

- Add cargo (build/test/clippy/check), diff, smart, wc, curl, wget sections
- Fix Python commands: use dedicated rtk ruff/pytest instead of rtk test
- Fix Go commands: use dedicated rtk go/golangci-lint, add go build/vet
- Make BENCH_DIR absolute so debug files work from temp fixture dirs
- Fallback to installed rtk if target/release/rtk not found
2026-03-06 09:40:24 +01:00

222 lines
6.9 KiB
Rust

use crate::tracking;
use crate::utils::package_manager_exec;
use anyhow::{Context, Result};
pub fn run(args: &[String], verbose: u8) -> Result<()> {
let timer = tracking::TimedExecution::start();
let mut cmd = package_manager_exec("prettier");
// Add user arguments
for arg in args {
cmd.arg(arg);
}
if verbose > 0 {
eprintln!("Running: prettier {}", args.join(" "));
}
let output = cmd
.output()
.context("Failed to run prettier (try: npm install -g prettier)")?;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let raw = format!("{}\n{}", stdout, stderr);
// #221: If prettier is not installed or produced no meaningful output,
// show stderr as-is instead of a misleading "All files formatted" message.
let has_output = stdout.lines().any(|l| !l.trim().is_empty());
if !has_output && !output.status.success() {
let msg = stderr.trim();
if msg.is_empty() {
eprintln!("Error: prettier not found or produced no output");
} else {
eprintln!("{}", msg);
}
timer.track(
&format!("prettier {}", args.join(" ")),
&format!("rtk prettier {}", args.join(" ")),
&raw,
&raw,
);
std::process::exit(output.status.code().unwrap_or(1));
}
let filtered = filter_prettier_output(&raw);
println!("{}", filtered);
timer.track(
&format!("prettier {}", args.join(" ")),
&format!("rtk prettier {}", args.join(" ")),
&raw,
&filtered,
);
// Preserve exit code for CI/CD
if !output.status.success() {
std::process::exit(output.status.code().unwrap_or(1));
}
Ok(())
}
/// Filter Prettier output - show only files that need formatting
pub fn filter_prettier_output(output: &str) -> String {
// #221: empty or whitespace-only output means prettier didn't run
if output.trim().is_empty() {
return "Error: prettier produced no output".to_string();
}
let mut files_to_format: Vec<String> = Vec::new();
let mut files_checked = 0;
let mut is_check_mode = true;
for line in output.lines() {
let trimmed = line.trim();
// Detect check mode vs write mode
if trimmed.contains("Checking formatting") {
is_check_mode = true;
}
// Count files that need formatting (check mode)
if !trimmed.is_empty()
&& !trimmed.starts_with("Checking")
&& !trimmed.starts_with("All matched")
&& !trimmed.starts_with("Code style")
&& !trimmed.contains("[warn]")
&& !trimmed.contains("[error]")
&& (trimmed.ends_with(".ts")
|| trimmed.ends_with(".tsx")
|| trimmed.ends_with(".js")
|| trimmed.ends_with(".jsx")
|| trimmed.ends_with(".json")
|| trimmed.ends_with(".md")
|| trimmed.ends_with(".css")
|| trimmed.ends_with(".scss"))
{
files_to_format.push(trimmed.to_string());
}
// Count total files checked
if trimmed.contains("All matched files use Prettier") {
if let Some(count_str) = trimmed.split_whitespace().next() {
if let Ok(count) = count_str.parse::<usize>() {
files_checked = count;
}
}
}
}
// Check if all files are formatted
if files_to_format.is_empty() && output.contains("All matched files use Prettier") {
return "✓ Prettier: All files formatted correctly".to_string();
}
// Check if files were written (write mode)
if output.contains("modified") || output.contains("formatted") {
is_check_mode = false;
}
let mut result = String::new();
if is_check_mode {
// Check mode: show files that need formatting
if files_to_format.is_empty() {
result.push_str("✓ Prettier: All files formatted correctly\n");
} else {
result.push_str(&format!(
"Prettier: {} files need formatting\n",
files_to_format.len()
));
result.push_str("═══════════════════════════════════════\n");
for (i, file) in files_to_format.iter().take(10).enumerate() {
result.push_str(&format!("{}. {}\n", i + 1, file));
}
if files_to_format.len() > 10 {
result.push_str(&format!(
"\n... +{} more files\n",
files_to_format.len() - 10
));
}
if files_checked > 0 {
result.push_str(&format!(
"\n✓ {} files already formatted\n",
files_checked - files_to_format.len()
));
}
}
} else {
// Write mode: show what was formatted
result.push_str(&format!(
"✓ Prettier: {} files formatted\n",
files_to_format.len()
));
}
result.trim().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_all_formatted() {
let output = r#"
Checking formatting...
All matched files use Prettier code style!
"#;
let result = filter_prettier_output(output);
assert!(result.contains("✓ Prettier"));
assert!(result.contains("All files formatted correctly"));
}
#[test]
fn test_filter_files_need_formatting() {
let output = r#"
Checking formatting...
src/components/ui/button.tsx
src/lib/auth/session.ts
src/pages/dashboard.tsx
Code style issues found in the above file(s). Forgot to run Prettier?
"#;
let result = filter_prettier_output(output);
assert!(result.contains("3 files need formatting"));
assert!(result.contains("button.tsx"));
assert!(result.contains("session.ts"));
}
#[test]
fn test_filter_many_files() {
let mut output = String::from("Checking formatting...\n");
for i in 0..15 {
output.push_str(&format!("src/file{}.ts\n", i));
}
let result = filter_prettier_output(&output);
assert!(result.contains("15 files need formatting"));
assert!(result.contains("... +5 more files"));
}
// --- #221: empty output should not say "All files formatted" ---
#[test]
fn test_filter_empty_output() {
let result = filter_prettier_output("");
assert!(result.contains("Error"));
assert!(!result.contains("All files formatted"));
}
#[test]
fn test_filter_whitespace_only_output() {
let result = filter_prettier_output(" \n\n ");
assert!(result.contains("Error"));
assert!(!result.contains("All files formatted"));
}
}