blob: b8746e9031e37513047eda48ceaaa2ceaca1ef79 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use rustfix::diagnostics::Diagnostic;
use std::{
collections::{HashMap, HashSet},
io::BufRead,
process::Command,
};
use crate::span::Span;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Lint {
/// The lint name such as clippy::needless_borrow or unused_variable
pub name: String,
pub span: Span,
}
#[allow(unused)]
fn get_sysroot() -> String {
#[cfg(target_arch = "x86_64")]
const HOST_ARCH: &str = "x64";
#[cfg(target_arch = "aarch64")]
const HOST_ARCH: &str = "arm64";
#[cfg(target_os = "linux")]
const HOST_OS: &str = "linux";
#[cfg(target_os = "macos")]
const HOST_OS: &str = "mac";
#[cfg(test)]
let sysroot = env!("RUST_SYSROOT").to_owned();
#[cfg(not(test))]
let sysroot = format!("prebuilt/third_party/rust/{}-{}", HOST_OS, HOST_ARCH);
sysroot
}
/// Returns a mapping of the lint categories (all, style, etc.) to the individual
/// names of the lints they contain by parsing the output of `clippy-driver -Whelp`.
pub fn get_categories() -> HashMap<String, HashSet<String>> {
let sysroot = get_sysroot();
let output = Command::new(format!("{}/bin/clippy-driver", sysroot))
.arg("--sysroot")
.arg(&sysroot)
.arg("-Whelp")
.output()
.expect("Couldn't run clippy-driver");
if !output.status.success() {
panic!("Couldn't run clippy-driver: {:?}", output);
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut lines = stdout.lines().map(str::trim);
let parse_categories = |line: &str| {
if let [category, lints] = line.splitn(2, ' ').collect::<Vec<_>>()[..] {
(category.to_owned(), lints.split(',').map(|s| s.trim().replace('-', "_")).collect())
} else {
panic!("Malformed lint category output")
}
};
let err_msg = format!(
"Couldn't parse clippy-driver help output:\nstdout: {}\nstderr:{}\n",
stdout,
&String::from_utf8_lossy(&output.stderr)
);
lines.find(|s| s.starts_with("Lint groups provided by rustc")).expect(&err_msg);
// Skip the expected header from table
assert_eq!(
lines.by_ref().take(4).collect::<Vec<_>>(),
vec![
"",
"name sub-lints",
"---- ---------",
"warnings all lints that are set to issue warnings"
]
);
let mut categories = lines
.by_ref()
.take_while(|line| !line.is_empty())
.map(parse_categories)
.collect::<HashMap<_, _>>();
lines.find(|s| s.starts_with("Lint groups provided by plugins")).expect(&err_msg);
categories.extend(lines.skip(1).take_while(|line| !line.is_empty()).map(parse_categories));
categories
}
/// Constructs a map of source files to lints contained in them, filtering on
/// the given list of lints and categories.
pub fn filter_lints<R: BufRead>(input: &mut R, filter: &[String]) -> HashMap<String, Vec<Lint>> {
let categories = get_categories();
// If a lint category is given, add all lints in that category to the filter
let mut filter_lints: HashSet<String> = HashSet::new();
for f in filter {
if let Some(lints) = categories.get(f) {
filter_lints.extend(lints.iter().cloned());
} else {
filter_lints.insert(f.to_owned());
}
}
let mut files: HashMap<String, Vec<Lint>> = HashMap::new();
serde_json::Deserializer::from_reader(input)
.into_iter::<Diagnostic>()
.map(|result| result.expect("parsing diagnostic"))
.filter_map(|d| d.code.as_ref().map(|c| filter_lints.contains(&c.code).then(|| d.clone())))
.flatten()
.for_each(|lint| {
let span = lint.spans.iter().find(|s| s.is_primary).expect("no primary span found");
let file = &span.file_name;
// ignore stuff in the build directory
if file.starts_with("out/") {
eprintln!("Ignoring file inside build dir: {}", file);
} else {
files
.entry(file.to_string())
.or_default()
.push(Lint { name: lint.code.unwrap().code, span: span.into() });
}
});
files
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rustc_categories() {
let c = get_categories();
assert!(c["future-incompatible"].contains("forbidden_lint_groups"));
assert!(c["rust-2021-compatibility"].contains("non_fmt_panics"));
}
#[test]
fn test_clippy_categories() {
let c = get_categories();
assert!(c["clippy::complexity"].contains("clippy::zero_divided_by_zero"));
assert!(c["clippy::suspicious"].contains("clippy::print_in_format_impl"));
}
}