Skip to main content

agentlint/analysis/
mod.rs

1mod commands;
2mod content;
3mod structure;
4
5use std::path::Path;
6
7use crate::config::Config;
8use crate::diagnostic::sort_diagnostics;
9use crate::project::ProjectFacts;
10use crate::scanner::SourceFile;
11use crate::{Diagnostic, LintReport, Severity};
12
13pub(crate) fn analyze(
14    root: &Path,
15    files: &[SourceFile],
16    project: &ProjectFacts,
17    config: &Config,
18    minimum_severity: Option<Severity>,
19) -> LintReport {
20    let mut diagnostics = Vec::new();
21    let mut total_tokens = 0;
22
23    for file in files {
24        structure::check(root, file, &mut diagnostics);
25        content::check_file(root, file, &mut diagnostics);
26        commands::check(root, file, project, &mut diagnostics);
27        total_tokens += content::approximate_tokens(&file.content);
28    }
29    let duplicated_tokens = content::check_cross_file(files, &mut diagnostics);
30
31    diagnostics = diagnostics
32        .into_iter()
33        .filter_map(|diagnostic| config.apply(diagnostic))
34        .filter(|diagnostic| {
35            minimum_severity.is_none_or(|minimum| diagnostic.severity.rank() >= minimum.rank())
36        })
37        .collect();
38    sort_diagnostics(&mut diagnostics);
39
40    LintReport {
41        version: env!("CARGO_PKG_VERSION"),
42        root: root.to_path_buf(),
43        files_scanned: files.len(),
44        estimated_total_tokens: total_tokens,
45        estimated_duplicated_tokens: duplicated_tokens,
46        diagnostics,
47        fixed_files: Vec::new(),
48    }
49}
50
51pub(super) fn line_column(text: &str, offset: usize) -> (usize, usize) {
52    let prefix = &text[..offset.min(text.len())];
53    let line = prefix.bytes().filter(|byte| *byte == b'\n').count() + 1;
54    let column = prefix
55        .rsplit('\n')
56        .next()
57        .map_or(1, |part| part.chars().count() + 1);
58    (line, column)
59}
60
61pub(super) fn push(items: &mut Vec<Diagnostic>, diagnostic: Diagnostic) {
62    if !items.iter().any(|existing| {
63        existing.rule == diagnostic.rule
64            && existing.file == diagnostic.file
65            && existing.line == diagnostic.line
66            && existing.message == diagnostic.message
67    }) {
68        items.push(diagnostic);
69    }
70}