Skip to main content

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

1#[cfg(test)]
2use googletest::prelude::*;
3use taplo::formatter::Options;
4
5use super::MANIFEST_PREFIX;
6use crate::{Example, Fix, TomlCtx, Violation, violation};
7
8const COLUMN_WIDTH: usize = 120;
9
10#[rustfmt::skip]
11const EXAMPLES: &[Example] = &[
12    Example {
13        label: "canonical manifest formatting",
14        code: "parts = [\"parts/core.toml\"]\n\n[spec]\nid = 1\n",
15        pass: true,
16    },
17    Example {
18        label: "missing spaces and trailing newline",
19        code: "parts=[\"parts/core.toml\"]\n\n[spec]\nid=1",
20        pass: false,
21    },
22];
23
24crate::toml_rule!(
25    toml_manifest_format,
26    "Require engine manifests to match the canonical Taplo format.",
27    "One formatter eliminates whitespace and key-order churn while preserving table declaration and array order relied upon by code generation.",
28    Low,
29    fix_toml_manifest_format,
30);
31
32fn options() -> Options {
33    Options {
34        align_comments: false,
35        align_single_comments: false,
36        allowed_blank_lines: 1,
37        column_width: COLUMN_WIDTH,
38        reorder_arrays: false,
39        reorder_inline_tables: true,
40        reorder_keys: false,
41        ..Options::default()
42    }
43}
44
45fn check_toml_manifest_format(ctx: &TomlCtx<'_>) -> Vec<Violation> {
46    if !ctx.file.rel.starts_with(MANIFEST_PREFIX) || !ctx.parse.errors.is_empty() {
47        return Vec::new();
48    }
49
50    let formatted = taplo::formatter::format(ctx.file.contents, options());
51
52    if formatted == ctx.file.contents {
53        Vec::new()
54    } else {
55        vec![violation(
56            ctx.file.rel,
57            first_changed_line(ctx.file.contents, &formatted),
58            "manifest does not match canonical Taplo formatting",
59        )]
60    }
61}
62
63#[expect(
64    clippy::unnecessary_wraps,
65    reason = "rule fix callbacks share an optional-fix signature"
66)]
67fn fix_toml_manifest_format(ctx: &TomlCtx<'_>, _: &Violation) -> Option<Fix> {
68    Some(Fix {
69        start_line: 1,
70        end_line: ctx.file.lines.len().max(1),
71        replacement: taplo::formatter::format(ctx.file.contents, options()),
72    })
73}
74
75fn first_changed_line(left: &str, right: &str) -> usize {
76    let changed_line = left
77        .lines()
78        .zip(right.lines())
79        .position(|(left, right)| left != right);
80
81    changed_line.map_or_else(
82        || left.lines().count().min(right.lines().count()) + 1,
83        |line| line + 1,
84    )
85}
86
87crate::tidy_toml_test!(check_toml_manifest_format, {
88    crate::example_tests!(EXAMPLES, check_toml_manifest_format);
89
90    #[gtest]
91    fn formatter_preserves_declaration_and_array_order() -> Result<()> {
92        let options = options();
93        verify_false!(options.reorder_keys)?;
94        verify_false!(options.reorder_arrays)?;
95        verify_true!(options.reorder_inline_tables)?;
96
97        Ok(())
98    }
99});