]> git.proxmox.com Git - rustc.git/blobdiff - src/tools/clippy/lintcheck/src/main.rs
New upstream version 1.59.0+dfsg1
[rustc.git] / src / tools / clippy / lintcheck / src / main.rs
index 97d3794fb84f0a951de6acd2e8a0e3eced02d357..53e669254cfeafde12b584fec22c15c4e2cc168b 100644 (file)
@@ -12,7 +12,7 @@ use std::process::Command;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::{collections::HashMap, io::ErrorKind};
 use std::{
-    env, fmt,
+    env,
     fs::write,
     path::{Path, PathBuf},
     thread,
@@ -101,13 +101,28 @@ struct ClippyWarning {
     is_ice: bool,
 }
 
-impl std::fmt::Display for ClippyWarning {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        writeln!(
-            f,
-            r#"target/lintcheck/sources/{}-{}/{}:{}:{} {} "{}""#,
-            &self.crate_name, &self.crate_version, &self.file, &self.line, &self.column, &self.linttype, &self.message
-        )
+#[allow(unused)]
+impl ClippyWarning {
+    fn to_output(&self, markdown: bool) -> String {
+        let file = format!("{}-{}/{}", &self.crate_name, &self.crate_version, &self.file);
+        let file_with_pos = format!("{}:{}:{}", &file, &self.line, &self.column);
+        if markdown {
+            let lint = format!("`{}`", self.linttype);
+
+            let mut output = String::from("| ");
+            output.push_str(&format!(
+                "[`{}`](../target/lintcheck/sources/{}#L{})",
+                file_with_pos, file, self.line
+            ));
+            output.push_str(&format!(r#" | {:<50} | "{}" |"#, lint, self.message));
+            output.push('\n');
+            output
+        } else {
+            format!(
+                "target/lintcheck/sources/{} {} \"{}\"\n",
+                file_with_pos, self.linttype, self.message
+            )
+        }
     }
 }
 
@@ -264,6 +279,7 @@ impl Crate {
         thread_limit: usize,
         total_crates_to_lint: usize,
         fix: bool,
+        lint_filter: &Vec<String>,
     ) -> Vec<ClippyWarning> {
         // advance the atomic index by one
         let index = target_dir_index.fetch_add(1, Ordering::SeqCst);
@@ -288,9 +304,9 @@ impl Crate {
         let shared_target_dir = clippy_project_root().join("target/lintcheck/shared_target_dir");
 
         let mut args = if fix {
-            vec!["--fix", "--allow-no-vcs", "--", "--cap-lints=warn"]
+            vec!["--fix", "--allow-no-vcs", "--"]
         } else {
-            vec!["--", "--message-format=json", "--", "--cap-lints=warn"]
+            vec!["--", "--message-format=json", "--"]
         };
 
         if let Some(options) = &self.options {
@@ -301,6 +317,13 @@ impl Crate {
             args.extend(&["-Wclippy::pedantic", "-Wclippy::cargo"])
         }
 
+        if lint_filter.is_empty() {
+            args.push("--cap-lints=warn");
+        } else {
+            args.push("--cap-lints=allow");
+            args.extend(lint_filter.iter().map(|filter| filter.as_str()))
+        }
+
         let all_output = std::process::Command::new(&cargo_clippy_path)
             // use the looping index to create individual target dirs
             .env(
@@ -360,14 +383,18 @@ impl Crate {
 
 #[derive(Debug)]
 struct LintcheckConfig {
-    // max number of jobs to spawn (default 1)
+    /// max number of jobs to spawn (default 1)
     max_jobs: usize,
-    // we read the sources to check from here
+    /// we read the sources to check from here
     sources_toml_path: PathBuf,
-    // we save the clippy lint results here
+    /// we save the clippy lint results here
     lintcheck_results_path: PathBuf,
-    // whether to just run --fix and not collect all the warnings
+    /// whether to just run --fix and not collect all the warnings
     fix: bool,
+    /// A list of lint that this lintcheck run shound focus on
+    lint_filter: Vec<String>,
+    /// Indicate if the output should support markdown syntax
+    markdown: bool,
 }
 
 impl LintcheckConfig {
@@ -383,12 +410,17 @@ impl LintcheckConfig {
                 .to_string()
         });
 
+        let markdown = clap_config.is_present("markdown");
         let sources_toml_path = PathBuf::from(sources_toml);
 
         // for the path where we save the lint results, get the filename without extension (so for
         // wasd.toml, use "wasd"...)
         let filename: PathBuf = sources_toml_path.file_stem().unwrap().into();
-        let lintcheck_results_path = PathBuf::from(format!("lintcheck-logs/{}_logs.txt", filename.display()));
+        let lintcheck_results_path = PathBuf::from(format!(
+            "lintcheck-logs/{}_logs.{}",
+            filename.display(),
+            if markdown { "md" } else { "txt" }
+        ));
 
         // look at the --threads arg, if 0 is passed, ask rayon rayon how many threads it would spawn and
         // use half of that for the physical core count
@@ -410,12 +442,27 @@ impl LintcheckConfig {
             None => 1,
         };
         let fix: bool = clap_config.is_present("fix");
+        let lint_filter: Vec<String> = clap_config
+            .values_of("filter")
+            .map(|iter| {
+                iter.map(|lint_name| {
+                    let mut filter = lint_name.replace('_', "-");
+                    if !filter.starts_with("clippy::") {
+                        filter.insert_str(0, "clippy::");
+                    }
+                    filter
+                })
+                .collect()
+            })
+            .unwrap_or_default();
 
         LintcheckConfig {
             max_jobs,
             sources_toml_path,
             lintcheck_results_path,
             fix,
+            lint_filter,
+            markdown,
         }
     }
 }
@@ -563,7 +610,7 @@ fn parse_json_message(json_message: &str, krate: &Crate) -> ClippyWarning {
     }
 }
 
-/// Generate a short list of occuring lints-types and their count
+/// Generate a short list of occurring lints-types and their count
 fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) {
     // count lint type occurrences
     let mut counter: HashMap<&String, usize> = HashMap::new();
@@ -577,10 +624,15 @@ fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String,
     // to not have a lint with 200 and 2 warnings take the same spot
     stats.sort_by_key(|(lint, count)| format!("{:0>4}, {}", count, lint));
 
+    let mut header = String::from("| lint                                               | count |\n");
+    header.push_str("| -------------------------------------------------- | ----- |\n");
     let stats_string = stats
         .iter()
-        .map(|(lint, count)| format!("{} {}\n", lint, count))
-        .collect::<String>();
+        .map(|(lint, count)| format!("| {:<50} |  {:>4} |\n", lint, count))
+        .fold(header, |mut table, line| {
+            table.push_str(&line);
+            table
+        });
 
     (stats_string, counter)
 }
@@ -682,6 +734,15 @@ pub fn main() {
     let old_stats = read_stats_from_file(&config.lintcheck_results_path);
 
     let counter = AtomicUsize::new(1);
+    let lint_filter: Vec<String> = config
+        .lint_filter
+        .iter()
+        .map(|filter| {
+            let mut filter = filter.clone();
+            filter.insert_str(0, "--force-warn=");
+            filter
+        })
+        .collect();
 
     let clippy_warnings: Vec<ClippyWarning> = if let Some(only_one_crate) = clap_config.value_of("only") {
         // if we don't have the specified crate in the .toml, throw an error
@@ -705,7 +766,9 @@ pub fn main() {
             .into_iter()
             .map(|krate| krate.download_and_extract())
             .filter(|krate| krate.name == only_one_crate)
-            .flat_map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &AtomicUsize::new(0), 1, 1, config.fix))
+            .flat_map(|krate| {
+                krate.run_clippy_lints(&cargo_clippy_path, &AtomicUsize::new(0), 1, 1, config.fix, &lint_filter)
+            })
             .collect()
     } else {
         if config.max_jobs > 1 {
@@ -718,7 +781,7 @@ pub fn main() {
             // quarter of the time which might result in a longer wall clock runtime
 
             // This helps when we check many small crates with dep-trees that don't have a lot of branches in
-            // order to achive some kind of parallelism
+            // order to achieve some kind of parallelism
 
             // by default, use a single thread
             let num_cpus = config.max_jobs;
@@ -729,7 +792,14 @@ pub fn main() {
                 .into_par_iter()
                 .map(|krate| krate.download_and_extract())
                 .flat_map(|krate| {
-                    krate.run_clippy_lints(&cargo_clippy_path, &counter, num_cpus, num_crates, config.fix)
+                    krate.run_clippy_lints(
+                        &cargo_clippy_path,
+                        &counter,
+                        num_cpus,
+                        num_crates,
+                        config.fix,
+                        &lint_filter,
+                    )
                 })
                 .collect()
         } else {
@@ -738,7 +808,9 @@ pub fn main() {
             crates
                 .into_iter()
                 .map(|krate| krate.download_and_extract())
-                .flat_map(|krate| krate.run_clippy_lints(&cargo_clippy_path, &counter, 1, num_crates, config.fix))
+                .flat_map(|krate| {
+                    krate.run_clippy_lints(&cargo_clippy_path, &counter, 1, num_crates, config.fix, &lint_filter)
+                })
                 .collect()
         }
     };
@@ -758,22 +830,31 @@ pub fn main() {
         .map(|w| (&w.crate_name, &w.message))
         .collect();
 
-    let mut all_msgs: Vec<String> = clippy_warnings.iter().map(ToString::to_string).collect();
+    let mut all_msgs: Vec<String> = clippy_warnings
+        .iter()
+        .map(|warn| warn.to_output(config.markdown))
+        .collect();
     all_msgs.sort();
-    all_msgs.push("\n\n\n\nStats:\n".into());
+    all_msgs.push("\n\n### Stats:\n\n".into());
     all_msgs.push(stats_formatted);
 
     // save the text into lintcheck-logs/logs.txt
     let mut text = clippy_ver; // clippy version number on top
-    text.push_str(&format!("\n{}", all_msgs.join("")));
-    text.push_str("ICEs:\n");
+    text.push_str("\n### Reports\n\n");
+    if config.markdown {
+        text.push_str("| file | lint | message |\n");
+        text.push_str("| --- | --- | --- |\n");
+    }
+    text.push_str(&format!("{}", all_msgs.join("")));
+    text.push_str("\n\n### ICEs:\n");
     ices.iter()
         .for_each(|(cratename, msg)| text.push_str(&format!("{}: '{}'", cratename, msg)));
 
     println!("Writing logs to {}", config.lintcheck_results_path.display());
+    std::fs::create_dir_all(config.lintcheck_results_path.parent().unwrap()).unwrap();
     write(&config.lintcheck_results_path, text).unwrap();
 
-    print_stats(old_stats, new_stats);
+    print_stats(old_stats, new_stats, &config.lint_filter);
 }
 
 /// read the previous stats from the lintcheck-log file
@@ -787,26 +868,27 @@ fn read_stats_from_file(file_path: &Path) -> HashMap<String, usize> {
 
     let lines: Vec<String> = file_content.lines().map(ToString::to_string).collect();
 
-    // search for the beginning "Stats:" and the end "ICEs:" of the section we want
-    let start = lines.iter().position(|line| line == "Stats:").unwrap();
-    let end = lines.iter().position(|line| line == "ICEs:").unwrap();
-
-    let stats_lines = &lines[start + 1..end];
-
-    stats_lines
+    lines
         .iter()
-        .map(|line| {
-            let mut spl = line.split(' ');
-            (
-                spl.next().unwrap().to_string(),
-                spl.next().unwrap().parse::<usize>().unwrap(),
-            )
+        .skip_while(|line| line.as_str() != "### Stats:")
+        // Skipping the table header and the `Stats:` label
+        .skip(4)
+        .take_while(|line| line.starts_with("| "))
+        .filter_map(|line| {
+            let mut spl = line.split('|');
+            // Skip the first `|` symbol
+            spl.next();
+            if let (Some(lint), Some(count)) = (spl.next(), spl.next()) {
+                Some((lint.trim().to_string(), count.trim().parse::<usize>().unwrap()))
+            } else {
+                None
+            }
         })
         .collect::<HashMap<String, usize>>()
 }
 
 /// print how lint counts changed between runs
-fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, usize>) {
+fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, usize>, lint_filter: &Vec<String>) {
     let same_in_both_hashmaps = old_stats
         .iter()
         .filter(|(old_key, old_val)| new_stats.get::<&String>(&old_key) == Some(old_val))
@@ -845,6 +927,7 @@ fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, us
     old_stats_deduped
         .iter()
         .filter(|(old_key, _)| new_stats_deduped.get::<&String>(&old_key).is_none())
+        .filter(|(old_key, _)| lint_filter.is_empty() || lint_filter.contains(old_key))
         .for_each(|(old_key, old_value)| {
             println!("{} {} => 0", old_key, old_value);
         });
@@ -903,6 +986,19 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
                 .long("--fix")
                 .help("runs cargo clippy --fix and checks if all suggestions apply"),
         )
+        .arg(
+            Arg::with_name("filter")
+                .long("--filter")
+                .takes_value(true)
+                .multiple(true)
+                .value_name("clippy_lint_name")
+                .help("apply a filter to only collect specified lints, this also overrides `allow` attributes"),
+        )
+        .arg(
+            Arg::with_name("markdown")
+                .long("--markdown")
+                .help("change the reports table to use markdown links"),
+        )
         .get_matches()
 }