Skip to main content

wowlab_tidy/languages/toml/rules/manifest/
shared_anchors.rs

1use wowlab_fs::{file, path::Path};
2
3use super::MANIFEST_PREFIX;
4use crate::{Example, TomlCtx, Violation, violation};
5
6const EXAMPLES: &[Example] = &[];
7const ORDERED_SECTIONS: &[&str] = &["auras", "spells", "auto_attacks", "hero_talents", "effects"];
8
9crate::toml_rule!(
10    toml_manifest_shared_anchors,
11    "Require shared includes to anchor every order-sensitive section they contribute.",
12    "Explicit insertion points keep generated local indices and named declaration order stable as shared manifests evolve.",
13    High,
14);
15
16fn check_toml_manifest_shared_anchors(ctx: &TomlCtx<'_>) -> Vec<Violation> {
17    if !ctx.file.rel.starts_with(MANIFEST_PREFIX)
18        || Path::new(ctx.file.rel)
19            .file_name()
20            .is_none_or(|name| name != "manifest.toml")
21        || !ctx.parse.errors.is_empty()
22    {
23        return Vec::new();
24    }
25
26    let Ok(document) = toml::from_str::<toml::Table>(ctx.file.contents) else {
27        return Vec::new();
28    };
29    let Some(includes) = document.get("shared").and_then(toml::Value::as_array) else {
30        return Vec::new();
31    };
32    let Some(class_dir) = ctx.file.path.parent().and_then(Path::parent) else {
33        return Vec::new();
34    };
35    let mut violations = Vec::new();
36
37    for include in includes {
38        let Some(include) = include.as_table() else {
39            continue;
40        };
41        let Some(relative) = include.get("path").and_then(toml::Value::as_str) else {
42            continue;
43        };
44        let shared_path = class_dir.join("shared").join(relative);
45        let Ok(source) = file::read_text(&shared_path) else {
46            continue;
47        };
48        let Ok(shared) = toml::from_str::<toml::Table>(&source) else {
49            continue;
50        };
51        let before = include.get("before").and_then(toml::Value::as_table);
52        let append = include.get("append").and_then(toml::Value::as_table);
53
54        for section in ORDERED_SECTIONS {
55            if shared.contains_key(*section)
56                && before.is_none_or(|anchors| !anchors.contains_key(*section))
57                && append.is_none_or(|sections| {
58                    sections.get(*section).and_then(toml::Value::as_bool) != Some(true)
59                })
60            {
61                violations.push(violation(
62                    ctx.file.rel,
63                    1,
64                    format!(
65                        "shared include `{relative}` contributes [{section}] without shared.before.{section} or shared.append.{section} = true"
66                    ),
67                ));
68            }
69        }
70    }
71
72    violations
73}