Skip to content

Commit 589db13

Browse files
committed
Cover multi-line item spacing with unit tests
Codecov reported 15 changed lines without coverage: branches of the separator's measurement that the fixtures never reach first, the trailing comments it moves, the new cases of will_break, is_nil and debug, and Experimental_features.to_string. tests/syntax_tests/res_test.ml now prints documents built for each case and checks the output, and formats a module with the feature off and on through the printer API. The fixture adds a multi-line template literal and a trailing comment on a multi-line item. print_list matched an accumulator that cannot be empty after the first item; it now matches the one case that adds a separator and treats every other one as the first item. Signed-off-by: Roland Peelen <roland@magic.dev>
1 parent d171bd7 commit 589db13

4 files changed

Lines changed: 185 additions & 1 deletion

File tree

‎compiler/syntax/src/res_printer.ml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,6 @@ let print_list ~get_loc ~nodes ~print ?(force_break = false)
404404
let doc = print_comments raw_doc t loc in
405405
let acc =
406406
match (prev_loc_opt, acc) with
407-
| None, _ | _, [] -> [doc]
408407
| Some prev_loc, prev_doc :: _ ->
409408
let sep =
410409
if start_pos.pos_lnum - prev_loc.Location.loc_end.pos_lnum > 1
@@ -414,6 +413,7 @@ let print_list ~get_loc ~nodes ~print ?(force_break = false)
414413
else Doc.hard_line
415414
in
416415
doc :: sep :: acc
416+
| _ -> [doc]
417417
in
418418
let first_loc_opt =
419419
match first_loc_opt with

‎tests/syntax_tests/data/experimental/MultilineItemSpacing/expected/multilineSpacing.res.txt‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,16 @@ let h = () => {
5656
}
5757
x + y
5858
}
59+
60+
// a template literal with a line break is multi-line
61+
let s = `multi
62+
line`
63+
64+
let t = 5
65+
66+
// a trailing comment stays on the last line of a multi-line item
67+
let g = {
68+
1
69+
} // trailing
70+
71+
let h = 6

‎tests/syntax_tests/data/experimental/MultilineItemSpacing/multilineSpacing.res‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,14 @@ let h = () => {
4444
}
4545
x + y
4646
}
47+
48+
// a template literal with a line break is multi-line
49+
let s = `multi
50+
line`
51+
let t = 5
52+
53+
// a trailing comment stays on the last line of a multi-line item
54+
let g = {
55+
1
56+
} // trailing
57+
let h = 6

‎tests/syntax_tests/res_test.ml‎

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,166 @@ module Parser_api_test = struct
556556
windows_crlf ()
557557
end
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:\nexpected:\n%S\nactual:\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\nlet b = 2"
598+
(print (items a b));
599+
check "a hard line spans lines" ~expected:"x\ny\n\nlet 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\ny`\n\nlet 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\ny`\n\nlet b = 2"
605+
(print (items (Doc.text "`x\ny`") b));
606+
check "an item list spans lines"
607+
~expected:"let a = 1\nlet b = 2\n\nlet 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)\nlet b = 2"
610+
(print (items call b));
611+
check "a group that breaks spans lines"
612+
~expected:"f(\n x,\n y,\n)\n\nlet b = 2"
613+
(print ~width:5 (items call b));
614+
check "a chosen custom layout is measured" ~expected:"let a = 1\nlet 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\nlet 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\nlet 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\nlet 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\nlet 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\nlet 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\nlet 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\nlet 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}\nlet 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\nlet 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+
559718
let () = Outcome_printer_tests.run ()
560719
let () = Parser_api_test.run ()
561720
let () = Res_utf8_test.run ()
721+
let () = Multiline_item_spacing_test.run ()

0 commit comments

Comments
 (0)