1use std::path::PathBuf;
2
3use clap::Args;
4use itertools::Itertools;
5use lux_lib::{config::Config, operations::Exec, workspace::Workspace};
6use path_slash::PathBufExt;
7
8use crate::utils::path::{classify_path, PathTarget};
9use crate::workspace::top_level_ignored_files;
10use miette::{IntoDiagnostic, Result};
11
12#[derive(Args)]
13pub struct Lint {
14 #[arg(long)]
16 path: Option<PathBuf>,
17 args: Option<Vec<String>>,
20 #[arg(long)]
24 no_ignore: bool,
25}
26
27pub async fn lint(lint_args: Lint, config: Config) -> Result<()> {
28 let target = match lint_args.path.as_deref() {
29 Some(path) => classify_path(path)?,
30 None => match Workspace::current()? {
31 Some(workspace) => PathTarget::Workspace(Box::new(workspace)),
32 None => PathTarget::Directory(std::env::current_dir().into_diagnostic()?),
33 },
34 };
35
36 let (target_path, workspace) = match target {
37 PathTarget::Workspace(ws) => (ws.root().to_slash_lossy().to_string(), Some(*ws)),
38 PathTarget::Directory(dir) => {
39 let ws = Workspace::from(&dir)?;
40 (dir.to_slash_lossy().to_string(), ws)
41 }
42 PathTarget::File(file) => {
43 let ws = match file.parent() {
44 Some(parent) => Workspace::from(parent)?,
45 None => None,
46 };
47 (file.to_slash_lossy().to_string(), ws)
48 }
49 };
50
51 let check_args: Vec<String> = match lint_args.args {
52 Some(args) => args,
53 None if lint_args.no_ignore => Vec::new(),
54 None => {
55 let ignored_files = workspace.iter().flat_map(|workspace| {
56 top_level_ignored_files(workspace)
57 .into_iter()
58 .map(|file| file.to_slash_lossy().to_string())
59 });
60 std::iter::once("--exclude-files".into())
61 .chain(ignored_files)
62 .collect_vec()
63 }
64 };
65
66 Exec::new("luacheck", None, &config)
67 .arg(target_path)
68 .args(check_args)
69 .disable_loader(true)
70 .exec()
71 .await?;
72
73 Ok(())
74}