xtask/tasks/fmt/lints/
workspaced.rs1use super::Lint;
7use super::LintCtx;
8use super::Lintable;
9use std::path::Path;
10use std::path::PathBuf;
11use toml_edit::DocumentMut;
12use toml_edit::Item;
13use toml_edit::TableLike;
14use toml_edit::Value;
15
16static WORKSPACE_EXCEPTIONS: &[(&str, &[&str])] = &[
18 ("disk_blob", &["tokio"]),
22 ("mesh_rpc", &["tokio"]),
26];
27
28pub struct WorkspacedManifest {
29 members: Vec<PathBuf>,
30 excluded: Vec<PathBuf>,
31 dependencies: Vec<PathBuf>,
32}
33
34impl Lint for WorkspacedManifest {
35 fn new(_ctx: &LintCtx) -> Self {
36 WorkspacedManifest {
37 members: Vec::new(),
38 excluded: Vec::new(),
39 dependencies: Vec::new(),
40 }
41 }
42
43 fn enter_workspace(&mut self, content: &Lintable<DocumentMut>) {
44 self.members = content["workspace"]
46 .get("members")
47 .and_then(|m| m.as_array())
48 .into_iter()
49 .flat_map(|a| a.into_iter())
50 .map(|m| Path::new(m.as_str().unwrap()).join("Cargo.toml"))
51 .collect();
52 self.excluded = content["workspace"]
53 .get("exclude")
54 .and_then(|e| e.as_array())
55 .into_iter()
56 .flat_map(|a| a.into_iter())
57 .map(|e| Path::new(e.as_str().unwrap()).join("Cargo.toml"))
58 .collect();
59 self.dependencies = content["workspace"]
60 .get("dependencies")
61 .and_then(|d| d.as_table())
62 .into_iter()
63 .flat_map(|t| t.into_iter())
64 .filter_map(|(_k, v)| {
66 v.get("path")
67 .map(|p| Path::new(p.as_str().unwrap()).join("Cargo.toml"))
68 })
69 .collect();
70 }
71
72 fn enter_crate(&mut self, content: &Lintable<DocumentMut>) {
73 let mut count = 0;
75 if let Some(member) = self.members.iter().position(|m| content.path() == m) {
76 self.members.remove(member);
77 count += 1;
78 }
79 if let Some(excluded) = self.excluded.iter().position(|e| content.path() == e) {
80 self.excluded.remove(excluded);
81 count += 1;
82 }
83 if let Some(dependency) = self.dependencies.iter().position(|d| content.path() == d) {
84 self.dependencies.remove(dependency);
85 count += 1;
86 }
87
88 if count == 0 {
89 content.unfixable("crate is not a workspace member, dependency, or exclusion");
90 } else if count > 1 {
91 content.unfixable("crate appears in multiple workspace sections");
92 }
93 }
94
95 fn visit_file(&mut self, _content: &mut Lintable<String>) {}
96
97 fn exit_crate(&mut self, content: &mut Lintable<DocumentMut>) {
98 let mut dep_tables = Vec::new();
100 for (name, v) in content.iter() {
101 match name {
102 "dependencies" | "build-dependencies" | "dev-dependencies" => {
103 dep_tables.push(v.as_table_like().unwrap())
104 }
105 "target" => {
106 let flattened = v
107 .as_table_like()
108 .unwrap()
109 .iter()
110 .flat_map(|(_, v)| v.as_table_like().unwrap().iter());
111
112 for (k, v) in flattened {
113 match k {
114 "dependencies" | "build-dependencies" | "dev-dependencies" => {
115 dep_tables.push(v.as_table_like().unwrap())
116 }
117 _ => {}
118 }
119 }
120 }
121 _ => {}
122 }
123 }
124
125 let crate_name = content["package"]["name"].as_str().unwrap();
126 let handle_bad_dep = |dep_name| {
127 let allowed = WORKSPACE_EXCEPTIONS
128 .iter()
129 .find_map(|&(p, crates)| (p == crate_name).then_some(crates))
130 .unwrap_or(&[]);
131
132 if allowed.contains(&dep_name) {
133 log::debug!(
134 "{} contains non-workspaced dependency {}. Allowed by exception.",
135 content.path().display(),
136 dep_name
137 );
138 } else {
139 content.unfixable(&format!("non-workspaced dependency {} found", dep_name));
140 }
141 };
142 let check_table_like = |t: &dyn TableLike, dep_name| {
143 if t.get("workspace").and_then(|x| x.as_bool()) != Some(true) {
144 handle_bad_dep(dep_name);
145 }
146 };
147
148 for table in dep_tables {
149 for (dep_name, value) in table.iter() {
150 match value {
151 Item::Value(Value::String(_)) => handle_bad_dep(dep_name),
152 Item::Value(Value::InlineTable(t)) => {
153 check_table_like(t, dep_name);
154
155 if t.len() == 1 {
156 content.unfixable(&format!(
157 "inline table syntax used for dependency on {} but only one table entry is present, change to the dotted form",
158 dep_name
159 ));
160 }
161 }
162 Item::Table(t) => check_table_like(t, dep_name),
163 _ => unreachable!(),
164 }
165 }
166 }
167 }
168
169 fn exit_workspace(&mut self, content: &mut Lintable<DocumentMut>) {
170 for member in self.members.iter() {
172 content.unfixable(&format!(
173 "workspace member {} does not exist",
174 member.display()
175 ));
176 }
177 }
180}