Skip to main content

frs/cmds/rsl/
engine.rs

1//! Rule engine for the `frs rsl` command.
2
3use std::path::Path;
4use std::path::PathBuf;
5
6use rayon::prelude::*;
7use rootcause::report;
8
9use crate::cmds::rsl::rules::RuleViolation;
10
11pub struct FileContext<'ast> {
12    pub path: &'ast Path,
13    pub file: &'ast syn::File,
14    pub(super) module_item_lists: Vec<Vec<crate::cmds::rsl::ast::ModuleItem<'ast>>>,
15}
16
17pub(super) fn check_paths(paths: &[PathBuf]) -> rootcause::Result<Vec<Box<dyn RuleViolation>>> {
18    // Collect results before propagating errors to preserve input order.
19    let file_results: Vec<_> = paths.par_iter().map(|path| self::check_path(path)).collect();
20    let mut violations = Vec::new();
21
22    for file_result in file_results {
23        violations.extend(file_result?);
24    }
25
26    Ok(violations)
27}
28
29fn check_path(path: &Path) -> rootcause::Result<Vec<Box<dyn RuleViolation>>> {
30    let source = std::fs::read_to_string(path).map_err(|error| {
31        report!("could not read Rust source")
32            .attach(format!("path={}", path.display()))
33            .attach(format!("error={error}"))
34    })?;
35    let syntax = syn::parse_file(&source).map_err(|error| {
36        report!("could not parse Rust source")
37            .attach(format!("path={}", path.display()))
38            .attach(format!("error={error}"))
39    })?;
40    let module_item_lists = crate::cmds::rsl::ast::module_item_lists(&syntax);
41
42    Ok(crate::cmds::rsl::rules::check(&FileContext {
43        path,
44        file: &syntax,
45        module_item_lists,
46    }))
47}