1use std::process::Command;
2
3use anyhow::{bail, Context, Result};
4use clap::{Parser, Subcommand};
5
6#[derive(Parser)]
7#[command(name = "cargo xtask")]
8struct Args {
9 #[command(subcommand)]
10 command: CliCommand,
11}
12
13#[derive(Subcommand)]
14enum CliCommand {
15 /// Runs `cargo clippy`.
16 Clippy(ClippyArgs),
17}
18
19fn main() -> Result<()> {
20 let args = Args::parse();
21
22 match args.command {
23 CliCommand::Clippy(args) => run_clippy(args),
24 }
25}
26
27#[derive(Parser)]
28struct ClippyArgs {
29 /// Automatically apply lint suggestions (`clippy --fix`).
30 #[arg(long)]
31 fix: bool,
32
33 /// The package to run Clippy against (`cargo -p <PACKAGE> clippy`).
34 #[arg(long, short)]
35 package: Option<String>,
36}
37
38fn run_clippy(args: ClippyArgs) -> Result<()> {
39 let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
40
41 let mut clippy_command = Command::new(&cargo);
42 clippy_command.arg("clippy");
43
44 if let Some(package) = args.package.as_ref() {
45 clippy_command.args(["--package", package]);
46 } else {
47 clippy_command.arg("--workspace");
48 }
49
50 clippy_command
51 .arg("--release")
52 .arg("--all-targets")
53 .arg("--all-features");
54
55 if args.fix {
56 clippy_command.arg("--fix");
57 }
58
59 clippy_command.arg("--");
60
61 // Deny all warnings.
62 // We don't do this yet on Windows, as it still has some warnings present.
63 #[cfg(not(target_os = "windows"))]
64 clippy_command.args(["--deny", "warnings"]);
65
66 /// These are all of the rules that currently have violations in the Zed
67 /// codebase.
68 ///
69 /// We'll want to drive this list down by either:
70 /// 1. fixing violations of the rule and begin enforcing it
71 /// 2. deciding we want to allow the rule permanently, at which point
72 /// we should codify that separately in this task.
73 ///
74 /// This list shouldn't be added to; it should only get shorter.
75 const MIGRATORY_RULES_TO_ALLOW: &[&str] = &[
76 // There are a bunch of rules currently failing in the `style` group, so
77 // allow all of those, for now.
78 "clippy::style",
79 // Individual rules that have violations in the codebase:
80 "clippy::almost_complete_range",
81 "clippy::arc_with_non_send_sync",
82 "clippy::await_holding_lock",
83 "clippy::bool_comparison",
84 "clippy::borrow_deref_ref",
85 "clippy::borrowed_box",
86 "clippy::cast_abs_to_unsigned",
87 "clippy::cmp_owned",
88 "clippy::crate_in_macro_def",
89 "clippy::default_constructed_unit_structs",
90 "clippy::derivable_impls",
91 "clippy::derive_ord_xor_partial_ord",
92 "clippy::eq_op",
93 "clippy::expect_fun_call",
94 "clippy::explicit_counter_loop",
95 "clippy::identity_op",
96 "clippy::implied_bounds_in_impls",
97 "clippy::iter_kv_map",
98 "clippy::iter_overeager_cloned",
99 "clippy::let_underscore_future",
100 "clippy::map_entry",
101 "clippy::needless_lifetimes",
102 "clippy::needless_option_as_deref",
103 "clippy::needless_update",
104 "clippy::never_loop",
105 "clippy::non_canonical_clone_impl",
106 "clippy::non_canonical_partial_ord_impl",
107 "clippy::redundant_closure_call",
108 "clippy::redundant_guards",
109 "clippy::reversed_empty_ranges",
110 "clippy::single_range_in_vec_init",
111 "clippy::suspicious_to_owned",
112 "clippy::type_complexity",
113 "clippy::unnecessary_to_owned",
114 "clippy::vec_init_then_push",
115 ];
116
117 // When fixing violations automatically for a single package we don't care
118 // about the rules we're already violating, since it may be possible to
119 // have them fixed automatically.
120 let ignore_suppressed_rules = args.fix && args.package.is_some();
121 if !ignore_suppressed_rules {
122 for rule in MIGRATORY_RULES_TO_ALLOW {
123 clippy_command.args(["--allow", rule]);
124 }
125 }
126
127 // Deny `dbg!` and `todo!`s.
128 clippy_command
129 .args(["--deny", "clippy::dbg_macro"])
130 .args(["--deny", "clippy::todo"]);
131
132 eprintln!(
133 "running: {cargo} {}",
134 clippy_command
135 .get_args()
136 .map(|arg| arg.to_str().unwrap())
137 .collect::<Vec<_>>()
138 .join(" ")
139 );
140
141 let exit_status = clippy_command
142 .spawn()
143 .context("failed to spawn child process")?
144 .wait()
145 .context("failed to wait for child process")?;
146
147 if !exit_status.success() {
148 bail!("clippy failed: {}", exit_status);
149 }
150
151 Ok(())
152}