syncable_cli/analyzer/dclint/rules/
dcl015.rs1use crate::analyzer::dclint::rules::{FixableRule, LintContext, Rule, make_failure};
6use crate::analyzer::dclint::types::{CheckFailure, RuleCategory, Severity};
7
8const CODE: &str = "DCL015";
9const NAME: &str = "top-level-properties-order";
10const DESCRIPTION: &str = "Top-level properties should follow a standard ordering convention.";
11const URL: &str = "https://github.com/zavoloklom/docker-compose-linter/blob/main/docs/rules/top-level-properties-order-rule.md";
12
13const KEY_ORDER: &[&str] = &[
15 "version", "name", "services", "networks", "volumes", "configs", "secrets",
17];
18
19pub fn rule() -> impl Rule {
20 FixableRule::new(
21 CODE,
22 NAME,
23 Severity::Style,
24 RuleCategory::Style,
25 DESCRIPTION,
26 URL,
27 check,
28 fix,
29 )
30}
31
32fn get_key_order(key: &str) -> usize {
33 KEY_ORDER
34 .iter()
35 .position(|&k| k == key)
36 .unwrap_or(KEY_ORDER.len())
37}
38
39fn check(ctx: &LintContext) -> Vec<CheckFailure> {
40 let mut failures = Vec::new();
41
42 if ctx.compose.top_level_keys.len() > 1 {
43 let mut sorted_keys = ctx.compose.top_level_keys.clone();
44 sorted_keys.sort_by_key(|k| get_key_order(k));
45
46 if ctx.compose.top_level_keys != sorted_keys {
47 let message = format!(
48 "Top-level properties are not in standard order. Expected: [{}], got: [{}].",
49 sorted_keys.join(", "),
50 ctx.compose.top_level_keys.join(", ")
51 );
52
53 failures.push(
54 make_failure(
55 &CODE.into(),
56 NAME,
57 Severity::Style,
58 RuleCategory::Style,
59 message,
60 1,
61 1,
62 true,
63 )
64 .with_data("expected", sorted_keys.join(", "))
65 .with_data("actual", ctx.compose.top_level_keys.join(", ")),
66 );
67 }
68 }
69
70 failures
71}
72
73fn fix(_source: &str) -> Option<String> {
74 None
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::analyzer::dclint::parser::parse_compose;
82
83 fn check_yaml(yaml: &str) -> Vec<CheckFailure> {
84 let compose = parse_compose(yaml).unwrap();
85 let ctx = LintContext::new(&compose, yaml, "docker-compose.yml");
86 check(&ctx)
87 }
88
89 #[test]
90 fn test_no_violation_correct_order() {
91 let yaml = r#"
92name: myproject
93services:
94 web:
95 image: nginx
96networks:
97 default:
98volumes:
99 data:
100"#;
101 assert!(check_yaml(yaml).is_empty());
102 }
103
104 #[test]
105 fn test_violation_wrong_order() {
106 let yaml = r#"
107services:
108 web:
109 image: nginx
110name: myproject
111"#;
112 let failures = check_yaml(yaml);
113 assert_eq!(failures.len(), 1);
114 assert!(failures[0].message.contains("standard order"));
115 }
116
117 #[test]
118 fn test_no_violation_single_key() {
119 let yaml = r#"
120services:
121 web:
122 image: nginx
123"#;
124 assert!(check_yaml(yaml).is_empty());
125 }
126
127 #[test]
128 fn test_violation_volumes_before_services() {
129 let yaml = r#"
130volumes:
131 data:
132services:
133 web:
134 image: nginx
135"#;
136 let failures = check_yaml(yaml);
137 assert_eq!(failures.len(), 1);
138 }
139}