use crate::commands::doctor::diagnosis::Check;
use crate::commands::doctor::types::{Diagnostic, Severity};
use std::process::{Command, Output};
pub trait CommandExecutor {
fn execute_clippy(&self) -> std::io::Result<Output>;
}
pub struct RealCommandExecutor;
impl CommandExecutor for RealCommandExecutor {
fn execute_clippy(&self) -> std::io::Result<Output> {
Command::new("cargo")
.args(["clippy", "--quiet", "--message-format=json"])
.output()
}
}
pub struct LintsCheck {
executor: Box<dyn CommandExecutor>,
}
impl LintsCheck {
pub fn new() -> Self {
Self {
executor: Box::new(RealCommandExecutor),
}
}
#[cfg(test)]
pub fn with_executor(executor: Box<dyn CommandExecutor>) -> Self {
Self { executor }
}
}
impl Check for LintsCheck {
fn run(&self) -> Vec<Diagnostic> {
let mut diagnostics = Vec::new();
let clippy_result = self.executor.execute_clippy();
match clippy_result {
Ok(output) => {
if output.status.success() {
diagnostics.push(Diagnostic::new(
self.name(),
Severity::Info,
"No linting issues found with clippy",
self.category(),
));
} else {
diagnostics.push(
Diagnostic::new(
self.name(),
Severity::Warning,
"Clippy found linting issues",
self.category(),
)
.with_suggestion("Run 'cargo clippy' to see and fix the issues"),
);
}
}
Err(_) => {
diagnostics.push(
Diagnostic::new(
self.name(),
Severity::Warning,
"Failed to run cargo clippy",
self.category(),
)
.with_suggestion(
"Make sure clippy is installed: 'rustup component add clippy'",
),
);
}
}
diagnostics
}
fn name(&self) -> &str {
"Linting"
}
fn description(&self) -> &str {
"Check for linting issues with clippy"
}
fn category(&self) -> &str {
"lints"
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::io::{Error, ErrorKind};
use std::os::unix::process::ExitStatusExt;
use std::process::ExitStatus;
enum MockExecutorResult {
Success(Output),
Failure(Output),
Error,
}
struct MockCommandExecutor {
result: MockExecutorResult,
}
impl CommandExecutor for MockCommandExecutor {
fn execute_clippy(&self) -> std::io::Result<Output> {
match &self.result {
MockExecutorResult::Success(output) => Ok(output.clone()),
MockExecutorResult::Failure(output) => Ok(output.clone()),
MockExecutorResult::Error => {
Err(Error::new(ErrorKind::NotFound, "command not found"))
}
}
}
}
fn success_output() -> Output {
Output {
status: ExitStatus::from_raw(0),
stdout: Vec::new(),
stderr: Vec::new(),
}
}
fn failure_output() -> Output {
Output {
status: ExitStatus::from_raw(1),
stdout: Vec::new(),
stderr: b"clippy errors found".to_vec(),
}
}
#[test]
fn test_lints_check_success() {
let executor = MockCommandExecutor {
result: MockExecutorResult::Success(success_output()),
};
let check = LintsCheck::with_executor(Box::new(executor));
let diagnostics = check.run();
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].severity, Severity::Info);
assert!(diagnostics[0].message.contains("No linting issues found"));
}
#[test]
fn test_lints_check_warning() {
let executor = MockCommandExecutor {
result: MockExecutorResult::Failure(failure_output()),
};
let check = LintsCheck::with_executor(Box::new(executor));
let diagnostics = check.run();
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].severity, Severity::Warning);
assert!(diagnostics[0]
.message
.contains("Clippy found linting issues"));
if let Some(suggestion) = &diagnostics[0].suggestion {
assert!(suggestion.contains("Run 'cargo clippy'"));
} else {
panic!("Expected suggestion to be present");
}
}
#[test]
fn test_lints_check_error() {
let executor = MockCommandExecutor {
result: MockExecutorResult::Error,
};
let check = LintsCheck::with_executor(Box::new(executor));
let diagnostics = check.run();
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].severity, Severity::Warning);
assert!(diagnostics[0]
.message
.contains("Failed to run cargo clippy"));
if let Some(suggestion) = &diagnostics[0].suggestion {
assert!(suggestion.contains("Make sure clippy is installed"));
} else {
panic!("Expected suggestion to be present");
}
}
#[test]
fn test_name_method() {
let check = LintsCheck::new();
assert_eq!(check.name(), "Linting");
}
#[test]
fn test_description_method() {
let check = LintsCheck::new();
assert_eq!(check.description(), "Check for linting issues with clippy");
}
#[test]
fn test_category_method() {
let check = LintsCheck::new();
assert_eq!(check.category(), "lints");
}
}