]> git.lizzy.rs Git - rust.git/blob - clippy_dev/src/fmt.rs
Auto merge of #5109 - phansch:ciao_util_dev, r=flip1995
[rust.git] / clippy_dev / src / fmt.rs
1 use clippy_dev::clippy_project_root;
2 use shell_escape::escape;
3 use std::ffi::OsStr;
4 use std::io;
5 use std::path::Path;
6 use std::process::{self, Command};
7 use walkdir::WalkDir;
8
9 #[derive(Debug)]
10 pub enum CliError {
11     CommandFailed(String),
12     IoError(io::Error),
13     RustfmtNotInstalled,
14     WalkDirError(walkdir::Error),
15 }
16
17 impl From<io::Error> for CliError {
18     fn from(error: io::Error) -> Self {
19         Self::IoError(error)
20     }
21 }
22
23 impl From<walkdir::Error> for CliError {
24     fn from(error: walkdir::Error) -> Self {
25         Self::WalkDirError(error)
26     }
27 }
28
29 struct FmtContext {
30     check: bool,
31     verbose: bool,
32 }
33
34 pub fn run(check: bool, verbose: bool) {
35     fn try_run(context: &FmtContext) -> Result<bool, CliError> {
36         let mut success = true;
37
38         let project_root = clippy_project_root();
39
40         rustfmt_test(context)?;
41
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"))?;
45
46         for entry in WalkDir::new(project_root.join("tests")) {
47             let entry = entry?;
48             let path = entry.path();
49
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"
54             {
55                 continue;
56             }
57
58             success &= rustfmt(context, &path)?;
59         }
60
61         Ok(success)
62     }
63
64     fn output_err(err: CliError) {
65         match err {
66             CliError::CommandFailed(command) => {
67                 eprintln!("error: A command failed! `{}`", command);
68             },
69             CliError::IoError(err) => {
70                 eprintln!("error: {}", err);
71             },
72             CliError::RustfmtNotInstalled => {
73                 eprintln!("error: rustfmt nightly is not installed.");
74             },
75             CliError::WalkDirError(err) => {
76                 eprintln!("error: {}", err);
77             },
78         }
79     }
80
81     let context = FmtContext { check, verbose };
82     let result = try_run(&context);
83     let code = match result {
84         Ok(true) => 0,
85         Ok(false) => {
86             eprintln!();
87             eprintln!("Formatting check failed.");
88             eprintln!("Run `cargo dev fmt` to update formatting.");
89             1
90         },
91         Err(err) => {
92             output_err(err);
93             1
94         },
95     };
96     process::exit(code);
97 }
98
99 fn format_command(program: impl AsRef<OsStr>, dir: impl AsRef<Path>, args: &[impl AsRef<OsStr>]) -> String {
100     let arg_display: Vec<_> = args.iter().map(|a| escape(a.as_ref().to_string_lossy())).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_test(context: &FmtContext) -> Result<(), CliError> {
143     let program = "rustfmt";
144     let dir = std::env::current_dir()?;
145     let args = &["+nightly", "--version"];
146
147     if context.verbose {
148         println!("{}", format_command(&program, &dir, args));
149     }
150
151     let output = Command::new(&program).current_dir(&dir).args(args.iter()).output()?;
152
153     if output.status.success() {
154         Ok(())
155     } else if std::str::from_utf8(&output.stderr)
156         .unwrap_or("")
157         .starts_with("error: 'rustfmt' is not installed")
158     {
159         Err(CliError::RustfmtNotInstalled)
160     } else {
161         Err(CliError::CommandFailed(format_command(&program, &dir, args)))
162     }
163 }
164
165 fn rustfmt(context: &FmtContext, path: &Path) -> Result<bool, CliError> {
166     let mut args = vec!["+nightly".as_ref(), path.as_os_str()];
167     if context.check {
168         args.push("--check".as_ref());
169     }
170     let success = exec(context, "rustfmt", std::env::current_dir()?, &args)?;
171     if !success {
172         eprintln!("rustfmt failed on {}", path.display());
173     }
174     Ok(success)
175 }