use crate::config::Config;
use std::path::PathBuf;
use syn::{File, visit::Visit};
mod syntax;
mod style;
mod imports;
mod unsafe_code;
mod complexity;
mod docs;
mod unwrap_usage;
mod todo_macros;
mod must_use;
mod anti_patterns;
pub use syntax::*;
pub use style::*;
pub use imports::*;
pub use unsafe_code::*;
pub use complexity::*;
pub use docs::*;
pub use unwrap_usage::*;
pub use todo_macros::*;
pub use must_use::*;
pub use anti_patterns::*;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Issue {
pub rule: String, pub severity: Severity,
pub message: String,
pub location: Location,
pub fix: Option<Fix>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Location {
pub line: usize,
pub column: usize,
pub end_line: Option<usize>,
pub end_column: Option<usize>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Fix {
pub description: String,
pub replacements: Vec<Replacement>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Replacement {
pub start: usize,
pub end: usize,
pub text: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Severity {
Error,
Warning,
Info,
}
pub struct RuleContext {
pub file_path: PathBuf,
pub content: String,
pub syntax_tree: File,
pub issues: Vec<Issue>,
}
pub trait Rule: Send + Sync {
fn name(&self) -> &'static str;
fn check(&self, ctx: &mut RuleContext);
}
impl RuleContext {
pub fn new(file_path: PathBuf, content: String, syntax_tree: File) -> Self {
Self {
file_path,
content,
syntax_tree,
issues: Vec::new(),
}
}
pub fn report(&mut self, issue: Issue) {
self.issues.push(issue);
}
pub fn report_issue(&mut self, rule: &str, severity: Severity, message: String, location: Location, fix: Option<Fix>) {
self.issues.push(Issue {
rule: rule.to_string(),
severity,
message,
location,
fix,
});
}
pub fn line_col(&self, _span: proc_macro2::Span) -> (usize, usize) {
(1, 1)
}
}
impl Severity {
pub fn github_level(&self) -> &'static str {
match self {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Info => "notice",
}
}
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Severity::Error => write!(f, "error"),
Severity::Warning => write!(f, "warning"),
Severity::Info => write!(f, "info"),
}
}
}
impl Issue {
pub fn display(&self) -> String {
use colored::*;
let severity_str = match self.severity {
Severity::Error => "error".red().bold(),
Severity::Warning => "warning".yellow().bold(),
Severity::Info => "info".cyan().bold(),
};
format!(
" {}:{} {} [{}] {}",
self.location.line,
self.location.column,
severity_str,
self.rule.dimmed(),
self.message
)
}
}
pub fn get_enabled_rules(config: &Config) -> Vec<Box<dyn Rule>> {
let mut rules: Vec<Box<dyn Rule>> = vec![];
rules.push(Box::new(UnmatchedDelimitersRule));
rules.push(Box::new(InvalidSyntaxRule));
if config.rules.check_naming {
rules.push(Box::new(NamingConventionRule));
}
if config.rules.check_line_length {
rules.push(Box::new(LineLengthRule::new(config.style.max_line_length)));
}
if config.rules.check_imports {
rules.push(Box::new(ImportOrderRule));
rules.push(Box::new(UnusedImportRule));
}
if config.rules.check_unsafe {
rules.push(Box::new(UnsafeBlockRule));
}
if config.rules.check_complexity {
rules.push(Box::new(CyclomaticComplexityRule::new(
config.complexity.max_cyclomatic
)));
rules.push(Box::new(CognitiveComplexityRule::new(
config.complexity.max_cognitive
)));
}
if config.rules.check_missing_docs {
rules.push(Box::new(MissingDocsRule));
}
if config.rules.check_unwrap_usage {
rules.push(Box::new(UnwrapUsageRule));
}
if config.rules.check_todo_macros {
rules.push(Box::new(TodoMacroRule));
}
if config.rules.check_must_use {
rules.push(Box::new(MustUseRule));
}
if config.rules.check_anti_patterns {
rules.push(Box::new(AntiPatternsRule));
}
rules
}