@@ -524,6 +524,86 @@ fn check_expression_references(
524524 }
525525}
526526
527+ /// Helper function to detect cycles in message references
528+ fn detect_cycles ( resource : & FluentResource ) -> Vec < ValidationError > {
529+ use fluent_syntax:: ast;
530+ use std:: collections:: { HashMap , HashSet } ;
531+
532+ let mut errors = Vec :: new ( ) ;
533+
534+ // Build a map of message IDs to their referenced IDs
535+ let mut message_refs: HashMap < String , Vec < String > > = HashMap :: new ( ) ;
536+
537+ for entry in resource. entries ( ) {
538+ match entry {
539+ ast:: Entry :: Message ( msg) => {
540+ let msg_id = msg. id . name . to_string ( ) ;
541+ let mut refs = Vec :: new ( ) ;
542+ collect_references ( & msg. value , & mut refs) ;
543+ for attr in & msg. attributes {
544+ collect_references ( & Some ( attr. value . clone ( ) ) , & mut refs) ;
545+ }
546+ message_refs. insert ( msg_id, refs) ;
547+ }
548+ ast:: Entry :: Term ( term) => {
549+ let term_id = format ! ( "-{}" , term. id. name) ;
550+ let mut refs = Vec :: new ( ) ;
551+ collect_references ( & Some ( term. value . clone ( ) ) , & mut refs) ;
552+ for attr in & term. attributes {
553+ collect_references ( & Some ( attr. value . clone ( ) ) , & mut refs) ;
554+ }
555+ message_refs. insert ( term_id, refs) ;
556+ }
557+ _ => { }
558+ }
559+ }
560+
561+ // Check each message for cycles using DFS
562+ for ( msg_id, _) in & message_refs {
563+ let mut visited = HashSet :: new ( ) ;
564+ let mut path = Vec :: new ( ) ;
565+ if has_cycle ( msg_id, & message_refs, & mut visited, & mut path) {
566+ errors. push ( ValidationError {
567+ error_type : "CyclicReference" . to_string ( ) ,
568+ message : format ! ( "Cyclic reference detected: {}" , path. join( " -> " ) ) ,
569+ message_id : Some ( msg_id. clone ( ) ) ,
570+ reference : None ,
571+ } ) ;
572+ }
573+ }
574+
575+ errors
576+ }
577+
578+ fn has_cycle (
579+ msg_id : & str ,
580+ message_refs : & HashMap < String , Vec < String > > ,
581+ visited : & mut HashSet < String > ,
582+ path : & mut Vec < String > ,
583+ ) -> bool {
584+ if visited. contains ( msg_id) {
585+ // Found a cycle - add the current message to show where cycle completes
586+ path. push ( msg_id. to_string ( ) ) ;
587+ return true ;
588+ }
589+
590+ visited. insert ( msg_id. to_string ( ) ) ;
591+ path. push ( msg_id. to_string ( ) ) ;
592+
593+ // Check all referenced messages
594+ if let Some ( refs) = message_refs. get ( msg_id) {
595+ for ref_id in refs {
596+ if has_cycle ( ref_id, message_refs, visited, path) {
597+ return true ;
598+ }
599+ }
600+ }
601+
602+ path. pop ( ) ;
603+ visited. remove ( msg_id) ;
604+ false
605+ }
606+
527607#[ pymodule]
528608mod rustfluent {
529609 use super :: * ;
0 commit comments