use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::error::Result;
use crate::facts::FactValues;
use crate::level::Level;
use crate::registry::RuleRegistry;
use crate::walker::FileIndex;
#[derive(Debug, Clone)]
pub struct Violation {
pub path: Option<PathBuf>,
pub message: String,
pub line: Option<usize>,
pub column: Option<usize>,
}
impl Violation {
pub fn new(message: impl Into<String>) -> Self {
Self {
path: None,
message: message.into(),
line: None,
column: None,
}
}
#[must_use]
pub fn with_path(mut self, path: impl Into<PathBuf>) -> Self {
self.path = Some(path.into());
self
}
#[must_use]
pub fn with_location(mut self, line: usize, column: usize) -> Self {
self.line = Some(line);
self.column = Some(column);
self
}
}
#[derive(Debug, Clone)]
pub struct RuleResult {
pub rule_id: String,
pub level: Level,
pub policy_url: Option<String>,
pub violations: Vec<Violation>,
pub is_fixable: bool,
}
impl RuleResult {
pub fn passed(&self) -> bool {
self.violations.is_empty()
}
}
#[derive(Debug)]
pub struct Context<'a> {
pub root: &'a Path,
pub index: &'a FileIndex,
pub registry: Option<&'a RuleRegistry>,
pub facts: Option<&'a FactValues>,
pub vars: Option<&'a HashMap<String, String>>,
pub git_tracked: Option<&'a std::collections::HashSet<std::path::PathBuf>>,
}
impl Context<'_> {
pub fn is_git_tracked(&self, rel_path: &Path) -> bool {
match self.git_tracked {
Some(set) => set.contains(rel_path),
None => false,
}
}
pub fn dir_has_tracked_files(&self, rel_path: &Path) -> bool {
match self.git_tracked {
Some(set) => crate::git::dir_has_tracked_files(rel_path, set),
None => false,
}
}
}
pub trait Rule: Send + Sync + std::fmt::Debug {
fn id(&self) -> &str;
fn level(&self) -> Level;
fn policy_url(&self) -> Option<&str> {
None
}
fn wants_git_tracked(&self) -> bool {
false
}
fn evaluate(&self, ctx: &Context<'_>) -> Result<Vec<Violation>>;
fn fixer(&self) -> Option<&dyn Fixer> {
None
}
}
#[derive(Debug)]
pub struct FixContext<'a> {
pub root: &'a Path,
pub dry_run: bool,
pub fix_size_limit: Option<u64>,
}
#[derive(Debug, Clone)]
pub enum FixOutcome {
Applied(String),
Skipped(String),
}
pub trait Fixer: Send + Sync + std::fmt::Debug {
fn describe(&self) -> String;
fn apply(&self, violation: &Violation, ctx: &FixContext<'_>) -> Result<FixOutcome>;
}
#[derive(Debug)]
pub enum ReadForFix {
Bytes(Vec<u8>),
Skipped(FixOutcome),
}
pub fn check_fix_size(
abs: &Path,
display_path: &std::path::Path,
ctx: &FixContext<'_>,
) -> Result<Option<FixOutcome>> {
let Some(limit) = ctx.fix_size_limit else {
return Ok(None);
};
let metadata = std::fs::metadata(abs).map_err(|source| crate::error::Error::Io {
path: abs.to_path_buf(),
source,
})?;
if metadata.len() > limit {
let reason = format!(
"{} is {} bytes; exceeds fix_size_limit ({}). Raise \
`fix_size_limit` in .alint.yml (or set it to `null` to disable) \
to fix files this large.",
display_path.display(),
metadata.len(),
limit,
);
eprintln!("alint: warning: {reason}");
return Ok(Some(FixOutcome::Skipped(reason)));
}
Ok(None)
}
pub fn read_for_fix(
abs: &Path,
display_path: &std::path::Path,
ctx: &FixContext<'_>,
) -> Result<ReadForFix> {
if let Some(outcome) = check_fix_size(abs, display_path, ctx)? {
return Ok(ReadForFix::Skipped(outcome));
}
let bytes = std::fs::read(abs).map_err(|source| crate::error::Error::Io {
path: abs.to_path_buf(),
source,
})?;
Ok(ReadForFix::Bytes(bytes))
}