wowlab_tidy/languages/toml/rules/cargo/
mod.rs1mod app_error_crates;
2mod bench_debug;
3mod crates_in_workspace;
4mod edition;
5mod feature_names;
6mod feature_no_std;
7mod flat_layout;
8mod mimalloc_apps;
9mod msrv;
10mod proc_macro_crate_helper;
11mod target_cpu;
12mod tempfile_dependency;
13mod unused_deps;
14mod workspace_dep_features;
15mod workspace_inheritance;
16mod workspace_lints;
17
18use crate::TomlCtx;
19
20const CARGO_WORKSPACE_REL: &str = "crates/Cargo.toml";
21const CARGO_DEP_TABLES: &[&str] = &["dependencies", "dev-dependencies", "build-dependencies"];
22
23fn is_cargo_member(rel: &str) -> bool {
24 rel != CARGO_WORKSPACE_REL && rel.starts_with("crates/") && is_cargo_manifest(rel)
25}
26
27fn is_cargo_manifest(rel: &str) -> bool {
28 rel.rsplit('/').next() == Some("Cargo.toml")
29}
30
31fn is_workspace_member_manifest(rel: &str) -> bool {
32 let mut parts = rel.split('/');
33
34 parts.next() == Some("crates")
35 && parts
36 .next()
37 .is_some_and(|dir| !dir.is_empty() && dir != "Cargo.toml")
38 && parts.next() == Some("Cargo.toml")
39 && parts.next().is_none()
40}
41
42fn assignment_line(lines: &[&str], key: &str) -> usize {
43 let quoted = format!("\"{key}\"");
44
45 lines
46 .iter()
47 .position(|line| {
48 let trimmed = line.trim_start();
49
50 trimmed
51 .strip_prefix("ed)
52 .or_else(|| trimmed.strip_prefix(key))
53 .is_some_and(|rest| rest.trim_start().starts_with('='))
54 })
55 .map_or(1, |index| index + 1)
56}
57
58fn cargo_document(ctx: &TomlCtx<'_>) -> Option<toml::Table> {
59 if !ctx.parse.errors.is_empty() {
60 return None;
61 }
62
63 toml::from_str(ctx.file.contents).ok()
64}
65
66fn dependency_tables(document: &toml::Table) -> Vec<&toml::Table> {
67 let mut tables = Vec::new();
68
69 for name in CARGO_DEP_TABLES {
70 if let Some(table) = document.get(*name).and_then(toml::Value::as_table) {
71 tables.push(table);
72 }
73 }
74
75 if let Some(targets) = document.get("target").and_then(toml::Value::as_table) {
76 for target in targets.values().filter_map(toml::Value::as_table) {
77 for name in CARGO_DEP_TABLES {
78 if let Some(table) = target.get(*name).and_then(toml::Value::as_table) {
79 tables.push(table);
80 }
81 }
82 }
83 }
84
85 tables
86}
87
88fn nested_table<'a>(document: &'a toml::Table, path: &[&str]) -> Option<&'a toml::Table> {
89 let mut table = document;
90
91 for segment in path {
92 table = table.get(*segment)?.as_table()?;
93 }
94
95 Some(table)
96}
97
98fn resolved_dependency_name<'a>(key: &'a str, value: &'a toml::Value) -> &'a str {
99 value
100 .as_table()
101 .and_then(|table| table.get("package"))
102 .and_then(toml::Value::as_str)
103 .unwrap_or(key)
104}
105
106fn header_name(line: &str) -> Option<&str> {
107 line.trim()
108 .strip_prefix('[')
109 .and_then(|rest| rest.strip_suffix(']'))
110 .map(str::trim)
111}
112
113fn section_line(lines: &[&str], section: &str) -> usize {
114 lines
115 .iter()
116 .position(|line| header_name(line) == Some(section))
117 .map_or(1, |index| index + 1)
118}
119
120fn key_line(lines: &[&str], section: &str, key: &str) -> usize {
121 if let Some(start) = lines
122 .iter()
123 .position(|line| header_name(line) == Some(section))
124 {
125 for (index, line) in lines.iter().enumerate().skip(start + 1) {
126 let trimmed = line.trim();
127
128 if header_name(trimmed).is_some() {
129 break;
130 }
131
132 let assigned = trimmed
133 .split_once('=')
134 .map(|(k, _)| k.trim().trim_matches('"'));
135
136 if assigned == Some(key) {
137 return index + 1;
138 }
139 }
140 }
141
142 let key_line = lines.iter().position(|line| {
143 header_name(line).is_some_and(|header| {
144 header
145 .strip_prefix(section)
146 .and_then(|rest| rest.strip_prefix('.'))
147 == Some(key)
148 })
149 });
150
151 key_line.map_or_else(|| section_line(lines, section), |index| index + 1)
152}