Skip to main content

wowlab_tidy/languages/toml/rules/cargo/
mod.rs

1mod 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(&quoted)
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}