wowlab_tidy/languages/toml/rules/manifest/
formatting.rs1#[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});