use super::{ClippyDiagnostic, LintCategory};
use std::path::Path;
use std::process::Command;
pub type DiagnosticMutationPair = (ClippyDiagnostic, Box<dyn crate::Mutation>);
#[derive(Debug, Clone)]
pub struct ClippyConfig {
pub machine_applicable_only: bool,
pub categories: Vec<LintCategory>,
pub include_lints: Vec<String>,
pub exclude_lints: Vec<String>,
pub extra_args: Vec<String>,
}
impl Default for ClippyConfig {
fn default() -> Self {
Self {
machine_applicable_only: true,
categories: Vec::new(),
include_lints: Vec::new(),
exclude_lints: Vec::new(),
extra_args: Vec::new(),
}
}
}
impl ClippyConfig {
pub fn new() -> Self {
Self::default()
}
pub fn machine_applicable_only(mut self) -> Self {
self.machine_applicable_only = true;
self
}
pub fn all_applicabilities(mut self) -> Self {
self.machine_applicable_only = false;
self
}
pub fn with_category(mut self, category: LintCategory) -> Self {
self.categories.push(category);
self
}
pub fn with_lint(mut self, lint: impl Into<String>) -> Self {
self.include_lints.push(lint.into());
self
}
pub fn without_lint(mut self, lint: impl Into<String>) -> Self {
self.exclude_lints.push(lint.into());
self
}
pub fn with_arg(mut self, arg: impl Into<String>) -> Self {
self.extra_args.push(arg.into());
self
}
fn build_args(&self) -> Vec<String> {
let mut args = vec![
"clippy".to_string(),
"--message-format=json".to_string(),
"--".to_string(),
];
for category in &self.categories {
args.push(format!("-W clippy::{}", category.as_str()));
}
for lint in &self.include_lints {
let lint_name = if lint.starts_with("clippy::") {
lint.clone()
} else {
format!("clippy::{}", lint)
};
args.push(format!("-W {}", lint_name));
}
for lint in &self.exclude_lints {
let lint_name = if lint.starts_with("clippy::") {
lint.clone()
} else {
format!("clippy::{}", lint)
};
args.push(format!("-A {}", lint_name));
}
args.extend(self.extra_args.clone());
args
}
}
#[derive(Debug)]
pub struct ClippyRunner {
config: ClippyConfig,
}
impl ClippyRunner {
pub fn new(config: ClippyConfig) -> Self {
Self { config }
}
pub fn default_runner() -> Self {
Self::new(ClippyConfig::default())
}
pub fn run(&self, path: impl AsRef<Path>) -> Result<Vec<ClippyDiagnostic>, ClippyError> {
let path = path.as_ref();
let output = Command::new("cargo")
.args(self.config.build_args())
.current_dir(path)
.output()
.map_err(|e| ClippyError::IoError(e.to_string()))?;
let _stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
let mut diagnostics = super::diagnostic::parse_clippy_output(&stdout)
.map_err(|e| ClippyError::ParseError(e.to_string()))?;
if self.config.machine_applicable_only {
diagnostics.retain(|d| d.has_auto_fix());
}
Ok(diagnostics)
}
pub fn run_to_mutations(
&self,
path: impl AsRef<Path>,
) -> Result<Vec<DiagnosticMutationPair>, ClippyError> {
let diagnostics = self.run(path)?;
let mutations: Vec<_> = diagnostics
.into_iter()
.filter_map(|d| {
let mutation = d.to_mutation()?;
Some((d, mutation))
})
.collect();
Ok(mutations)
}
}
#[derive(Debug, Clone)]
pub enum ClippyError {
IoError(String),
ParseError(String),
ClippyFailed(String),
}
impl std::fmt::Display for ClippyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClippyError::IoError(msg) => write!(f, "IO error: {}", msg),
ClippyError::ParseError(msg) => write!(f, "Parse error: {}", msg),
ClippyError::ClippyFailed(msg) => write!(f, "Clippy failed: {}", msg),
}
}
}
impl std::error::Error for ClippyError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_build_args() {
let config = ClippyConfig::new()
.with_category(LintCategory::Style)
.with_lint("bool_comparison")
.without_lint("clippy::too_many_arguments");
let args = config.build_args();
assert!(args.contains(&"clippy".to_string()));
assert!(args.contains(&"--message-format=json".to_string()));
assert!(args.iter().any(|a| a.contains("style")));
assert!(args.iter().any(|a| a.contains("bool_comparison")));
}
#[test]
fn test_config_machine_applicable() {
let config = ClippyConfig::new().machine_applicable_only();
assert!(config.machine_applicable_only);
let config2 = config.all_applicabilities();
assert!(!config2.machine_applicable_only);
}
}