@@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
1515use serde_json:: json;
1616use std:: path:: PathBuf ;
1717
18- use crate :: analyzer:: dclint:: { lint , lint_file , DclintConfig , LintResult , Severity , RuleCategory } ;
18+ use crate :: analyzer:: dclint:: { DclintConfig , LintResult , RuleCategory , Severity , lint , lint_file } ;
1919
2020/// Arguments for the dclint tool
2121#[ derive( Debug , Deserialize ) ]
@@ -83,21 +83,43 @@ impl DclintTool {
8383 /// Get actionable fix recommendation for a rule
8484 fn get_fix_recommendation ( code : & str ) -> & ' static str {
8585 match code {
86- "DCL001" => "Remove either the 'build' or 'image' field, or add 'pull_policy' if both are intentional." ,
87- "DCL002" => "Use unique container names for each service, or remove explicit container_name to use auto-generated names." ,
88- "DCL003" => "Use different host ports for each service, or bind to different interfaces (e.g., 127.0.0.1:8080:80)." ,
86+ "DCL001" => {
87+ "Remove either the 'build' or 'image' field, or add 'pull_policy' if both are intentional."
88+ }
89+ "DCL002" => {
90+ "Use unique container names for each service, or remove explicit container_name to use auto-generated names."
91+ }
92+ "DCL003" => {
93+ "Use different host ports for each service, or bind to different interfaces (e.g., 127.0.0.1:8080:80)."
94+ }
8995 "DCL004" => "Remove quotes from volume paths. YAML doesn't require quotes for paths." ,
90- "DCL005" => "Add explicit interface binding, e.g., '127.0.0.1:8080:80' instead of '8080:80' for local-only access." ,
91- "DCL006" => "Remove the 'version' field. Docker Compose now infers the version automatically." ,
96+ "DCL005" => {
97+ "Add explicit interface binding, e.g., '127.0.0.1:8080:80' instead of '8080:80' for local-only access."
98+ }
99+ "DCL006" => {
100+ "Remove the 'version' field. Docker Compose now infers the version automatically."
101+ }
92102 "DCL007" => "Add 'name: myproject' at the top level for explicit project naming." ,
93- "DCL008" => "Quote port mappings to prevent YAML parsing issues, e.g., \" 8080:80\" instead of 8080:80." ,
94- "DCL009" => "Use lowercase container names with only letters, numbers, hyphens, and underscores." ,
95- "DCL010" => "Sort dependencies alphabetically for better readability and easier merges." ,
96- "DCL011" => "Use explicit version tags (e.g., nginx:1.25) instead of implicit latest or untagged images." ,
97- "DCL012" => "Reorder service keys to follow convention: image, build, container_name, ports, volumes, environment, etc." ,
103+ "DCL008" => {
104+ "Quote port mappings to prevent YAML parsing issues, e.g., \" 8080:80\" instead of 8080:80."
105+ }
106+ "DCL009" => {
107+ "Use lowercase container names with only letters, numbers, hyphens, and underscores."
108+ }
109+ "DCL010" => {
110+ "Sort dependencies alphabetically for better readability and easier merges."
111+ }
112+ "DCL011" => {
113+ "Use explicit version tags (e.g., nginx:1.25) instead of implicit latest or untagged images."
114+ }
115+ "DCL012" => {
116+ "Reorder service keys to follow convention: image, build, container_name, ports, volumes, environment, etc."
117+ }
98118 "DCL013" => "Sort port mappings alphabetically/numerically for consistency." ,
99119 "DCL014" => "Sort services alphabetically for better navigation and easier merges." ,
100- "DCL015" => "Reorder top-level keys: name, services, networks, volumes, configs, secrets." ,
120+ "DCL015" => {
121+ "Reorder top-level keys: name, services, networks, volumes, configs, secrets."
122+ }
101123 _ => "Review the rule documentation for specific guidance." ,
102124 }
103125 }
@@ -123,7 +145,10 @@ impl DclintTool {
123145 "DCL015" => "top-level-properties-order-rule" ,
124146 _ => return String :: new ( ) ,
125147 } ;
126- format ! ( "https://github.com/zavoloklom/docker-compose-linter/blob/main/docs/rules/{}.md" , rule_name)
148+ format ! (
149+ "https://github.com/zavoloklom/docker-compose-linter/blob/main/docs/rules/{}.md" ,
150+ rule_name
151+ )
127152 } else {
128153 String :: new ( )
129154 }
@@ -132,41 +157,54 @@ impl DclintTool {
132157 /// Format result optimized for agent decision-making
133158 fn format_result ( result : & LintResult , filename : & str ) -> String {
134159 // Categorize and enrich failures
135- let enriched_failures: Vec < serde_json:: Value > = result. failures . iter ( ) . map ( |f| {
136- let code = f. code . as_str ( ) ;
137- let priority = Self :: get_priority ( f. severity , f. category ) ;
138-
139- json ! ( {
140- "code" : code,
141- "ruleName" : f. rule_name,
142- "severity" : f. severity. as_str( ) ,
143- "priority" : priority,
144- "category" : f. category. as_str( ) ,
145- "message" : f. message,
146- "line" : f. line,
147- "column" : f. column,
148- "fixable" : f. fixable,
149- "fix" : Self :: get_fix_recommendation( code) ,
150- "docs" : Self :: get_rule_url( code) ,
160+ let enriched_failures: Vec < serde_json:: Value > = result
161+ . failures
162+ . iter ( )
163+ . map ( |f| {
164+ let code = f. code . as_str ( ) ;
165+ let priority = Self :: get_priority ( f. severity , f. category ) ;
166+
167+ json ! ( {
168+ "code" : code,
169+ "ruleName" : f. rule_name,
170+ "severity" : f. severity. as_str( ) ,
171+ "priority" : priority,
172+ "category" : f. category. as_str( ) ,
173+ "message" : f. message,
174+ "line" : f. line,
175+ "column" : f. column,
176+ "fixable" : f. fixable,
177+ "fix" : Self :: get_fix_recommendation( code) ,
178+ "docs" : Self :: get_rule_url( code) ,
179+ } )
151180 } )
152- } ) . collect ( ) ;
181+ . collect ( ) ;
153182
154183 // Group by priority for agent decision ordering
155- let critical: Vec < _ > = enriched_failures. iter ( )
184+ let critical: Vec < _ > = enriched_failures
185+ . iter ( )
156186 . filter ( |f| f[ "priority" ] == "critical" )
157- . cloned ( ) . collect ( ) ;
158- let high: Vec < _ > = enriched_failures. iter ( )
187+ . cloned ( )
188+ . collect ( ) ;
189+ let high: Vec < _ > = enriched_failures
190+ . iter ( )
159191 . filter ( |f| f[ "priority" ] == "high" )
160- . cloned ( ) . collect ( ) ;
161- let medium: Vec < _ > = enriched_failures. iter ( )
192+ . cloned ( )
193+ . collect ( ) ;
194+ let medium: Vec < _ > = enriched_failures
195+ . iter ( )
162196 . filter ( |f| f[ "priority" ] == "medium" )
163- . cloned ( ) . collect ( ) ;
164- let low: Vec < _ > = enriched_failures. iter ( )
197+ . cloned ( )
198+ . collect ( ) ;
199+ let low: Vec < _ > = enriched_failures
200+ . iter ( )
165201 . filter ( |f| f[ "priority" ] == "low" )
166- . cloned ( ) . collect ( ) ;
202+ . cloned ( )
203+ . collect ( ) ;
167204
168205 // Group by category for thematic fixes
169- let mut by_category: std:: collections:: HashMap < & str , Vec < _ > > = std:: collections:: HashMap :: new ( ) ;
206+ let mut by_category: std:: collections:: HashMap < & str , Vec < _ > > =
207+ std:: collections:: HashMap :: new ( ) ;
170208 for f in & enriched_failures {
171209 let cat = f[ "category" ] . as_str ( ) . unwrap_or ( "other" ) ;
172210 by_category. entry ( cat) . or_default ( ) . push ( f. clone ( ) ) ;
@@ -188,7 +226,10 @@ impl DclintTool {
188226 } ;
189227
190228 // Count fixable issues
191- let fixable_count = enriched_failures. iter ( ) . filter ( |f| f[ "fixable" ] == true ) . count ( ) ;
229+ let fixable_count = enriched_failures
230+ . iter ( )
231+ . filter ( |f| f[ "fixable" ] == true )
232+ . count ( ) ;
192233
193234 // Build agent-optimized output
194235 let mut output = json ! ( {
@@ -222,14 +263,18 @@ impl DclintTool {
222263
223264 // Add quick fixes summary for agent
224265 if !enriched_failures. is_empty ( ) {
225- let quick_fixes: Vec < String > = enriched_failures. iter ( )
266+ let quick_fixes: Vec < String > = enriched_failures
267+ . iter ( )
226268 . filter ( |f| f[ "priority" ] == "critical" || f[ "priority" ] == "high" )
227269 . take ( 5 )
228- . map ( |f| format ! ( "Line {}: {} - {}" ,
229- f[ "line" ] ,
230- f[ "code" ] . as_str( ) . unwrap_or( "" ) ,
231- f[ "fix" ] . as_str( ) . unwrap_or( "" )
232- ) )
270+ . map ( |f| {
271+ format ! (
272+ "Line {}: {} - {}" ,
273+ f[ "line" ] ,
274+ f[ "code" ] . as_str( ) . unwrap_or( "" ) ,
275+ f[ "fix" ] . as_str( ) . unwrap_or( "" )
276+ )
277+ } )
233278 . collect ( ) ;
234279
235280 if !quick_fixes. is_empty ( ) {
@@ -364,12 +409,15 @@ mod tests {
364409 let tool = DclintTool :: new ( temp_dir ( ) ) ;
365410 let args = DclintArgs {
366411 compose_file : None ,
367- content : Some ( r#"
412+ content : Some (
413+ r#"
368414services:
369415 web:
370416 build: .
371417 image: nginx:latest
372- "# . to_string ( ) ) ,
418+ "#
419+ . to_string ( ) ,
420+ ) ,
373421 ignore : vec ! [ ] ,
374422 threshold : None ,
375423 fix : false ,
@@ -392,12 +440,15 @@ services:
392440 let tool = DclintTool :: new ( temp_dir ( ) ) ;
393441 let args = DclintArgs {
394442 compose_file : None ,
395- content : Some ( r#"
443+ content : Some (
444+ r#"
396445version: "3.8"
397446services:
398447 web:
399448 image: nginx:latest
400- "# . to_string ( ) ) ,
449+ "#
450+ . to_string ( ) ,
451+ ) ,
401452 ignore : vec ! [ "DCL006" . to_string( ) , "DCL011" . to_string( ) ] ,
402453 threshold : None ,
403454 fix : false ,
@@ -424,14 +475,18 @@ services:
424475 let temp = temp_dir ( ) . join ( "dclint_test" ) ;
425476 fs:: create_dir_all ( & temp) . unwrap ( ) ;
426477 let compose_file = temp. join ( "docker-compose.yml" ) ;
427- fs:: write ( & compose_file, r#"
478+ fs:: write (
479+ & compose_file,
480+ r#"
428481name: myproject
429482services:
430483 web:
431484 image: nginx:1.25
432485 ports:
433486 - "8080:80"
434- "# ) . unwrap ( ) ;
487+ "# ,
488+ )
489+ . unwrap ( ) ;
435490
436491 let tool = DclintTool :: new ( temp. clone ( ) ) ;
437492 let args = DclintArgs {
@@ -481,7 +536,17 @@ services:
481536 assert ! ( parsed[ "success" ] . as_bool( ) . unwrap_or( false ) ) ;
482537 assert ! ( parsed[ "decision_context" ] . is_string( ) ) ;
483538 // Should not have critical or high priority issues
484- assert_eq ! ( parsed[ "summary" ] [ "by_priority" ] [ "critical" ] . as_u64( ) . unwrap_or( 99 ) , 0 ) ;
485- assert_eq ! ( parsed[ "summary" ] [ "by_priority" ] [ "high" ] . as_u64( ) . unwrap_or( 99 ) , 0 ) ;
539+ assert_eq ! (
540+ parsed[ "summary" ] [ "by_priority" ] [ "critical" ]
541+ . as_u64( )
542+ . unwrap_or( 99 ) ,
543+ 0
544+ ) ;
545+ assert_eq ! (
546+ parsed[ "summary" ] [ "by_priority" ] [ "high" ]
547+ . as_u64( )
548+ . unwrap_or( 99 ) ,
549+ 0
550+ ) ;
486551 }
487552}
0 commit comments