mod commands;
mod content;
mod structure;
use std::path::Path;
use crate::config::Config;
use crate::diagnostic::sort_diagnostics;
use crate::project::ProjectFacts;
use crate::scanner::SourceFile;
use crate::{Diagnostic, LintReport, Severity};
pub(crate) fn analyze(
root: &Path,
files: &[SourceFile],
project: &ProjectFacts,
config: &Config,
minimum_severity: Option<Severity>,
) -> LintReport {
let mut diagnostics = Vec::new();
let mut total_tokens = 0;
for file in files {
structure::check(root, file, &mut diagnostics);
content::check_file(root, file, &mut diagnostics);
commands::check(root, file, project, &mut diagnostics);
total_tokens += content::approximate_tokens(&file.content);
}
let duplicated_tokens = content::check_cross_file(files, &mut diagnostics);
diagnostics = diagnostics
.into_iter()
.filter_map(|diagnostic| config.apply(diagnostic))
.filter(|diagnostic| {
minimum_severity.is_none_or(|minimum| diagnostic.severity.rank() >= minimum.rank())
})
.collect();
sort_diagnostics(&mut diagnostics);
LintReport {
version: env!("CARGO_PKG_VERSION"),
root: root.to_path_buf(),
files_scanned: files.len(),
estimated_total_tokens: total_tokens,
estimated_duplicated_tokens: duplicated_tokens,
diagnostics,
fixed_files: Vec::new(),
}
}
pub(super) fn line_column(text: &str, offset: usize) -> (usize, usize) {
let prefix = &text[..offset.min(text.len())];
let line = prefix.bytes().filter(|byte| *byte == b'\n').count() + 1;
let column = prefix
.rsplit('\n')
.next()
.map_or(1, |part| part.chars().count() + 1);
(line, column)
}
pub(super) fn push(items: &mut Vec<Diagnostic>, diagnostic: Diagnostic) {
if !items.iter().any(|existing| {
existing.rule == diagnostic.rule
&& existing.file == diagnostic.file
&& existing.line == diagnostic.line
&& existing.message == diagnostic.message
}) {
items.push(diagnostic);
}
}