Skip to content

Commit f28c54c

Browse files
committed
Auto merge of #6744 - matthiaskrgr:lintcheck, r=flip1995
more lintcheck updates * do some refactoring and renaming here and there * add comments to functions * fix bug where git repos would not get checked out to the proper commits (cmd was not actually run in repo directory 😅 ) * print warnings if we can't clone or check out a git repo * filter out noise from cargo-metadata errors and lint messages that contained absolute file paths (these would change with every pinned-nightly bump, polluting the logs) changelog: more lintcheck refactoring and fixes for git crates
2 parents 87c682a + a95c250 commit f28c54c

File tree

2 files changed

+107
-111
lines changed

2 files changed

+107
-111
lines changed

clippy_dev/src/lintcheck.rs

+82-40
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ use clap::ArgMatches;
1717
use serde::{Deserialize, Serialize};
1818
use serde_json::Value;
1919

20-
// use this to store the crates when interacting with the crates.toml file
20+
/// List of sources to check, loaded from a .toml file
2121
#[derive(Debug, Serialize, Deserialize)]
22-
struct CrateList {
22+
struct SourceList {
2323
crates: HashMap<String, TomlCrate>,
2424
}
2525

26-
// crate data we stored in the toml, can have multiple versions per crate
27-
// A single TomlCrate is laster mapped to several CrateSources in that case
26+
/// A crate source stored inside the .toml
27+
/// will be translated into on one of the `CrateSource` variants
2828
#[derive(Debug, Serialize, Deserialize)]
2929
struct TomlCrate {
3030
name: String,
@@ -34,17 +34,16 @@ struct TomlCrate {
3434
path: Option<String>,
3535
}
3636

37-
// represents an archive we download from crates.io, or a git repo, or a local repo
37+
/// Represents an archive we download from crates.io, or a git repo, or a local repo/folder
38+
/// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate`
3839
#[derive(Debug, Serialize, Deserialize, Eq, Hash, PartialEq)]
3940
enum CrateSource {
4041
CratesIo { name: String, version: String },
4142
Git { name: String, url: String, commit: String },
4243
Path { name: String, path: PathBuf },
4344
}
4445

45-
// represents the extracted sourcecode of a crate
46-
// we actually don't need to special-case git repos here because it does not matter for clippy, yay!
47-
// (clippy only needs a simple path)
46+
/// Represents the actual source code of a crate that we ran "cargo clippy" on
4847
#[derive(Debug)]
4948
struct Crate {
5049
version: String,
@@ -53,6 +52,7 @@ struct Crate {
5352
path: PathBuf,
5453
}
5554

55+
/// A single warning that clippy issued while checking a `Crate`
5656
#[derive(Debug)]
5757
struct ClippyWarning {
5858
crate_name: String,
@@ -62,7 +62,7 @@ struct ClippyWarning {
6262
column: String,
6363
linttype: String,
6464
message: String,
65-
ice: bool,
65+
is_ice: bool,
6666
}
6767

6868
impl std::fmt::Display for ClippyWarning {
@@ -76,6 +76,9 @@ impl std::fmt::Display for ClippyWarning {
7676
}
7777

7878
impl CrateSource {
79+
/// Makes the sources available on the disk for clippy to check.
80+
/// Clones a git repo and checks out the specified commit or downloads a crate from crates.io or
81+
/// copies a local folder
7982
fn download_and_extract(&self) -> Crate {
8083
match self {
8184
CrateSource::CratesIo { name, version } => {
@@ -122,19 +125,28 @@ impl CrateSource {
122125
// clone the repo if we have not done so
123126
if !repo_path.is_dir() {
124127
println!("Cloning {} and checking out {}", url, commit);
125-
Command::new("git")
128+
if !Command::new("git")
126129
.arg("clone")
127130
.arg(url)
128131
.arg(&repo_path)
129-
.output()
130-
.expect("Failed to clone git repo!");
132+
.status()
133+
.expect("Failed to clone git repo!")
134+
.success()
135+
{
136+
eprintln!("Failed to clone {} into {}", url, repo_path.display())
137+
}
131138
}
132139
// check out the commit/branch/whatever
133-
Command::new("git")
140+
if !Command::new("git")
134141
.arg("checkout")
135142
.arg(commit)
136-
.output()
137-
.expect("Failed to check out commit");
143+
.current_dir(&repo_path)
144+
.status()
145+
.expect("Failed to check out commit")
146+
.success()
147+
{
148+
eprintln!("Failed to checkout {} of repo at {}", commit, repo_path.display())
149+
}
138150

139151
Crate {
140152
version: commit.clone(),
@@ -178,6 +190,8 @@ impl CrateSource {
178190
}
179191

180192
impl Crate {
193+
/// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy
194+
/// issued
181195
fn run_clippy_lints(&self, cargo_clippy_path: &PathBuf) -> Vec<ClippyWarning> {
182196
println!("Linting {} {}...", &self.name, &self.version);
183197
let cargo_clippy_path = std::fs::canonicalize(cargo_clippy_path).unwrap();
@@ -211,21 +225,45 @@ impl Crate {
211225
let warnings: Vec<ClippyWarning> = output_lines
212226
.into_iter()
213227
// get all clippy warnings and ICEs
214-
.filter(|line| line.contains("clippy::") || line.contains("internal compiler error: "))
228+
.filter(|line| filter_clippy_warnings(&line))
215229
.map(|json_msg| parse_json_message(json_msg, &self))
216230
.collect();
217231
warnings
218232
}
219233
}
220234

235+
/// takes a single json-formatted clippy warnings and returns true (we are interested in that line)
236+
/// or false (we aren't)
237+
fn filter_clippy_warnings(line: &str) -> bool {
238+
// we want to collect ICEs because clippy might have crashed.
239+
// these are summarized later
240+
if line.contains("internal compiler error: ") {
241+
return true;
242+
}
243+
// in general, we want all clippy warnings
244+
// however due to some kind of bug, sometimes there are absolute paths
245+
// to libcore files inside the message
246+
// or we end up with cargo-metadata output (https://github.com/rust-lang/rust-clippy/issues/6508)
247+
248+
// filter out these message to avoid unnecessary noise in the logs
249+
if line.contains("clippy::")
250+
&& !(line.contains("could not read cargo metadata")
251+
|| (line.contains(".rustup") && line.contains("toolchains")))
252+
{
253+
return true;
254+
}
255+
false
256+
}
257+
258+
/// Builds clippy inside the repo to make sure we have a clippy executable we can use.
221259
fn build_clippy() {
222260
Command::new("cargo")
223261
.arg("build")
224262
.output()
225263
.expect("Failed to build clippy!");
226264
}
227265

228-
// get a list of CrateSources we want to check from a "lintcheck_crates.toml" file.
266+
/// Read a `toml` file and return a list of `CrateSources` that we want to check with clippy
229267
fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) {
230268
let toml_path = PathBuf::from(
231269
env::var("LINTCHECK_TOML").unwrap_or(toml_path.unwrap_or("clippy_dev/lintcheck_crates.toml").to_string()),
@@ -234,7 +272,7 @@ fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) {
234272
let toml_filename = toml_path.file_stem().unwrap().to_str().unwrap().to_string();
235273
let toml_content: String =
236274
std::fs::read_to_string(&toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display()));
237-
let crate_list: CrateList =
275+
let crate_list: SourceList =
238276
toml::from_str(&toml_content).unwrap_or_else(|e| panic!("Failed to parse {}: \n{}", toml_path.display(), e));
239277
// parse the hashmap of the toml file into a list of crates
240278
let tomlcrates: Vec<TomlCrate> = crate_list
@@ -288,7 +326,7 @@ fn read_crates(toml_path: Option<&str>) -> (String, Vec<CrateSource>) {
288326
(toml_filename, crate_sources)
289327
}
290328

291-
// extract interesting data from a json lint message
329+
/// Parse the json output of clippy and return a `ClippyWarning`
292330
fn parse_json_message(json_message: &str, krate: &Crate) -> ClippyWarning {
293331
let jmsg: Value = serde_json::from_str(&json_message).unwrap_or_else(|e| panic!("Failed to parse json:\n{:?}", e));
294332

@@ -309,11 +347,31 @@ fn parse_json_message(json_message: &str, krate: &Crate) -> ClippyWarning {
309347
.into(),
310348
linttype: jmsg["message"]["code"]["code"].to_string().trim_matches('"').into(),
311349
message: jmsg["message"]["message"].to_string().trim_matches('"').into(),
312-
ice: json_message.contains("internal compiler error: "),
350+
is_ice: json_message.contains("internal compiler error: "),
313351
}
314352
}
315353

316-
// the main fn
354+
/// Generate a short list of occuring lints-types and their count
355+
fn gather_stats(clippy_warnings: &[ClippyWarning]) -> String {
356+
// count lint type occurrences
357+
let mut counter: HashMap<&String, usize> = HashMap::new();
358+
clippy_warnings
359+
.iter()
360+
.for_each(|wrn| *counter.entry(&wrn.linttype).or_insert(0) += 1);
361+
362+
// collect into a tupled list for sorting
363+
let mut stats: Vec<(&&String, &usize)> = counter.iter().map(|(lint, count)| (lint, count)).collect();
364+
// sort by "000{count} {clippy::lintname}"
365+
// to not have a lint with 200 and 2 warnings take the same spot
366+
stats.sort_by_key(|(lint, count)| format!("{:0>4}, {}", count, lint));
367+
368+
stats
369+
.iter()
370+
.map(|(lint, count)| format!("{} {}\n", lint, count))
371+
.collect::<String>()
372+
}
373+
374+
/// lintchecks `main()` function
317375
pub fn run(clap_config: &ArgMatches) {
318376
let cargo_clippy_path: PathBuf = PathBuf::from("target/debug/cargo-clippy");
319377

@@ -374,32 +432,16 @@ pub fn run(clap_config: &ArgMatches) {
374432
.collect()
375433
};
376434

377-
// generate some stats:
435+
// generate some stats
436+
let stats_formatted = gather_stats(&clippy_warnings);
378437

379438
// grab crashes/ICEs, save the crate name and the ice message
380439
let ices: Vec<(&String, &String)> = clippy_warnings
381440
.iter()
382-
.filter(|warning| warning.ice)
441+
.filter(|warning| warning.is_ice)
383442
.map(|w| (&w.crate_name, &w.message))
384443
.collect();
385444

386-
// count lint type occurrences
387-
let mut counter: HashMap<&String, usize> = HashMap::new();
388-
clippy_warnings
389-
.iter()
390-
.for_each(|wrn| *counter.entry(&wrn.linttype).or_insert(0) += 1);
391-
392-
// collect into a tupled list for sorting
393-
let mut stats: Vec<(&&String, &usize)> = counter.iter().map(|(lint, count)| (lint, count)).collect();
394-
// sort by "000{count} {clippy::lintname}"
395-
// to not have a lint with 200 and 2 warnings take the same spot
396-
stats.sort_by_key(|(lint, count)| format!("{:0>4}, {}", count, lint));
397-
398-
let stats_formatted: String = stats
399-
.iter()
400-
.map(|(lint, count)| format!("{} {}\n", lint, count))
401-
.collect::<String>();
402-
403445
let mut all_msgs: Vec<String> = clippy_warnings.iter().map(|warning| warning.to_string()).collect();
404446
all_msgs.sort();
405447
all_msgs.push("\n\n\n\nStats\n\n".into());

0 commit comments

Comments
 (0)