1use std::collections::BTreeMap;
2use std::fs;
3use std::path::Path;
4
5use anyhow::{Context as _, Result};
6use cargo_toml::{Dependency, Manifest};
7use clap::Parser;
8
9use crate::workspace::load_workspace;
10
11#[derive(Parser)]
12pub struct PackageConformityArgs {}
13
14pub fn run_package_conformity(_args: PackageConformityArgs) -> Result<()> {
15 let workspace = load_workspace()?;
16
17 let mut non_workspace_dependencies = BTreeMap::new();
18
19 for package in workspace.workspace_packages() {
20 let is_extension = package
21 .manifest_path
22 .parent()
23 .and_then(|parent| parent.parent())
24 .is_some_and(|grandparent_dir| grandparent_dir.ends_with("extensions"));
25
26 let cargo_toml = read_cargo_toml(&package.manifest_path)?;
27
28 let is_using_workspace_lints = cargo_toml.lints.is_some_and(|lints| lints.workspace);
29 if !is_using_workspace_lints {
30 eprintln!(
31 "{package:?} is not using workspace lints",
32 package = package.name
33 );
34 }
35
36 // Extensions should not use workspace dependencies.
37 if is_extension || package.name == "zed_extension_api" {
38 continue;
39 }
40
41 // Ignore `workspace-hack`, as it produces a lot of false positives.
42 if package.name == "workspace-hack" {
43 continue;
44 }
45
46 for dependencies in [
47 &cargo_toml.dependencies,
48 &cargo_toml.dev_dependencies,
49 &cargo_toml.build_dependencies,
50 ] {
51 for (name, dependency) in dependencies {
52 if let Dependency::Inherited(_) = dependency {
53 continue;
54 }
55
56 non_workspace_dependencies
57 .entry(name.to_owned())
58 .or_insert_with(Vec::new)
59 .push(package.name.clone());
60 }
61 }
62 }
63
64 for (dependency, packages) in non_workspace_dependencies {
65 eprintln!(
66 "{dependency} is being used as a non-workspace dependency: {}",
67 packages.join(", ")
68 );
69 }
70
71 Ok(())
72}
73
74/// Returns the contents of the `Cargo.toml` file at the given path.
75fn read_cargo_toml(path: impl AsRef<Path>) -> Result<Manifest> {
76 let path = path.as_ref();
77 let cargo_toml_bytes = fs::read(path)?;
78 Manifest::from_slice(&cargo_toml_bytes)
79 .with_context(|| format!("reading Cargo.toml at {path:?}"))
80}