Skip to main content

lux_cli/
check.rs

1use std::path::{Path, PathBuf};
2
3use clap::Args;
4use emmylua_check::OutputDestination;
5use itertools::Itertools;
6use lux_lib::{config::Config, progress::MultiProgress, workspace::Workspace};
7use miette::{miette, Result};
8
9use crate::{
10    args::OutputFormat,
11    utils::path::{classify_path, PathTarget},
12    workspace::{sync_dependencies_if_locked, sync_test_dependencies_if_locked},
13};
14
15#[derive(Args)]
16pub struct Check {
17    /// Path to a workspace, directory, or Lua file to check. Defaults to the current workspace.
18    #[arg(long)]
19    path: Option<PathBuf>,
20    /// Comma-separated list of ignore patterns.
21    /// Patterns must follow glob syntax.
22    /// Lux will automatically add top-level ignored project files.
23    #[arg(short, long, value_delimiter = ',')]
24    ignore: Option<Vec<String>>,
25
26    /// The output format.
27    #[arg(long, default_value = "text", value_enum, ignore_case = true)]
28    output_format: OutputFormat,
29
30    /// Output destination.{n}
31    /// (stdout or a file path, only used when the output format is json).
32    #[arg(long, default_value = "stdout")]
33    output: OutputDestination,
34
35    /// Treat warnings as errors.
36    #[arg(long)]
37    warnings_as_errors: bool,
38}
39
40impl From<OutputFormat> for emmylua_check::OutputFormat {
41    fn from(value: OutputFormat) -> Self {
42        match value {
43            OutputFormat::Json => emmylua_check::OutputFormat::Json,
44            OutputFormat::Text => emmylua_check::OutputFormat::Text,
45        }
46    }
47}
48
49pub async fn check(args: Check, config: Config) -> Result<()> {
50    let target = match args.path.as_deref() {
51        None => PathTarget::Workspace(Box::new(Workspace::current_or_err()?)),
52        Some(path) => classify_path(path)?,
53    };
54
55    let (workspace_dirs, rc_files) = match target {
56        PathTarget::Workspace(workspace) => {
57            let progress = MultiProgress::new_arc(&config);
58            sync_dependencies_if_locked(&workspace, progress.clone(), &config).await?;
59            sync_test_dependencies_if_locked(&workspace, progress, &config).await?;
60
61            let dirs = workspace
62                .members()
63                .iter()
64                .map(|project| project.root())
65                .flat_map(|project_root| {
66                    vec![
67                        project_root.join("src"),
68                        project_root.join("lua"),
69                        // For now, we don't include tests
70                        // because they require LLS_Addons definitions for busted
71
72                        // project_root.join("test"),
73                        // project_root.join("tests"),
74                        // project_root.join("spec"),
75                    ]
76                })
77                .filter(|dir| dir.is_dir())
78                .collect_vec();
79
80            let luarc_path = workspace.luarc_path(&config);
81
82            let rc = if luarc_path.is_file() {
83                Some(vec![luarc_path])
84            } else {
85                None
86            };
87
88            (dirs, rc)
89        }
90        PathTarget::Directory(dir) => {
91            let rc = resolve_rc_files(&dir, &config)?;
92            (vec![dir], rc)
93        }
94        PathTarget::File(file) => {
95            let root = file
96                .parent()
97                .unwrap_or_else(|| Path::new("."))
98                .to_path_buf();
99
100            let rc = resolve_rc_files(&root, &config)?;
101            (vec![root], rc)
102        }
103    };
104
105    if workspace_dirs.is_empty() {
106        println!("Nothing to check!");
107        return Ok(());
108    }
109
110    let emmylua_check_args = emmylua_check::CmdArgs {
111        config: rc_files,
112        workspace: workspace_dirs,
113        ignore: args.ignore,
114        output_format: args.output_format.into(),
115        output: args.output,
116        warnings_as_errors: args.warnings_as_errors,
117        verbose: config.verbose(),
118    };
119
120    emmylua_check::run_check(emmylua_check_args)
121        .await
122        .map_err(|err| miette!("{err}"))?;
123    Ok(())
124}
125
126fn resolve_rc_files(root: &Path, config: &Config) -> Result<Option<Vec<PathBuf>>> {
127    let rc_path = match Workspace::from(root)? {
128        Some(workspace) => workspace.luarc_path(config),
129        None => root.join(".luarc.json"),
130    };
131
132    if rc_path.is_file() {
133        Ok(Some(vec![rc_path]))
134    } else {
135        Ok(None)
136    }
137}