Skip to main content

agentlint/
lib.rs

1//! Reusable analysis engine for `agentlint`.
2
3pub mod analysis;
4pub mod config;
5pub mod diagnostic;
6pub mod fix;
7pub mod output;
8pub mod project;
9pub mod rules;
10pub mod scanner;
11
12use std::path::{Path, PathBuf};
13
14use anyhow::Result;
15use serde::Serialize;
16
17pub use config::{Config, SeveritySetting};
18pub use diagnostic::{Diagnostic, Severity, Span};
19pub use rules::{RuleCategory, RuleDefinition};
20
21/// Options supplied by front ends embedding the linter.
22#[derive(Debug, Clone, Default)]
23pub struct LintOptions {
24    pub config_path: Option<PathBuf>,
25    pub extra_ignores: Vec<String>,
26    pub minimum_severity: Option<Severity>,
27}
28
29/// Stable machine-readable result returned by the analysis engine.
30#[derive(Debug, Clone, Serialize)]
31pub struct LintReport {
32    pub version: &'static str,
33    pub root: PathBuf,
34    pub files_scanned: usize,
35    pub estimated_total_tokens: usize,
36    pub estimated_duplicated_tokens: usize,
37    pub diagnostics: Vec<Diagnostic>,
38    #[serde(skip_serializing_if = "Vec::is_empty")]
39    pub fixed_files: Vec<PathBuf>,
40}
41
42impl LintReport {
43    pub fn error_count(&self) -> usize {
44        self.diagnostics
45            .iter()
46            .filter(|item| item.severity == Severity::Error)
47            .count()
48    }
49
50    pub fn warning_count(&self) -> usize {
51        self.diagnostics
52            .iter()
53            .filter(|item| item.severity == Severity::Warning)
54            .count()
55    }
56}
57
58/// Scan and analyze a repository without executing any project code.
59///
60/// # Errors
61///
62/// Returns an error when the root cannot be resolved, configuration is invalid, or a
63/// relevant source file cannot be read safely.
64pub fn lint(root: &Path, options: &LintOptions) -> Result<LintReport> {
65    let root = root.canonicalize()?;
66    let config = Config::load(&root, options.config_path.as_deref())?;
67    let project = project::ProjectFacts::inspect(&root)?;
68    let files = scanner::scan(&root, &config, &options.extra_ignores)?;
69    Ok(analysis::analyze(
70        &root,
71        &files,
72        &project,
73        &config,
74        options.minimum_severity,
75    ))
76}