1 use shell_escape::escape;
4 use std::path::{Path, PathBuf};
5 use std::process::{self, Command};
10 CommandFailed(String),
14 WalkDirError(walkdir::Error),
17 impl From<io::Error> for CliError {
18 fn from(error: io::Error) -> Self {
23 impl From<walkdir::Error> for CliError {
24 fn from(error: walkdir::Error) -> Self {
25 Self::WalkDirError(error)
34 pub fn run(check: bool, verbose: bool) {
35 fn try_run(context: &FmtContext) -> Result<bool, CliError> {
36 let mut success = true;
38 let project_root = project_root()?;
40 rustfmt_test(context)?;
42 success &= cargo_fmt(context, project_root.as_path())?;
43 success &= cargo_fmt(context, &project_root.join("clippy_dev"))?;
44 success &= cargo_fmt(context, &project_root.join("rustc_tools_util"))?;
46 for entry in WalkDir::new(project_root.join("tests")) {
48 let path = entry.path();
50 if path.extension() != Some("rs".as_ref())
51 || entry.file_name() == "ice-3891.rs"
52 // Avoid rustfmt bug rust-lang/rustfmt#1873
53 || cfg!(windows) && entry.file_name() == "implicit_hasher.rs"
58 success &= rustfmt(context, &path)?;
64 fn output_err(err: CliError) {
66 CliError::CommandFailed(command) => {
67 eprintln!("error: A command failed! `{}`", command);
69 CliError::IoError(err) => {
70 eprintln!("error: {}", err);
72 CliError::ProjectRootNotFound => {
73 eprintln!("error: Can't determine root of project. Please run inside a Clippy working dir.");
75 CliError::RustfmtNotInstalled => {
76 eprintln!("error: rustfmt nightly is not installed.");
78 CliError::WalkDirError(err) => {
79 eprintln!("error: {}", err);
84 let context = FmtContext { check, verbose };
85 let result = try_run(&context);
86 let code = match result {
90 eprintln!("Formatting check failed.");
91 eprintln!("Run `./util/dev fmt` to update formatting.");
102 fn format_command(program: impl AsRef<OsStr>, dir: impl AsRef<Path>, args: &[impl AsRef<OsStr>]) -> String {
103 let arg_display: Vec<_> = args
105 .map(|a| escape(a.as_ref().to_string_lossy()).to_owned())
110 escape(dir.as_ref().to_string_lossy()),
111 escape(program.as_ref().to_string_lossy()),
112 arg_display.join(" ")
117 context: &FmtContext,
118 program: impl AsRef<OsStr>,
119 dir: impl AsRef<Path>,
120 args: &[impl AsRef<OsStr>],
121 ) -> Result<bool, CliError> {
123 println!("{}", format_command(&program, &dir, args));
126 let mut child = Command::new(&program).current_dir(&dir).args(args.iter()).spawn()?;
127 let code = child.wait()?;
128 let success = code.success();
130 if !context.check && !success {
131 return Err(CliError::CommandFailed(format_command(&program, &dir, args)));
137 fn cargo_fmt(context: &FmtContext, path: &Path) -> Result<bool, CliError> {
138 let mut args = vec!["+nightly", "fmt", "--all"];
141 args.push("--check");
143 let success = exec(context, &bin_path("cargo"), path, &args)?;
148 fn rustfmt_test(context: &FmtContext) -> Result<(), CliError> {
149 let program = bin_path("rustfmt");
150 let dir = std::env::current_dir()?;
151 let args = &["+nightly", "--version"];
154 println!("{}", format_command(&program, &dir, args));
157 let output = Command::new(&program).current_dir(&dir).args(args.iter()).output()?;
159 if output.status.success() {
161 } else if std::str::from_utf8(&output.stderr)
163 .starts_with("error: 'rustfmt' is not installed")
165 Err(CliError::RustfmtNotInstalled)
167 Err(CliError::CommandFailed(format_command(&program, &dir, args)))
171 fn rustfmt(context: &FmtContext, path: &Path) -> Result<bool, CliError> {
172 let mut args = vec!["+nightly".as_ref(), path.as_os_str()];
174 args.push("--check".as_ref());
176 let success = exec(context, &bin_path("rustfmt"), std::env::current_dir()?, &args)?;
178 eprintln!("rustfmt failed on {}", path.display());
183 fn project_root() -> Result<PathBuf, CliError> {
184 let current_dir = std::env::current_dir()?;
185 for path in current_dir.ancestors() {
186 let result = std::fs::read_to_string(path.join("Cargo.toml"));
187 if let Err(err) = &result {
188 if err.kind() == io::ErrorKind::NotFound {
193 let content = result?;
194 if content.contains("[package]\nname = \"clippy\"") {
195 return Ok(path.to_path_buf());
199 Err(CliError::ProjectRootNotFound)
202 // Workaround for https://github.com/rust-lang/cargo/issues/7475.
203 // FIXME: replace `&bin_path("command")` with `"command"` once the issue is fixed
204 fn bin_path(bin: &str) -> String {
205 let mut p = home::cargo_home().unwrap();
208 p.display().to_string()