]> git.lizzy.rs Git - rust.git/blob - clippy_dev/src/fmt.rs
Auto merge of #4313 - Manishearth:owl, r=yaahallo
[rust.git] / clippy_dev / src / fmt.rs
1 use shell_escape::escape;
2 use std::ffi::OsStr;
3 use std::io;
4 use std::path::{Path, PathBuf};
5 use std::process::{self, Command};
6 use walkdir::WalkDir;
7
8 #[derive(Debug)]
9 pub enum CliError {
10     CommandFailed(String),
11     IoError(io::Error),
12     ProjectRootNotFound,
13     WalkDirError(walkdir::Error),
14 }
15
16 impl From<io::Error> for CliError {
17     fn from(error: io::Error) -> Self {
18         Self::IoError(error)
19     }
20 }
21
22 impl From<walkdir::Error> for CliError {
23     fn from(error: walkdir::Error) -> Self {
24         Self::WalkDirError(error)
25     }
26 }
27
28 struct FmtContext {
29     check: bool,
30     verbose: bool,
31 }
32
33 pub fn run(check: bool, verbose: bool) {
34     fn try_run(context: &FmtContext) -> Result<bool, CliError> {
35         let mut success = true;
36
37         let project_root = project_root()?;
38
39         success &= cargo_fmt(context, project_root.as_path())?;
40         success &= cargo_fmt(context, &project_root.join("clippy_dev"))?;
41         success &= cargo_fmt(context, &project_root.join("rustc_tools_util"))?;
42
43         for entry in WalkDir::new(project_root.join("tests")) {
44             let entry = entry?;
45             let path = entry.path();
46
47             if path.extension() != Some("rs".as_ref())
48                 || entry.file_name() == "ice-3891.rs"
49                 // Avoid rustfmt bug rust-lang/rustfmt#1873
50                 || cfg!(windows) && entry.file_name() == "implicit_hasher.rs"
51             {
52                 continue;
53             }
54
55             success &= rustfmt(context, &path)?;
56         }
57
58         Ok(success)
59     }
60
61     fn output_err(err: CliError) {
62         match err {
63             CliError::CommandFailed(command) => {
64                 eprintln!("error: A command failed! `{}`", command);
65             },
66             CliError::IoError(err) => {
67                 eprintln!("error: {}", err);
68             },
69             CliError::ProjectRootNotFound => {
70                 eprintln!("error: Can't determine root of project. Please run inside a Clippy working dir.");
71             },
72             CliError::WalkDirError(err) => {
73                 eprintln!("error: {}", err);
74             },
75         }
76     }
77
78     let context = FmtContext { check, verbose };
79     let result = try_run(&context);
80     let code = match result {
81         Ok(true) => 0,
82         Ok(false) => {
83             eprintln!();
84             eprintln!("Formatting check failed.");
85             eprintln!("Run `./util/dev fmt` to update formatting.");
86             1
87         },
88         Err(err) => {
89             output_err(err);
90             1
91         },
92     };
93     process::exit(code);
94 }
95
96 fn format_command(program: impl AsRef<OsStr>, dir: impl AsRef<Path>, args: &[impl AsRef<OsStr>]) -> String {
97     let arg_display: Vec<_> = args
98         .iter()
99         .map(|a| escape(a.as_ref().to_string_lossy()).to_owned())
100         .collect();
101
102     format!(
103         "cd {} && {} {}",
104         escape(dir.as_ref().to_string_lossy()),
105         escape(program.as_ref().to_string_lossy()),
106         arg_display.join(" ")
107     )
108 }
109
110 fn exec(
111     context: &FmtContext,
112     program: impl AsRef<OsStr>,
113     dir: impl AsRef<Path>,
114     args: &[impl AsRef<OsStr>],
115 ) -> Result<bool, CliError> {
116     if context.verbose {
117         println!("{}", format_command(&program, &dir, args));
118     }
119
120     let mut child = Command::new(&program).current_dir(&dir).args(args.iter()).spawn()?;
121     let code = child.wait()?;
122     let success = code.success();
123
124     if !context.check && !success {
125         return Err(CliError::CommandFailed(format_command(&program, &dir, args)));
126     }
127
128     Ok(success)
129 }
130
131 fn cargo_fmt(context: &FmtContext, path: &Path) -> Result<bool, CliError> {
132     let mut args = vec!["+nightly", "fmt", "--all"];
133     if context.check {
134         args.push("--");
135         args.push("--check");
136     }
137     let success = exec(context, "cargo", path, &args)?;
138
139     Ok(success)
140 }
141
142 fn rustfmt(context: &FmtContext, path: &Path) -> Result<bool, CliError> {
143     let mut args = vec!["+nightly".as_ref(), path.as_os_str()];
144     if context.check {
145         args.push("--check".as_ref());
146     }
147     let success = exec(context, "rustfmt", std::env::current_dir()?, &args)?;
148     if !success {
149         eprintln!("rustfmt failed on {}", path.display());
150     }
151     Ok(success)
152 }
153
154 fn project_root() -> Result<PathBuf, CliError> {
155     let current_dir = std::env::current_dir()?;
156     for path in current_dir.ancestors() {
157         let result = std::fs::read_to_string(path.join("Cargo.toml"));
158         if let Err(err) = &result {
159             if err.kind() == io::ErrorKind::NotFound {
160                 continue;
161             }
162         }
163
164         let content = result?;
165         if content.contains("[package]\nname = \"clippy\"") {
166             return Ok(path.to_path_buf());
167         }
168     }
169
170     Err(CliError::ProjectRootNotFound)
171 }