sea_core/formatter/
printer.rs

1//! Core formatter implementation.
2
3use crate::formatter::config::FormatConfig;
4use crate::parser::ast::{
5    parse_source, Ast, AstNode, FileMetadata, ImportDecl, ImportItem, ImportSpecifier,
6};
7use crate::policy::Expression;
8use std::fmt;
9
10/// Error type for formatting operations.
11#[derive(Debug)]
12pub enum FormatError {
13    /// Failed to parse the source code.
14    ParseError(String),
15    /// Internal formatting error.
16    InternalError(String),
17}
18
19impl fmt::Display for FormatError {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            FormatError::ParseError(msg) => write!(f, "Parse error: {}", msg),
23            FormatError::InternalError(msg) => write!(f, "Internal error: {}", msg),
24        }
25    }
26}
27
28impl std::error::Error for FormatError {}
29
30/// Format SEA source code.
31///
32/// Parses the source, then pretty-prints it with consistent formatting.
33///
34/// # Arguments
35///
36/// * `source` - The SEA source code to format
37/// * `config` - Formatting configuration options
38///
39/// # Returns
40///
41/// The formatted source code, or an error if parsing fails.
42///
43/// # Example
44///
45/// ```rust,ignore
46/// use sea_core::formatter::{format, FormatConfig};
47///
48/// let source = r#"Entity   "Foo"  in    bar"#;
49/// let formatted = format(source, FormatConfig::default()).unwrap();
50/// assert_eq!(formatted, "Entity \"Foo\" in bar\n");
51/// ```
52pub fn format(source: &str, config: FormatConfig) -> Result<String, FormatError> {
53    // Check if we should preserve comments
54    if config.preserve_comments {
55        format_preserving_comments(source, config)
56    } else {
57        format_without_comments(source, config)
58    }
59}
60
61/// Format SEA source code without preserving comments.
62fn format_without_comments(source: &str, config: FormatConfig) -> Result<String, FormatError> {
63    let ast = parse_source(source).map_err(|e| FormatError::ParseError(e.to_string()))?;
64
65    let mut formatter = Formatter::new(config, None);
66    formatter.format_ast(&ast);
67
68    Ok(formatter.output)
69}
70
71/// Format SEA source code while preserving comments.
72///
73/// This function:
74/// 1. Extracts all comments from the source with their positions
75/// 2. Parses and formats the code
76/// 3. Re-inserts comments at appropriate positions
77pub fn format_preserving_comments(
78    source: &str,
79    config: FormatConfig,
80) -> Result<String, FormatError> {
81    use crate::formatter::comments::CommentedSource;
82
83    let commented = CommentedSource::new(source);
84    let ast = parse_source(source).map_err(|e| FormatError::ParseError(e.to_string()))?;
85
86    let mut formatter = Formatter::new(config, Some(commented));
87    formatter.format_ast(&ast);
88
89    Ok(formatter.output)
90}
91
92/// Internal formatter state.
93struct Formatter {
94    config: FormatConfig,
95    output: String,
96    indent_level: usize,
97    /// Optional source with comments for preservation
98    commented_source: Option<crate::formatter::comments::CommentedSource>,
99}
100
101impl Formatter {
102    fn new(
103        config: FormatConfig,
104        commented_source: Option<crate::formatter::comments::CommentedSource>,
105    ) -> Self {
106        Self {
107            config,
108            output: String::new(),
109            indent_level: 0,
110            commented_source,
111        }
112    }
113
114    /// Format the entire AST.
115    fn format_ast(&mut self, ast: &Ast) {
116        // Output file header comments if we have them
117        // Clone to avoid borrow conflict with self.write()
118        let header_comments: Vec<_> = self
119            .commented_source
120            .as_ref()
121            .map(|cs| cs.file_header_comments.clone())
122            .unwrap_or_default();
123
124        for comment in &header_comments {
125            self.write("// ");
126            self.write(&comment.text);
127            self.newline();
128        }
129        if !header_comments.is_empty() {
130            self.newline();
131        }
132
133        // Format file header
134        self.format_file_metadata(&ast.metadata);
135
136        // Format declarations
137        for (i, decl) in ast.declarations.iter().enumerate() {
138            if i > 0 || !ast.metadata.imports.is_empty() || ast.metadata.namespace.is_some() {
139                self.newline();
140            }
141            self.format_declaration(&decl.node);
142        }
143
144        // Ensure trailing newline
145        if self.config.trailing_newline && !self.output.ends_with('\n') {
146            self.output.push('\n');
147        }
148    }
149
150    /// Format file-level metadata (annotations and imports).
151    fn format_file_metadata(&mut self, meta: &FileMetadata) {
152        // Annotations in canonical order
153        if let Some(ref ns) = meta.namespace {
154            self.write("@namespace ");
155            self.write_string_literal(ns);
156            self.newline();
157        }
158        if let Some(ref version) = meta.version {
159            self.write("@version ");
160            self.write_string_literal(version);
161            self.newline();
162        }
163        if let Some(ref owner) = meta.owner {
164            self.write("@owner ");
165            self.write_string_literal(owner);
166            self.newline();
167        }
168        if let Some(ref profile) = meta.profile {
169            self.write("@profile ");
170            self.write_string_literal(profile);
171            self.newline();
172        }
173
174        // Blank line after annotations if any and before imports
175        if (meta.namespace.is_some()
176            || meta.version.is_some()
177            || meta.owner.is_some()
178            || meta.profile.is_some())
179            && !meta.imports.is_empty()
180        {
181            self.newline();
182        }
183
184        // Imports (sorted if configured)
185        let mut imports = meta.imports.clone();
186        if self.config.sort_imports {
187            imports.sort_by(|a, b| a.from_module.cmp(&b.from_module));
188        }
189
190        for import in &imports {
191            self.format_import(import);
192            self.newline();
193        }
194    }
195
196    /// Format an import declaration.
197    fn format_import(&mut self, import: &ImportDecl) {
198        self.write("import ");
199        match &import.specifier {
200            ImportSpecifier::Named(items) => {
201                self.write("{ ");
202                for (i, item) in items.iter().enumerate() {
203                    if i > 0 {
204                        self.write(", ");
205                    }
206                    self.format_import_item(item);
207                }
208                self.write(" }");
209            }
210            ImportSpecifier::Wildcard(alias) => {
211                self.write("* as ");
212                self.write(alias);
213            }
214        }
215        self.write(" from ");
216        self.write_string_literal(&import.from_module);
217    }
218
219    /// Format an import item.
220    fn format_import_item(&mut self, item: &ImportItem) {
221        self.write(&item.name);
222        if let Some(ref alias) = item.alias {
223            self.write(" as ");
224            self.write(alias);
225        }
226    }
227
228    /// Format a declaration node.
229    fn format_declaration(&mut self, node: &AstNode) {
230        match node {
231            AstNode::Export(inner) => {
232                self.write("export ");
233                self.format_declaration(&inner.node);
234            }
235            AstNode::Entity {
236                name,
237                version,
238                annotations,
239                domain,
240            } => {
241                self.write("Entity ");
242                self.write_string_literal(name);
243                if let Some(v) = version {
244                    self.write(" v");
245                    self.write(v);
246                }
247                // Format annotations
248                if let Some(replaces) = annotations.get("replaces") {
249                    if let Some(s) = replaces.as_str() {
250                        self.newline();
251                        self.indent();
252                        self.write_indent();
253                        self.write("@replaces ");
254                        // Parse the replaces value which may include version
255                        if s.contains(" v") {
256                            let parts: Vec<&str> = s.splitn(2, " v").collect();
257                            self.write_string_literal(parts[0]);
258                            if parts.len() > 1 {
259                                self.write(" v");
260                                self.write(parts[1]);
261                            }
262                        } else {
263                            self.write_string_literal(s);
264                        }
265                        self.dedent();
266                    }
267                }
268                if let Some(changes) = annotations.get("changes") {
269                    if let Some(arr) = changes.as_array() {
270                        self.newline();
271                        self.indent();
272                        self.write_indent();
273                        self.write("@changes [");
274                        for (i, change) in arr.iter().enumerate() {
275                            if i > 0 {
276                                self.write(", ");
277                            }
278                            if let Some(s) = change.as_str() {
279                                self.write_string_literal(s);
280                            }
281                        }
282                        self.write("]");
283                        self.dedent();
284                    }
285                }
286                if let Some(d) = domain {
287                    self.write(" in ");
288                    self.write(d);
289                }
290                self.newline();
291            }
292            AstNode::Resource {
293                name,
294                annotations,
295                unit_name,
296                domain,
297            } => {
298                self.write("Resource ");
299                self.write_string_literal(name);
300                // Format annotations
301                if let Some(replaces) = annotations.get("replaces") {
302                    if let Some(s) = replaces.as_str() {
303                        self.newline();
304                        self.indent();
305                        self.write_indent();
306                        self.write("@replaces ");
307                        // Parse the replaces value which may include version
308                        if s.contains(" v") {
309                            let parts: Vec<&str> = s.splitn(2, " v").collect();
310                            self.write_string_literal(parts[0]);
311                            if parts.len() > 1 {
312                                self.write(" v");
313                                self.write(parts[1]);
314                            }
315                        } else {
316                            self.write_string_literal(s);
317                        }
318                        self.dedent();
319                    }
320                }
321                if let Some(changes) = annotations.get("changes") {
322                    if let Some(arr) = changes.as_array() {
323                        self.newline();
324                        self.indent();
325                        self.write_indent();
326                        self.write("@changes [");
327                        for (i, change) in arr.iter().enumerate() {
328                            if i > 0 {
329                                self.write(", ");
330                            }
331                            if let Some(s) = change.as_str() {
332                                self.write_string_literal(s);
333                            }
334                        }
335                        self.write("]");
336                        self.dedent();
337                    }
338                }
339                if let Some(u) = unit_name {
340                    self.write(" ");
341                    self.write(u);
342                }
343                if let Some(d) = domain {
344                    self.write(" in ");
345                    self.write(d);
346                }
347                self.newline();
348            }
349            AstNode::Flow {
350                resource_name,
351                annotations,
352                from_entity,
353                to_entity,
354                quantity,
355            } => {
356                self.write("Flow ");
357                self.write_string_literal(resource_name);
358                // Format annotations
359                if let Some(replaces) = annotations.get("replaces") {
360                    if let Some(s) = replaces.as_str() {
361                        self.newline();
362                        self.indent();
363                        self.write_indent();
364                        self.write("@replaces ");
365                        if s.contains(" v") {
366                            let parts: Vec<&str> = s.splitn(2, " v").collect();
367                            self.write_string_literal(parts[0]);
368                            if parts.len() > 1 {
369                                self.write(" v");
370                                self.write(parts[1]);
371                            }
372                        } else {
373                            self.write_string_literal(s);
374                        }
375                        self.dedent();
376                    }
377                }
378                if let Some(changes) = annotations.get("changes") {
379                    if let Some(arr) = changes.as_array() {
380                        self.newline();
381                        self.indent();
382                        self.write_indent();
383                        self.write("@changes [");
384                        for (i, change) in arr.iter().enumerate() {
385                            if i > 0 {
386                                self.write(", ");
387                            }
388                            if let Some(s) = change.as_str() {
389                                self.write_string_literal(s);
390                            }
391                        }
392                        self.write("]");
393                        self.dedent();
394                    }
395                }
396                self.write(" from ");
397                self.write_string_literal(from_entity);
398                self.write(" to ");
399                self.write_string_literal(to_entity);
400                if let Some(q) = quantity {
401                    self.write(" quantity ");
402                    self.write(&q.to_string());
403                }
404                self.newline();
405            }
406            AstNode::Pattern { name, regex } => {
407                self.write("Pattern ");
408                self.write_string_literal(name);
409                self.write(" matches ");
410                self.write_string_literal(regex);
411                self.newline();
412            }
413            AstNode::Role { name, domain } => {
414                self.write("Role ");
415                self.write_string_literal(name);
416                if let Some(d) = domain {
417                    self.write(" in ");
418                    self.write(d);
419                }
420                self.newline();
421            }
422            AstNode::Relation {
423                name,
424                subject_role,
425                predicate,
426                object_role,
427                via_flow,
428            } => {
429                self.write("Relation ");
430                self.write_string_literal(name);
431                self.newline();
432                self.indent();
433                self.write_indent();
434                self.write("subject: ");
435                self.write_string_literal(subject_role);
436                self.newline();
437                self.write_indent();
438                self.write("predicate: ");
439                self.write_string_literal(predicate);
440                self.newline();
441                self.write_indent();
442                self.write("object: ");
443                self.write_string_literal(object_role);
444                if let Some(flow) = via_flow {
445                    self.newline();
446                    self.write_indent();
447                    self.write("via: flow ");
448                    self.write_string_literal(flow);
449                }
450                self.dedent();
451                self.newline();
452            }
453            AstNode::Dimension { name } => {
454                self.write("Dimension ");
455                self.write_string_literal(name);
456                self.newline();
457            }
458            AstNode::UnitDeclaration {
459                symbol,
460                dimension,
461                factor,
462                base_unit,
463            } => {
464                self.write("Unit ");
465                self.write_string_literal(symbol);
466                self.write(" of ");
467                self.write_string_literal(dimension);
468                self.write(" factor ");
469                self.write(&factor.to_string());
470                self.write(" base ");
471                self.write_string_literal(base_unit);
472                self.newline();
473            }
474            AstNode::Policy {
475                name,
476                version,
477                metadata,
478                expression,
479            } => {
480                self.write("Policy ");
481                self.write(name);
482                if let (Some(kind), Some(modality), Some(priority)) =
483                    (&metadata.kind, &metadata.modality, metadata.priority)
484                {
485                    self.write(" per ");
486                    self.write(&format!("{}", kind));
487                    self.write(" ");
488                    self.write(&format!("{}", modality));
489                    self.write(" priority ");
490                    self.write(&priority.to_string());
491                }
492                // Policy annotations
493                if let Some(ref rationale) = metadata.rationale {
494                    self.newline();
495                    self.indent();
496                    self.write_indent();
497                    self.write("@rationale ");
498                    self.write_string_literal(rationale);
499                    self.dedent();
500                }
501                if !metadata.tags.is_empty() {
502                    self.newline();
503                    self.indent();
504                    self.write_indent();
505                    self.write("@tags [");
506                    for (i, tag) in metadata.tags.iter().enumerate() {
507                        if i > 0 {
508                            self.write(", ");
509                        }
510                        self.write_string_literal(tag);
511                    }
512                    self.write("]");
513                    self.dedent();
514                }
515                if let Some(v) = version {
516                    self.newline();
517                    self.indent();
518                    self.write_indent();
519                    self.write("v");
520                    self.write(v);
521                    self.dedent();
522                }
523                self.newline();
524                self.indent();
525                self.write_indent();
526                self.write("as: ");
527                self.format_expression(expression);
528                self.dedent();
529                self.newline();
530            }
531            AstNode::Instance {
532                name,
533                entity_type,
534                fields,
535            } => {
536                self.write("Instance ");
537                self.write(name);
538                self.write(" of ");
539                self.write_string_literal(entity_type);
540                if !fields.is_empty() {
541                    self.write(" {");
542                    self.newline();
543                    self.indent();
544                    let mut sorted_fields: Vec<_> = fields.iter().collect();
545                    sorted_fields.sort_by_key(|(k, _)| *k);
546                    for (i, (field_name, expr)) in sorted_fields.iter().enumerate() {
547                        self.write_indent();
548                        self.write(field_name);
549                        self.write(": ");
550                        self.format_expression(expr);
551                        if i < sorted_fields.len() - 1 {
552                            self.write(",");
553                        }
554                        self.newline();
555                    }
556                    self.dedent();
557                    self.write_indent();
558                    self.write("}");
559                }
560                self.newline();
561            }
562            AstNode::ConceptChange {
563                name,
564                from_version,
565                to_version,
566                migration_policy,
567                breaking_change,
568            } => {
569                self.write("ConceptChange ");
570                self.write_string_literal(name);
571                self.newline();
572                self.indent();
573                self.write_indent();
574                self.write("@from_version v");
575                self.write(from_version);
576                self.newline();
577                self.write_indent();
578                self.write("@to_version v");
579                self.write(to_version);
580                self.newline();
581                self.write_indent();
582                self.write("@migration_policy ");
583                self.write(migration_policy);
584                self.newline();
585                self.write_indent();
586                self.write("@breaking_change ");
587                self.write(if *breaking_change { "true" } else { "false" });
588                self.dedent();
589                self.newline();
590            }
591            AstNode::Metric {
592                name,
593                expression,
594                metadata,
595            } => {
596                self.write("Metric ");
597                self.write_string_literal(name);
598                self.write(" as:");
599                self.newline();
600                self.indent();
601                self.write_indent();
602                self.format_expression(expression);
603                // Metric annotations
604                if let Some(ref interval) = metadata.refresh_interval {
605                    self.newline();
606                    self.write_indent();
607                    self.write("@refresh_interval ");
608                    self.write(&interval.num_seconds().to_string());
609                    self.write(" \"seconds\"");
610                }
611                if let Some(ref unit) = metadata.unit {
612                    self.newline();
613                    self.write_indent();
614                    self.write("@unit ");
615                    self.write_string_literal(unit);
616                }
617                if let Some(threshold) = &metadata.threshold {
618                    self.newline();
619                    self.write_indent();
620                    self.write("@threshold ");
621                    self.write(&threshold.to_string());
622                }
623                if let Some(ref severity) = metadata.severity {
624                    self.newline();
625                    self.write_indent();
626                    self.write("@severity ");
627                    self.write_string_literal(&format!("{:?}", severity).to_lowercase());
628                }
629                if let Some(target) = &metadata.target {
630                    self.newline();
631                    self.write_indent();
632                    self.write("@target ");
633                    self.write(&target.to_string());
634                }
635                if let Some(ref window) = metadata.window {
636                    self.newline();
637                    self.write_indent();
638                    self.write("@window ");
639                    self.write(&window.num_seconds().to_string());
640                    self.write(" \"seconds\"");
641                }
642                self.dedent();
643                self.newline();
644            }
645            AstNode::MappingDecl {
646                name,
647                target,
648                rules,
649            } => {
650                self.write("Mapping ");
651                self.write_string_literal(name);
652                self.write(" for ");
653                self.write(&format!("{}", target).to_lowercase());
654                self.write(" {");
655                self.newline();
656                self.indent();
657                for rule in rules {
658                    self.write_indent();
659                    self.write(&rule.primitive_type);
660                    self.write(" ");
661                    self.write_string_literal(&rule.primitive_name);
662                    self.write(" -> ");
663                    self.write(&rule.target_type);
664                    self.write(" { ");
665                    let mut first = true;
666                    for (k, v) in &rule.fields {
667                        if !first {
668                            self.write(", ");
669                        }
670                        self.write_string_literal(k);
671                        self.write(": ");
672                        self.write(&v.to_string());
673                        first = false;
674                    }
675                    self.write(" }");
676                    self.newline();
677                }
678                self.dedent();
679                self.write_indent();
680                self.write("}");
681                self.newline();
682            }
683            AstNode::ProjectionDecl {
684                name,
685                target,
686                overrides,
687            } => {
688                self.write("Projection ");
689                self.write_string_literal(name);
690                self.write(" for ");
691                self.write(&format!("{}", target).to_lowercase());
692                self.write(" {");
693                self.newline();
694                self.indent();
695                for over in overrides {
696                    self.write_indent();
697                    self.write(&over.primitive_type);
698                    self.write(" ");
699                    self.write_string_literal(&over.primitive_name);
700                    self.write(" { ");
701                    let mut first = true;
702                    for (k, v) in &over.fields {
703                        if !first {
704                            self.write(", ");
705                        }
706                        self.write_string_literal(k);
707                        self.write(": ");
708                        self.write(&v.to_string());
709                        first = false;
710                    }
711                    self.write(" }");
712                    self.newline();
713                }
714                self.dedent();
715                self.write_indent();
716                self.write("}");
717                self.newline();
718            }
719        }
720    }
721
722    /// Format an expression using the Expression's Display implementation.
723    ///
724    /// The Expression type has a proper Display that outputs SEA-compatible syntax.
725    fn format_expression(&mut self, expr: &Expression) {
726        self.write(&format!("{}", expr));
727    }
728
729    // Helper methods
730
731    fn write(&mut self, s: &str) {
732        self.output.push_str(s);
733    }
734
735    fn write_string_literal(&mut self, s: &str) {
736        self.output.push('"');
737        // Escape special characters
738        for c in s.chars() {
739            match c {
740                '"' => self.output.push_str("\\\""),
741                '\\' => self.output.push_str("\\\\"),
742                '\n' => self.output.push_str("\\n"),
743                '\r' => self.output.push_str("\\r"),
744                '\t' => self.output.push_str("\\t"),
745                _ => self.output.push(c),
746            }
747        }
748        self.output.push('"');
749    }
750
751    fn newline(&mut self) {
752        self.output.push('\n');
753    }
754
755    fn write_indent(&mut self) {
756        let indent = self.config.indent_string();
757        for _ in 0..self.indent_level {
758            self.output.push_str(&indent);
759        }
760    }
761
762    fn indent(&mut self) {
763        self.indent_level += 1;
764    }
765
766    fn dedent(&mut self) {
767        if self.indent_level > 0 {
768            self.indent_level -= 1;
769        }
770    }
771}
772
773#[cfg(test)]
774mod tests {
775    use super::*;
776
777    #[test]
778    fn test_format_entity_basic() {
779        let input = r#"Entity "Foo""#;
780        let result = format(input, FormatConfig::default()).unwrap();
781        assert!(result.contains("Entity \"Foo\""));
782        assert!(result.ends_with('\n'));
783    }
784
785    #[test]
786    fn test_format_entity_with_domain() {
787        let input = r#"Entity   "Bar"   in   test"#;
788        let result = format(input, FormatConfig::default()).unwrap();
789        assert!(result.contains("Entity \"Bar\" in test"));
790    }
791
792    #[test]
793    fn test_format_resource() {
794        let input = r#"Resource "Money" USD in finance"#;
795        let result = format(input, FormatConfig::default()).unwrap();
796        assert!(result.contains("Resource \"Money\" USD in finance"));
797    }
798
799    #[test]
800    fn test_format_flow() {
801        let input = r#"Flow "Money" from "A" to "B" quantity 100"#;
802        let result = format(input, FormatConfig::default()).unwrap();
803        assert!(result.contains("Flow \"Money\" from \"A\" to \"B\" quantity 100"));
804    }
805
806    #[test]
807    fn test_format_idempotent() {
808        let input = r#"Entity "Test" in foo"#;
809        let once = format(input, FormatConfig::default()).unwrap();
810        let twice = format(&once, FormatConfig::default()).unwrap();
811        assert_eq!(once, twice, "Formatting should be idempotent");
812    }
813
814    #[test]
815    fn test_format_multiple_declarations() {
816        let input = r#"
817Entity "A"
818Entity "B"
819Resource "R" units
820"#;
821        let result = format(input, FormatConfig::default()).unwrap();
822        assert!(result.contains("Entity \"A\""));
823        assert!(result.contains("Entity \"B\""));
824        assert!(result.contains("Resource \"R\""));
825    }
826
827    #[test]
828    fn test_format_with_tabs() {
829        let input = r#"
830Relation "Test"
831    subject: "A"
832    predicate: "rel"
833    object: "B"
834"#;
835        let config = FormatConfig::default().with_tabs();
836        let result = format(input, config).unwrap();
837        assert!(result.contains('\t'), "Should use tabs for indentation");
838    }
839
840    #[test]
841    fn test_format_imports_sorted() {
842        let input = r#"
843import { B } from "z.sea"
844import { A } from "a.sea"
845Entity "Test"
846"#;
847        let config = FormatConfig::default();
848        let result = format(input, config).unwrap();
849        // A should come before B in sorted output
850        let a_pos = result.find("a.sea").unwrap();
851        let z_pos = result.find("z.sea").unwrap();
852        assert!(a_pos < z_pos, "Imports should be sorted alphabetically");
853    }
854
855    #[test]
856    fn test_format_policy_with_expression() {
857        let input = r#"Policy test as: x = 5"#;
858        let result = format(input, FormatConfig::default()).unwrap();
859        // Should contain the expression, not Debug format
860        assert!(result.contains("Policy test"));
861        assert!(result.contains("as:"));
862        // Expression should use Display format, not Debug
863        assert!(!result.contains("Binary {"), "Should not use Debug format");
864    }
865
866    #[test]
867    fn test_format_instance_with_fields() {
868        let input = r#"
869Instance test_user of "User" {
870    name: "Alice",
871    age: 30
872}
873"#;
874        let result = format(input, FormatConfig::default()).unwrap();
875        assert!(result.contains("Instance test_user of \"User\""));
876        assert!(result.contains("name:"));
877        assert!(result.contains("age:"));
878    }
879
880    #[test]
881    fn test_format_preserves_header_comments() {
882        let input = r#"// This is a header comment
883// Second line
884Entity "Foo"
885"#;
886        let result = format(input, FormatConfig::default()).unwrap();
887        assert!(result.contains("// This is a header comment"));
888        assert!(result.contains("// Second line"));
889        assert!(result.contains("Entity \"Foo\""));
890    }
891
892    #[test]
893    fn test_format_without_comments() {
894        let input = r#"// Comment
895Entity "Foo"
896"#;
897        let config = FormatConfig {
898            preserve_comments: false,
899            ..Default::default()
900        };
901        let result = format(input, config).unwrap();
902        // Comments should not be preserved when disabled
903        assert!(!result.contains("// Comment"));
904        assert!(result.contains("Entity \"Foo\""));
905    }
906}