Skip to main content

lintel_validate/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(unused_assignments)] // thiserror/miette derive macros trigger false positives
3
4extern crate alloc;
5
6use std::time::Instant;
7
8use anyhow::Result;
9use bpaf::{Bpaf, ShellComp};
10use lintel_diagnostics::reporter::{CheckResult, Reporter};
11
12use lintel_cli_common::CliCacheOptions;
13
14// -----------------------------------------------------------------------
15// Core validation modules
16// -----------------------------------------------------------------------
17
18pub mod catalog;
19pub mod discover;
20pub mod parsers;
21pub mod registry;
22pub(crate) mod suggest;
23pub mod validate;
24
25// -----------------------------------------------------------------------
26// ValidateArgs — shared CLI struct
27// -----------------------------------------------------------------------
28
29#[derive(Debug, Clone, Bpaf)]
30pub struct ValidateArgs {
31    #[bpaf(long("exclude"), argument("PATTERN"))]
32    pub exclude: Vec<String>,
33
34    #[bpaf(external(lintel_cli_common::cli_cache_options))]
35    pub cache: CliCacheOptions,
36
37    #[bpaf(positional("PATH"), complete_shell(ShellComp::File { mask: None }))]
38    pub globs: Vec<String>,
39}
40
41impl From<&ValidateArgs> for validate::ValidateArgs {
42    fn from(args: &ValidateArgs) -> Self {
43        // When a single directory is passed as an arg, use it as the config
44        // search directory so that `lintel.toml` inside that directory is found.
45        let config_dir = args
46            .globs
47            .iter()
48            .find(|g| std::path::Path::new(g).is_dir())
49            .map(std::path::PathBuf::from);
50
51        validate::ValidateArgs {
52            globs: args.globs.clone(),
53            exclude: args.exclude.clone(),
54            cache_dir: args.cache.cache_dir.clone(),
55            force_schema_fetch: args.cache.force_schema_fetch || args.cache.force,
56            force_validation: args.cache.force_validation || args.cache.force,
57            no_catalog: args.cache.no_catalog,
58            config_dir,
59            schema_cache_ttl: args.cache.schema_cache_ttl,
60        }
61    }
62}
63
64// -----------------------------------------------------------------------
65// Helpers
66// -----------------------------------------------------------------------
67
68/// Load `lintel.toml` and merge its excludes into the args.
69///
70/// Config excludes are prepended so they have the same priority as CLI excludes.
71/// When a directory arg is passed (e.g. `lintel check some/dir`), we search
72/// for `lintel.toml` starting from that directory rather than cwd.
73pub fn merge_config(args: &mut ValidateArgs) {
74    let search_dir = args
75        .globs
76        .iter()
77        .find(|g| std::path::Path::new(g).is_dir())
78        .map(std::path::PathBuf::from);
79
80    let cfg_result = match &search_dir {
81        Some(dir) => lintel_config::find_and_load(dir).map(Option::unwrap_or_default),
82        None => lintel_config::load(),
83    };
84
85    match cfg_result {
86        Ok(cfg) => {
87            // Config excludes first, then CLI excludes.
88            let cli_excludes = core::mem::take(&mut args.exclude);
89            args.exclude = cfg.exclude;
90            args.exclude.extend(cli_excludes);
91        }
92        Err(e) => {
93            eprintln!("warning: failed to load lintel.toml: {e}");
94        }
95    }
96}
97
98// -----------------------------------------------------------------------
99// Run function — shared between check/ci/validate commands
100// -----------------------------------------------------------------------
101
102/// Run validation and report results via the given reporter.
103///
104/// Returns `true` if there were validation errors, `false` if clean.
105///
106/// # Errors
107///
108/// Returns an error if file collection or schema validation encounters an I/O error.
109pub async fn run(args: &mut ValidateArgs, reporter: &mut dyn Reporter) -> Result<bool> {
110    merge_config(args);
111
112    let lib_args = validate::ValidateArgs::from(&*args);
113    let start = Instant::now();
114    let result: CheckResult = validate::run_with(&lib_args, None, |file| {
115        reporter.on_file_checked(file);
116    })
117    .await?;
118    let had_errors = result.has_errors();
119    let elapsed = start.elapsed();
120
121    reporter.report(result, elapsed);
122
123    Ok(had_errors)
124}