use crate::common::{ManifestTargetArgs, WorkspacePath};
use clap::Args;
#[derive(Args)]
pub struct LintArgs {
#[arg(long)]
all: bool,
#[command(flatten)]
workspace: WorkspacePath,
#[command(flatten)]
manifest: ManifestTargetArgs,
#[arg(long)]
fmt: bool,
#[arg(long)]
clippy: bool,
#[arg(long)]
strict: bool,
#[arg(long)]
dylint: bool,
}
impl LintArgs {
const fn has_explicit_selection(&self) -> bool {
self.all || self.fmt || self.clippy || self.dylint
}
pub fn resolve(self) -> anyhow::Result<cargo_gears_core::lint::LintParams> {
let workspace_path =
cargo_gears_core::common::resolve_workspace_path(self.workspace.path.as_deref())?;
let explicit = self.has_explicit_selection();
let resolved = self.manifest.into_selection().resolve(&workspace_path)?;
let policy = &resolved.lint;
let (fmt, clippy, dylint) = if explicit {
let all = self.all;
(self.fmt || all, self.clippy || all, self.dylint || all)
} else {
(
policy.fmt,
policy.clippy,
policy.dylint.as_ref().is_some_and(|d| d.enabled),
)
};
if self.strict && !clippy {
anyhow::bail!("`--strict` requires `--clippy` or `--all`");
}
let dylint_skip = resolved
.lint
.dylint
.as_ref()
.map_or_else(Vec::new, |d| d.skip.clone());
Ok(cargo_gears_core::lint::LintParams {
workspace_root: resolved.workspace_root,
fmt,
clippy,
strict: self.strict,
dylint,
dylint_skip,
})
}
}
#[cfg(test)]
mod tests {
use super::LintArgs;
use clap::Parser;
use std::fs;
use tempfile::TempDir;
#[derive(Parser)]
struct TestCli {
#[command(flatten)]
lint: LintArgs,
}
fn parse(temp: &TempDir, extra: &[&str]) -> LintArgs {
let p = temp.path().to_str().expect("temp path should be UTF-8");
let mut args = vec!["test", "-p", p, "--app", "app", "--env", "dev"];
args.extend(extra);
TestCli::try_parse_from(args).expect("should parse").lint
}
fn write_workspace(temp: &TempDir, manifest: &str) {
fs::write(temp.path().join("Gears.toml"), manifest).expect("write manifest");
fs::create_dir_all(temp.path().join("config")).expect("create config dir");
fs::write(temp.path().join("config/app-dev.yml"), "server: {}\n").expect("write config");
}
const MINIMAL: &str = "[apps.app.dev]\nconfig = \"app-dev.yml\"\ngears = []\n";
#[test]
fn parses_default_lint_args() {
let cli = TestCli::try_parse_from(["gears", "--app", "app1", "--env", "dev"])
.expect("lint args should parse");
assert!(!cli.lint.all);
assert!(!cli.lint.fmt);
assert!(!cli.lint.clippy);
assert!(!cli.lint.strict);
assert!(!cli.lint.dylint);
}
#[test]
fn defaults_to_manifest_lint_policy() {
let temp = TempDir::new().expect("temp dir");
write_workspace(
&temp,
&format!(
"{MINIMAL}\
[apps.app.dev.lint]\n\
fmt = false\n\
clippy = true\n\
[apps.app.dev.lint.dylint]\n\
enabled = true\n"
),
);
let resolved = parse(&temp, &[]).resolve().expect("resolve");
assert!(!resolved.fmt);
assert!(resolved.clippy);
assert!(resolved.dylint);
}
#[test]
fn explicit_selection_overrides_manifest_policy() {
let temp = TempDir::new().expect("temp dir");
write_workspace(
&temp,
&format!(
"{MINIMAL}\
[apps.app.dev.lint]\n\
fmt = true\n\
clippy = true\n"
),
);
let resolved = parse(&temp, &["--dylint"]).resolve().expect("resolve");
assert!(!resolved.fmt);
assert!(!resolved.clippy);
assert!(resolved.dylint);
}
#[test]
fn all_enables_every_lint() {
let temp = TempDir::new().expect("temp dir");
write_workspace(&temp, MINIMAL);
let resolved = parse(&temp, &["--all"]).resolve().expect("resolve");
assert!(resolved.fmt);
assert!(resolved.clippy);
assert!(resolved.dylint);
}
#[test]
fn strict_requires_clippy_or_all() {
let temp = TempDir::new().expect("temp dir");
write_workspace(&temp, MINIMAL);
let err = parse(&temp, &["--strict", "--dylint"])
.resolve()
.expect_err("strict without clippy should fail");
assert_eq!(err.to_string(), "`--strict` requires `--clippy` or `--all`");
}
#[test]
fn strict_with_clippy_is_accepted() {
let temp = TempDir::new().expect("temp dir");
write_workspace(&temp, MINIMAL);
parse(&temp, &["--strict", "--clippy"])
.resolve()
.expect("strict with clippy should succeed");
}
#[test]
fn strict_with_all_is_accepted() {
let temp = TempDir::new().expect("temp dir");
write_workspace(&temp, MINIMAL);
parse(&temp, &["--strict", "--all"])
.resolve()
.expect("strict with all should succeed");
}
#[test]
fn dylint_skip_from_manifest() {
let temp = TempDir::new().expect("temp dir");
write_workspace(
&temp,
&format!(
"{MINIMAL}\
[apps.app.dev.lint.dylint]\n\
enabled = true\n\
skip = [\"some_lint\", \"other_lint\"]\n"
),
);
let resolved = parse(&temp, &[]).resolve().expect("resolve");
assert_eq!(resolved.dylint_skip, vec!["some_lint", "other_lint"]);
}
}