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::explicit_counter_loop",
94 "clippy::identity_op",
95 "clippy::implied_bounds_in_impls",
96 "clippy::iter_kv_map",
97 "clippy::iter_overeager_cloned",
98 "clippy::let_underscore_future",
99 "clippy::map_entry",
100 "clippy::needless_lifetimes",
101 "clippy::needless_option_as_deref",
102 "clippy::needless_update",
103 "clippy::never_loop",
104 "clippy::non_canonical_clone_impl",
105 "clippy::non_canonical_partial_ord_impl",
106 "clippy::redundant_closure_call",
107 "clippy::redundant_guards",
108 "clippy::reversed_empty_ranges",
109 "clippy::single_range_in_vec_init",
110 "clippy::suspicious_to_owned",
111 "clippy::type_complexity",
112 "clippy::unnecessary_to_owned",
113 "clippy::vec_init_then_push",
114 ];
115
116 // When fixing violations automatically for a single package we don't care
117 // about the rules we're already violating, since it may be possible to
118 // have them fixed automatically.
119 let ignore_suppressed_rules = args.fix && args.package.is_some();
120 if !ignore_suppressed_rules {
121 for rule in MIGRATORY_RULES_TO_ALLOW {
122 clippy_command.args(["--allow", rule]);
123 }
124 }
125
126 // Deny `dbg!` and `todo!`s.
127 clippy_command
128 .args(["--deny", "clippy::dbg_macro"])
129 .args(["--deny", "clippy::todo"]);
130
131 eprintln!(
132 "running: {cargo} {}",
133 clippy_command
134 .get_args()
135 .map(|arg| arg.to_str().unwrap())
136 .collect::<Vec<_>>()
137 .join(" ")
138 );
139
140 let exit_status = clippy_command
141 .spawn()
142 .context("failed to spawn child process")?
143 .wait()
144 .context("failed to wait for child process")?;
145
146 if !exit_status.success() {
147 bail!("clippy failed: {}", exit_status);
148 }
149
150 Ok(())
151}