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