@@ -556,6 +556,166 @@ module Parser_api_test = struct
556556 windows_crlf ()
557557end
558558
559+ (* Res_doc.item_separator puts a blank line next to an item that prints on
560+ several lines, measuring the documents on each side as to_string lays them
561+ out. Res_doc.comment hides comments from the measurement. *)
562+ module Multiline_item_spacing_test = struct
563+ module Doc = Res_doc
564+
565+ let check name ~expected actual =
566+ if actual <> expected then
567+ failwith
568+ (Printf. sprintf " %s:\n expected:\n %S\n actual:\n %S" name expected actual)
569+
570+ (* Module items are printed inside a broken group, like print_structure. *)
571+ let print ?(width = 80 ) doc =
572+ Doc. to_string ~width (Doc. breakable_group ~force_break: true doc)
573+ let items prev next = Doc. concat [prev; Doc. item_separator ~prev ~next ; next]
574+ let a = Doc. text " let a = 1"
575+ let b = Doc. text " let b = 2"
576+
577+ (* A function call that fits on one line at width 80 but not at width 10. *)
578+ let call =
579+ Doc. group
580+ (Doc. concat
581+ [
582+ Doc. text " f(" ;
583+ Doc. indent
584+ (Doc. concat
585+ [
586+ Doc. soft_line;
587+ Doc. text " x," ;
588+ Doc. line;
589+ Doc. text " y" ;
590+ Doc. trailing_comma;
591+ ]);
592+ Doc. soft_line;
593+ Doc. text " )" ;
594+ ])
595+
596+ let measuring () =
597+ check " one-line items stay together" ~expected: " let a = 1\n let b = 2"
598+ (print (items a b));
599+ check " a hard line spans lines" ~expected: " x\n y\n\n let b = 2"
600+ (print (items (Doc. concat [Doc. text " x" ; Doc. hard_line; Doc. text " y" ]) b));
601+ check " a literal line spans lines" ~expected: " `x\n y`\n\n let b = 2"
602+ (print
603+ (items (Doc. concat [Doc. text " `x" ; Doc. literal_line; Doc. text " y`" ]) b));
604+ check " a newline inside text spans lines" ~expected: " `x\n y`\n\n let b = 2"
605+ (print (items (Doc. text " `x\n y`" ) b));
606+ check " an item list spans lines"
607+ ~expected: " let a = 1\n let b = 2\n\n let c = 3"
608+ (print (items (items a b) (Doc. text " let c = 3" )));
609+ check " a group that fits is one line" ~expected: " f(x, y)\n let b = 2"
610+ (print (items call b));
611+ check " a group that breaks spans lines"
612+ ~expected: " f(\n x,\n y,\n )\n\n let b = 2"
613+ (print ~width: 5 (items call b));
614+ check " a chosen custom layout is measured" ~expected: " let a = 1\n let b = 2"
615+ (print (items (Doc. custom_layout [a]) b));
616+ check " break parents and line suffixes do not span lines"
617+ ~expected: " let a = 1 // c\n let b = 2"
618+ (print
619+ (items
620+ (Doc. concat
621+ [a; Doc. break_parent; Doc. line_suffix (Doc. text " // c" )])
622+ b));
623+ check " a comment above an item does not count"
624+ ~expected: " let a = 1\n // c\n let b = 2"
625+ (print
626+ (items a
627+ (Doc. concat
628+ [Doc. comment (Doc. concat [Doc. text " // c" ; Doc. hard_line]); b])));
629+ let forced_if_breaks =
630+ Doc. if_breaks (Doc. text " a" ) (Doc. concat [Doc. text " b" ; Doc. hard_line])
631+ in
632+ check " an if-breaks broken by its flat branch takes the break branch"
633+ ~expected: " xa\n let b = 2"
634+ (print (items (Doc. concat [Doc. text " x" ; forced_if_breaks]) b));
635+ check " an if-breaks in a broken group takes the break branch"
636+ ~expected: " {\n }\n\n let b = 2"
637+ (print
638+ (items
639+ (Doc. breakable_group ~force_break: true
640+ (Doc. concat
641+ [
642+ Doc. if_breaks (Doc. text " {" ) (Doc. text " (" );
643+ Doc. line;
644+ Doc. text " }" ;
645+ ]))
646+ b))
647+
648+ let trailing_comments () =
649+ let block =
650+ Doc. concat [Doc. text " let a = {" ; Doc. hard_line; Doc. text " }" ]
651+ in
652+ let after_block suffix =
653+ items (Doc. concat [block; Doc. line_suffix suffix]) b
654+ in
655+ check " a same-line trailing comment stays on its line"
656+ ~expected: " let a = {\n } // c\n\n let b = 2"
657+ (print (after_block (Doc. text " // c" )));
658+ check " an own-line trailing comment moves below the blank line"
659+ ~expected: " let a = {\n }\n\n // c\n let b = 2"
660+ (print (after_block (Doc. concat [Doc. hard_line; Doc. text " // c" ])));
661+ check " an empty own-line trailing comment prints an empty line"
662+ ~expected: " let a = {\n }\n\n\n let b = 2"
663+ (print (after_block Doc. hard_line))
664+
665+ let predicates () =
666+ let separator = Doc. item_separator ~prev: a ~next: b in
667+ assert (Doc. will_break separator);
668+ assert (Doc. will_break Doc. break_parent);
669+ assert (Doc. will_break (Doc. comment Doc. hard_line));
670+ assert (not (Doc. will_break (Doc. comment a)));
671+ assert (not (Doc. is_nil separator));
672+ assert (not (Doc. is_nil (Doc. comment a)));
673+ check " an empty custom layout prints nothing" ~expected: " "
674+ (print (Doc. custom_layout [] ))
675+
676+ (* debug prints to stdout; send it to the null device. *)
677+ let debug () =
678+ flush stdout;
679+ let saved = Unix. dup Unix. stdout in
680+ let null = Unix. openfile Filename. null [Unix. O_WRONLY ] 0 in
681+ Unix. dup2 null Unix. stdout;
682+ Fun. protect
683+ ~finally: (fun () ->
684+ flush stdout;
685+ Unix. dup2 saved Unix. stdout;
686+ Unix. close saved;
687+ Unix. close null)
688+ (fun () -> Doc. debug (Doc. concat [items a b; Doc. comment a]))
689+
690+ let printer () =
691+ let source = " module A = {\n let x = 1\n }\n let y = 2\n " in
692+ let format () =
693+ let result =
694+ Res_driver. parse_implementation_from_source ~display_filename: " A.res"
695+ ~source
696+ in
697+ Res_printer. print_implementation ~width: 80 result.parsetree
698+ ~comments: result.comments
699+ in
700+ check " the printer keeps items together by default" ~expected: source
701+ (format () );
702+ Experimental_features. enable_from_string
703+ (Experimental_features. to_string
704+ Experimental_features. MultilineItemSpacing );
705+ Fun. protect ~finally: Experimental_features. reset (fun () ->
706+ check " the printer spaces items with MultilineItemSpacing"
707+ ~expected: " module A = {\n let x = 1\n }\n\n let y = 2\n " (format () ))
708+
709+ let run () =
710+ measuring () ;
711+ trailing_comments () ;
712+ predicates () ;
713+ debug () ;
714+ printer () ;
715+ print_endline " ✅ multi-line item spacing"
716+ end
717+
559718let () = Outcome_printer_tests. run ()
560719let () = Parser_api_test. run ()
561720let () = Res_utf8_test. run ()
721+ let () = Multiline_item_spacing_test. run ()
0 commit comments