forked from rescript-lang/rescript
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathres_printer.ml
5937 lines (5788 loc) · 195 KB
/
res_printer.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
module Doc = Res_doc
module CommentTable = Res_comments_table
module Comment = Res_comment
module Token = Res_token
module Parens = Res_parens
module ParsetreeViewer = Res_parsetree_viewer
type callback_style =
(* regular arrow function, example: `let f = x => x + 1` *)
| NoCallback
(* `Thing.map(foo, (arg1, arg2) => MyModuleBlah.toList(argument))` *)
| FitsOnOneLine
(* Thing.map(longArgumet, veryLooooongArgument, (arg1, arg2) =>
* MyModuleBlah.toList(argument)
* )
*)
| ArgumentsFitOnOneLine
let add_parens doc =
Doc.group
(Doc.concat
[
Doc.lparen;
Doc.indent (Doc.concat [Doc.soft_line; doc]);
Doc.soft_line;
Doc.rparen;
])
let add_braces doc =
Doc.group
(Doc.concat
[
Doc.lbrace;
Doc.indent (Doc.concat [Doc.soft_line; doc]);
Doc.soft_line;
Doc.rbrace;
])
let add_async doc = Doc.concat [Doc.text "async "; doc]
let get_first_leading_comment tbl loc =
match Hashtbl.find tbl.CommentTable.leading loc with
| comment :: _ -> Some comment
| [] -> None
| exception Not_found -> None
(* Checks if `loc` has a leading line comment, i.e. `// comment above`*)
let has_leading_line_comment tbl loc =
match get_first_leading_comment tbl loc with
| Some comment -> Comment.is_single_line_comment comment
| None -> false
let has_comment_below tbl loc =
match Hashtbl.find tbl.CommentTable.trailing loc with
| comment :: _ ->
let comment_loc = Comment.loc comment in
comment_loc.Location.loc_start.pos_lnum > loc.Location.loc_end.pos_lnum
| [] -> false
| exception Not_found -> false
let has_nested_jsx_or_more_than_one_child expr =
let rec loop in_recursion expr =
match expr.Parsetree.pexp_desc with
| Pexp_construct
({txt = Longident.Lident "::"}, Some {pexp_desc = Pexp_tuple [hd; tail]})
->
if in_recursion || ParsetreeViewer.is_jsx_expression hd then true
else loop true tail
| _ -> false
in
loop false expr
let has_comments_inside tbl loc =
match Hashtbl.find_opt tbl.CommentTable.inside loc with
| None -> false
| _ -> true
let has_trailing_comments tbl loc =
match Hashtbl.find_opt tbl.CommentTable.trailing loc with
| None -> false
| _ -> true
let print_multiline_comment_content txt =
(* Turns
* |* first line
* * second line
* * third line *|
* Into
* |* first line
* * second line
* * third line *|
*
* What makes a comment suitable for this kind of indentation?
* -> multiple lines + every line starts with a star
*)
let rec indent_stars lines acc =
match lines with
| [] -> Doc.nil
| [last_line] ->
let line = String.trim last_line in
let doc = Doc.text (" " ^ line) in
let trailing_space = if line = "" then Doc.nil else Doc.space in
List.rev (trailing_space :: doc :: acc) |> Doc.concat
| line :: lines ->
let line = String.trim line in
if line != "" && String.unsafe_get line 0 == '*' then
let doc = Doc.text (" " ^ line) in
indent_stars lines (Doc.hard_line :: doc :: acc)
else
let trailing_space =
let len = String.length txt in
if len > 0 && String.unsafe_get txt (len - 1) = ' ' then Doc.space
else Doc.nil
in
let content = Comment.trim_spaces txt in
Doc.concat [Doc.text content; trailing_space]
in
let lines = String.split_on_char '\n' txt in
match lines with
| [] -> Doc.text "/* */"
| [line] ->
Doc.concat
[Doc.text "/* "; Doc.text (Comment.trim_spaces line); Doc.text " */"]
| first :: rest ->
let first_line = Comment.trim_spaces first in
Doc.concat
[
Doc.text "/*";
(match first_line with
| "" | "*" -> Doc.nil
| _ -> Doc.space);
indent_stars rest [Doc.hard_line; Doc.text first_line];
Doc.text "*/";
]
let print_trailing_comment (prev_loc : Location.t) (node_loc : Location.t)
comment =
let single_line = Comment.is_single_line_comment comment in
let content =
let txt = Comment.txt comment in
if single_line then Doc.text ("//" ^ txt)
else print_multiline_comment_content txt
in
let diff =
let cmt_start = (Comment.loc comment).loc_start in
cmt_start.pos_lnum - prev_loc.loc_end.pos_lnum
in
let is_below =
(Comment.loc comment).loc_start.pos_lnum > node_loc.loc_end.pos_lnum
in
if diff > 0 || is_below then
Doc.concat
[
Doc.break_parent;
Doc.line_suffix
(Doc.concat
[
Doc.hard_line;
(if diff > 1 then Doc.hard_line else Doc.nil);
content;
]);
]
else if not single_line then Doc.concat [Doc.space; content]
else Doc.line_suffix (Doc.concat [Doc.space; content])
let print_leading_comment ?next_comment comment =
let single_line = Comment.is_single_line_comment comment in
let content =
let txt = Comment.txt comment in
if single_line then Doc.text ("//" ^ txt)
else print_multiline_comment_content txt
in
let separator =
Doc.concat
[
(if single_line then Doc.concat [Doc.hard_line; Doc.break_parent]
else Doc.nil);
(match next_comment with
| Some next ->
let next_loc = Comment.loc next in
let curr_loc = Comment.loc comment in
let diff =
next_loc.Location.loc_start.pos_lnum
- curr_loc.Location.loc_end.pos_lnum
in
let next_single_line = Comment.is_single_line_comment next in
if single_line && next_single_line then
if diff > 1 then Doc.hard_line else Doc.nil
else if single_line && not next_single_line then
if diff > 1 then Doc.hard_line else Doc.nil
else if diff > 1 then Doc.concat [Doc.hard_line; Doc.hard_line]
else if diff == 1 then Doc.hard_line
else Doc.space
| None -> Doc.nil);
]
in
Doc.concat [content; separator]
(* This function is used for printing comments inside an empty block *)
let print_comments_inside cmt_tbl loc =
let print_comment comment =
let single_line = Comment.is_single_line_comment comment in
let txt = Comment.txt comment in
if single_line then Doc.text ("//" ^ txt)
else print_multiline_comment_content txt
in
let force_break =
loc.Location.loc_start.pos_lnum <> loc.Location.loc_end.pos_lnum
in
let rec loop acc comments =
match comments with
| [] -> Doc.nil
| [comment] ->
let cmt_doc = print_comment comment in
let cmts_doc = Doc.concat (Doc.soft_line :: List.rev (cmt_doc :: acc)) in
let doc =
Doc.breakable_group ~force_break
(Doc.concat
[Doc.if_breaks (Doc.indent cmts_doc) cmts_doc; Doc.soft_line])
in
doc
| comment :: rest ->
let cmt_doc = Doc.concat [print_comment comment; Doc.line] in
loop (cmt_doc :: acc) rest
in
match Hashtbl.find cmt_tbl.CommentTable.inside loc with
| exception Not_found -> Doc.nil
| comments ->
Hashtbl.remove cmt_tbl.inside loc;
loop [] comments
(* This function is used for printing comments inside an empty file *)
let print_comments_inside_file cmt_tbl =
let rec loop acc comments =
match comments with
| [] -> Doc.nil
| [comment] ->
let cmt_doc = print_leading_comment comment in
let doc =
Doc.group (Doc.concat [Doc.concat (List.rev (cmt_doc :: acc))])
in
doc
| comment :: (next_comment :: _comments as rest) ->
let cmt_doc = print_leading_comment ~next_comment comment in
loop (cmt_doc :: acc) rest
in
match Hashtbl.find cmt_tbl.CommentTable.inside Location.none with
| exception Not_found -> Doc.nil
| comments ->
Hashtbl.remove cmt_tbl.inside Location.none;
Doc.group (loop [] comments)
let print_leading_comments node tbl loc =
let rec loop acc comments =
match comments with
| [] -> node
| [comment] ->
let cmt_doc = print_leading_comment comment in
let diff =
loc.Location.loc_start.pos_lnum
- (Comment.loc comment).Location.loc_end.pos_lnum
in
let separator =
if Comment.is_single_line_comment comment then
if diff > 1 then Doc.hard_line else Doc.nil
else if diff == 0 then Doc.space
else if diff > 1 then Doc.concat [Doc.hard_line; Doc.hard_line]
else Doc.hard_line
in
let doc =
Doc.group
(Doc.concat [Doc.concat (List.rev (cmt_doc :: acc)); separator; node])
in
doc
| comment :: (next_comment :: _comments as rest) ->
let cmt_doc = print_leading_comment ~next_comment comment in
loop (cmt_doc :: acc) rest
in
match Hashtbl.find tbl loc with
| exception Not_found -> node
| comments ->
(* Remove comments from tbl: Some ast nodes have the same location.
* We only want to print comments once *)
Hashtbl.remove tbl loc;
loop [] comments
let print_trailing_comments node tbl loc =
let rec loop prev acc comments =
match comments with
| [] -> Doc.concat (List.rev acc)
| comment :: comments ->
let cmt_doc = print_trailing_comment prev loc comment in
loop (Comment.loc comment) (cmt_doc :: acc) comments
in
match Hashtbl.find tbl loc with
| exception Not_found -> node
| [] -> node
| _first :: _ as comments ->
(* Remove comments from tbl: Some ast nodes have the same location.
* We only want to print comments once *)
Hashtbl.remove tbl loc;
let cmts_doc = loop loc [] comments in
Doc.concat [node; cmts_doc]
let print_comments doc (tbl : CommentTable.t) loc =
let doc_with_leading_comments = print_leading_comments doc tbl.leading loc in
print_trailing_comments doc_with_leading_comments tbl.trailing loc
let print_list ~get_loc ~nodes ~print ?(force_break = false) t =
let rec loop (prev_loc : Location.t) acc nodes =
match nodes with
| [] -> (prev_loc, Doc.concat (List.rev acc))
| node :: nodes ->
let loc = get_loc node in
let start_pos =
match get_first_leading_comment t loc with
| None -> loc.loc_start
| Some comment -> (Comment.loc comment).loc_start
in
let sep =
if start_pos.pos_lnum - prev_loc.loc_end.pos_lnum > 1 then
Doc.concat [Doc.hard_line; Doc.hard_line]
else Doc.hard_line
in
let doc = print_comments (print node t) t loc in
loop loc (doc :: sep :: acc) nodes
in
match nodes with
| [] -> Doc.nil
| node :: nodes ->
let first_loc = get_loc node in
let doc = print_comments (print node t) t first_loc in
let last_loc, docs = loop first_loc [doc] nodes in
let force_break =
force_break || first_loc.loc_start.pos_lnum != last_loc.loc_end.pos_lnum
in
Doc.breakable_group ~force_break docs
let print_listi ~get_loc ~nodes ~print ?(force_break = false) t =
let rec loop i (prev_loc : Location.t) acc nodes =
match nodes with
| [] -> (prev_loc, Doc.concat (List.rev acc))
| node :: nodes ->
let loc = get_loc node in
let start_pos =
match get_first_leading_comment t loc with
| None -> loc.loc_start
| Some comment -> (Comment.loc comment).loc_start
in
let sep =
if start_pos.pos_lnum - prev_loc.loc_end.pos_lnum > 1 then
Doc.concat [Doc.hard_line; Doc.hard_line]
else Doc.line
in
let doc = print_comments (print node t i) t loc in
loop (i + 1) loc (doc :: sep :: acc) nodes
in
match nodes with
| [] -> Doc.nil
| node :: nodes ->
let first_loc = get_loc node in
let doc = print_comments (print node t 0) t first_loc in
let last_loc, docs = loop 1 first_loc [doc] nodes in
let force_break =
force_break || first_loc.loc_start.pos_lnum != last_loc.loc_end.pos_lnum
in
Doc.breakable_group ~force_break docs
let rec print_longident_aux accu = function
| Longident.Lident s -> Doc.text s :: accu
| Ldot (lid, s) -> print_longident_aux (Doc.text s :: accu) lid
| Lapply (lid1, lid2) ->
let d1 = Doc.join ~sep:Doc.dot (print_longident_aux [] lid1) in
let d2 = Doc.join ~sep:Doc.dot (print_longident_aux [] lid2) in
Doc.concat [d1; Doc.lparen; d2; Doc.rparen] :: accu
let print_longident = function
| Longident.Lident txt -> Doc.text txt
| lid -> Doc.join ~sep:Doc.dot (print_longident_aux [] lid)
type identifier_style = UppercaseExoticIdent | ExoticIdent | NormalIdent
let classify_ident_content ?(allow_uident = false) ?(allow_hyphen = false) txt =
if Token.is_keyword_txt txt then ExoticIdent
else
let len = String.length txt in
let rec loop i =
if i == len then NormalIdent
else if i == 0 then
match String.unsafe_get txt i with
| '\\' -> UppercaseExoticIdent
| 'A' .. 'Z' when allow_uident -> loop (i + 1)
| 'a' .. 'z' | '_' -> loop (i + 1)
| _ -> ExoticIdent
else
match String.unsafe_get txt i with
| 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '\'' | '_' -> loop (i + 1)
| '-' when allow_hyphen -> loop (i + 1)
| _ -> ExoticIdent
in
loop 0
let print_ident_like ?allow_uident ?allow_hyphen txt =
match classify_ident_content ?allow_uident ?allow_hyphen txt with
| ExoticIdent -> Doc.concat [Doc.text "\\\""; Doc.text txt; Doc.text "\""]
| UppercaseExoticIdent | NormalIdent -> Doc.text txt
let rec unsafe_for_all_range s ~start ~finish p =
start > finish
|| p (String.unsafe_get s start)
&& unsafe_for_all_range s ~start:(start + 1) ~finish p
let for_all_from s start p =
let len = String.length s in
unsafe_for_all_range s ~start ~finish:(len - 1) p
(* See https://github.com/rescript-lang/rescript-compiler/blob/726cfa534314b586e5b5734471bc2023ad99ebd9/jscomp/ext/ext_string.ml#L510 *)
let is_valid_numeric_polyvar_number (x : string) =
let len = String.length x in
len > 0
&&
let a = Char.code (String.unsafe_get x 0) in
a <= 57
&&
if len > 1 then
a > 48
&& for_all_from x 1 (function
| '0' .. '9' -> true
| _ -> false)
else a >= 48
(* Exotic identifiers in poly-vars have a "lighter" syntax: #"ease-in" *)
let print_poly_var_ident txt =
(* numeric poly-vars don't need quotes: #644 *)
if is_valid_numeric_polyvar_number txt then Doc.text txt
else
match classify_ident_content ~allow_uident:true txt with
| UppercaseExoticIdent ->
let len = String.length txt in
(* UppercaseExoticIdent follows the \"..." format,
so removing the leading backslash is enough to transform it into polyvar style *)
Doc.text ((String.sub [@doesNotRaise]) txt 1 (len - 1))
| ExoticIdent -> Doc.concat [Doc.text "\""; Doc.text txt; Doc.text "\""]
| NormalIdent -> (
match txt with
| "" -> Doc.concat [Doc.text "\""; Doc.text txt; Doc.text "\""]
| _ -> Doc.text txt)
let polyvar_ident_to_string poly_var_ident =
Doc.concat [Doc.text "#"; print_poly_var_ident poly_var_ident]
|> Doc.to_string ~width:80
let print_lident l =
let flat_lid_opt lid =
let rec flat accu = function
| Longident.Lident s -> Some (s :: accu)
| Ldot (lid, s) -> flat (s :: accu) lid
| Lapply (_, _) -> None
in
flat [] lid
in
match l with
| Longident.Lident txt -> print_ident_like txt
| Longident.Ldot (path, txt) ->
let doc =
match flat_lid_opt path with
| Some txts ->
Doc.concat
[
Doc.join ~sep:Doc.dot (List.map Doc.text txts);
Doc.dot;
print_ident_like txt;
]
| None -> Doc.text "printLident: Longident.Lapply is not supported"
in
doc
| Lapply (_, _) -> Doc.text "printLident: Longident.Lapply is not supported"
let print_longident_location l cmt_tbl =
let doc = print_longident l.Location.txt in
print_comments doc cmt_tbl l.loc
(* Module.SubModule.x *)
let print_lident_path path cmt_tbl =
let doc = print_lident path.Location.txt in
print_comments doc cmt_tbl path.loc
(* Module.SubModule.x or Module.SubModule.X *)
let print_ident_path path cmt_tbl =
let doc = print_lident path.Location.txt in
print_comments doc cmt_tbl path.loc
let print_string_loc sloc cmt_tbl =
let doc = print_ident_like sloc.Location.txt in
print_comments doc cmt_tbl sloc.loc
let print_string_contents txt =
let lines = String.split_on_char '\n' txt in
Doc.join ~sep:Doc.literal_line (List.map Doc.text lines)
let print_constant ?(template_literal = false) c =
match c with
| Parsetree.Pconst_integer (s, suffix) -> (
match suffix with
| Some c -> Doc.text (s ^ Char.escaped c)
| None -> Doc.text s)
| Pconst_string (txt, None) ->
Doc.concat [Doc.text "\""; print_string_contents txt; Doc.text "\""]
| Pconst_string (txt, Some prefix) ->
if prefix = "INTERNAL_RES_CHAR_CONTENTS" then
Doc.concat [Doc.text "'"; Doc.text txt; Doc.text "'"]
else
let lquote, rquote =
if template_literal then ("`", "`") else ("\"", "\"")
in
Doc.concat
[
(if prefix = "js" then Doc.nil else Doc.text prefix);
Doc.text lquote;
print_string_contents txt;
Doc.text rquote;
]
| Pconst_float (s, _) -> Doc.text s
| Pconst_char c ->
let str =
match Char.unsafe_chr c with
| '\'' -> "\\'"
| '\\' -> "\\\\"
| '\n' -> "\\n"
| '\t' -> "\\t"
| '\r' -> "\\r"
| '\b' -> "\\b"
| ' ' .. '~' as c ->
let s = (Bytes.create [@doesNotRaise]) 1 in
Bytes.unsafe_set s 0 c;
Bytes.unsafe_to_string s
| _ -> Res_utf8.encode_code_point c
in
Doc.text ("'" ^ str ^ "'")
module State = struct
let custom_layout_threshold = 2
type t = {custom_layout: int}
let init () = {custom_layout = 0}
let next_custom_layout t = {custom_layout = t.custom_layout + 1}
let should_break_callback t = t.custom_layout > custom_layout_threshold
end
let rec print_structure ~state (s : Parsetree.structure) t =
match s with
| [] -> print_comments_inside_file t
| structure ->
print_list
~get_loc:(fun s -> s.Parsetree.pstr_loc)
~nodes:structure
~print:(print_structure_item ~state)
t
and print_structure_item ~state (si : Parsetree.structure_item) cmt_tbl =
match si.pstr_desc with
| Pstr_value (rec_flag, value_bindings) ->
let rec_flag =
match rec_flag with
| Asttypes.Nonrecursive -> Doc.nil
| Asttypes.Recursive -> Doc.text "rec "
in
print_value_bindings ~state ~rec_flag value_bindings cmt_tbl
| Pstr_type (rec_flag, type_declarations) ->
let rec_flag =
match rec_flag with
| Asttypes.Nonrecursive -> Doc.nil
| Asttypes.Recursive -> Doc.text "rec "
in
print_type_declarations ~state ~rec_flag type_declarations cmt_tbl
| Pstr_primitive value_description ->
print_value_description ~state value_description cmt_tbl
| Pstr_eval (expr, attrs) ->
let expr_doc =
let doc = print_expression_with_comments ~state expr cmt_tbl in
match Parens.structure_expr expr with
| Parens.Parenthesized -> add_parens doc
| Braced braces -> print_braces doc expr braces
| Nothing -> doc
in
Doc.concat [print_attributes ~state attrs cmt_tbl; expr_doc]
| Pstr_attribute attr ->
fst (print_attribute ~state ~standalone:true attr cmt_tbl)
| Pstr_extension (extension, attrs) ->
Doc.concat
[
print_attributes ~state attrs cmt_tbl;
Doc.concat
[print_extension ~state ~at_module_lvl:true extension cmt_tbl];
]
| Pstr_include include_declaration ->
print_include_declaration ~state include_declaration cmt_tbl
| Pstr_open open_description ->
print_open_description ~state open_description cmt_tbl
| Pstr_modtype mod_type_decl ->
print_module_type_declaration ~state mod_type_decl cmt_tbl
| Pstr_module module_binding ->
print_module_binding ~state ~is_rec:false module_binding cmt_tbl 0
| Pstr_recmodule module_bindings ->
print_listi
~get_loc:(fun mb -> mb.Parsetree.pmb_loc)
~nodes:module_bindings
~print:(print_module_binding ~state ~is_rec:true)
cmt_tbl
| Pstr_exception extension_constructor ->
print_exception_def ~state extension_constructor cmt_tbl
| Pstr_typext type_extension ->
print_type_extension ~state type_extension cmt_tbl
| Pstr_class _ | Pstr_class_type _ -> Doc.nil
and print_type_extension ~state (te : Parsetree.type_extension) cmt_tbl =
let prefix = Doc.text "type " in
let name = print_lident_path te.ptyext_path cmt_tbl in
let type_params = print_type_params ~state te.ptyext_params cmt_tbl in
let extension_constructors =
let ecs = te.ptyext_constructors in
let force_break =
match (ecs, List.rev ecs) with
| first :: _, last :: _ ->
first.pext_loc.loc_start.pos_lnum > te.ptyext_path.loc.loc_end.pos_lnum
|| first.pext_loc.loc_start.pos_lnum < last.pext_loc.loc_end.pos_lnum
| _ -> false
in
let private_flag =
match te.ptyext_private with
| Asttypes.Private -> Doc.concat [Doc.text "private"; Doc.line]
| Public -> Doc.nil
in
let rows =
print_listi
~get_loc:(fun n -> n.Parsetree.pext_loc)
~print:(print_extension_constructor ~state)
~nodes:ecs ~force_break cmt_tbl
in
Doc.breakable_group ~force_break
(Doc.indent
(Doc.concat
[
Doc.line;
private_flag;
rows;
(* Doc.join ~sep:Doc.line ( *)
(* List.mapi printExtensionConstructor ecs *)
(* ) *)
]))
in
Doc.group
(Doc.concat
[
print_attributes ~state ~loc:te.ptyext_path.loc te.ptyext_attributes
cmt_tbl;
prefix;
name;
type_params;
Doc.text " +=";
extension_constructors;
])
and print_module_binding ~state ~is_rec module_binding cmt_tbl i =
let prefix =
if i = 0 then
Doc.concat
[Doc.text "module "; (if is_rec then Doc.text "rec " else Doc.nil)]
else Doc.text "and "
in
let mod_expr_doc, mod_constraint_doc =
match module_binding.pmb_expr with
| {pmod_desc = Pmod_constraint (mod_expr, mod_type)}
when not
(ParsetreeViewer.has_await_attribute
module_binding.pmb_expr.pmod_attributes) ->
( print_mod_expr ~state mod_expr cmt_tbl,
Doc.concat [Doc.text ": "; print_mod_type ~state mod_type cmt_tbl] )
| mod_expr -> (print_mod_expr ~state mod_expr cmt_tbl, Doc.nil)
in
let mod_expr_doc_parens =
if Parens.mod_expr_parens module_binding.pmb_expr then
Doc.concat [Doc.lparen; mod_expr_doc; Doc.rparen]
else mod_expr_doc
in
let mod_name =
let doc = Doc.text module_binding.pmb_name.Location.txt in
print_comments doc cmt_tbl module_binding.pmb_name.loc
in
let doc =
Doc.concat
[
print_attributes ~state ~loc:module_binding.pmb_name.loc
module_binding.pmb_attributes cmt_tbl;
prefix;
mod_name;
mod_constraint_doc;
Doc.text " = ";
mod_expr_doc_parens;
]
in
print_comments doc cmt_tbl module_binding.pmb_loc
and print_module_type_declaration ~state
(mod_type_decl : Parsetree.module_type_declaration) cmt_tbl =
let mod_name =
let doc = Doc.text mod_type_decl.pmtd_name.txt in
print_comments doc cmt_tbl mod_type_decl.pmtd_name.loc
in
Doc.concat
[
print_attributes ~state mod_type_decl.pmtd_attributes cmt_tbl;
Doc.text "module type ";
mod_name;
(match mod_type_decl.pmtd_type with
| None -> Doc.nil
| Some mod_type ->
Doc.concat [Doc.text " = "; print_mod_type ~state mod_type cmt_tbl]);
]
and print_mod_type ~state mod_type cmt_tbl =
let mod_type_doc =
match mod_type.pmty_desc with
| Parsetree.Pmty_ident longident ->
Doc.concat
[
print_attributes ~state ~loc:longident.loc mod_type.pmty_attributes
cmt_tbl;
print_longident_location longident cmt_tbl;
]
| Pmty_signature [] ->
if has_comments_inside cmt_tbl mod_type.pmty_loc then
let doc = print_comments_inside cmt_tbl mod_type.pmty_loc in
Doc.concat [Doc.lbrace; doc; Doc.rbrace]
else
let should_break =
mod_type.pmty_loc.loc_start.pos_lnum
< mod_type.pmty_loc.loc_end.pos_lnum
in
Doc.breakable_group ~force_break:should_break
(Doc.concat [Doc.lbrace; Doc.soft_line; Doc.soft_line; Doc.rbrace])
| Pmty_signature signature ->
let signature_doc =
Doc.breakable_group ~force_break:true
(Doc.concat
[
Doc.lbrace;
Doc.indent
(Doc.concat
[Doc.line; print_signature ~state signature cmt_tbl]);
Doc.line;
Doc.rbrace;
])
in
Doc.concat
[
print_attributes ~state mod_type.pmty_attributes cmt_tbl; signature_doc;
]
| Pmty_functor _ ->
let parameters, return_type = ParsetreeViewer.functor_type mod_type in
let parameters_doc =
match parameters with
| [] -> Doc.nil
| [(attrs, {Location.txt = "_"; loc}, Some mod_type)] ->
let cmt_loc =
{loc with loc_end = mod_type.Parsetree.pmty_loc.loc_end}
in
let attrs = print_attributes ~state attrs cmt_tbl in
let doc =
Doc.concat [attrs; print_mod_type ~state mod_type cmt_tbl]
in
print_comments doc cmt_tbl cmt_loc
| params ->
Doc.group
(Doc.concat
[
Doc.lparen;
Doc.indent
(Doc.concat
[
Doc.soft_line;
Doc.join
~sep:(Doc.concat [Doc.comma; Doc.line])
(List.map
(fun (attrs, lbl, mod_type) ->
let cmt_loc =
match mod_type with
| None -> lbl.Asttypes.loc
| Some mod_type ->
{
lbl.Asttypes.loc with
loc_end =
mod_type.Parsetree.pmty_loc.loc_end;
}
in
let attrs =
print_attributes ~state attrs cmt_tbl
in
let lbl_doc =
if lbl.Location.txt = "_" || lbl.txt = "*" then
Doc.nil
else
let doc = Doc.text lbl.txt in
print_comments doc cmt_tbl lbl.loc
in
let doc =
Doc.concat
[
attrs;
lbl_doc;
(match mod_type with
| None -> Doc.nil
| Some mod_type ->
Doc.concat
[
(if lbl.txt = "_" then Doc.nil
else Doc.text ": ");
print_mod_type ~state mod_type
cmt_tbl;
]);
]
in
print_comments doc cmt_tbl cmt_loc)
params);
]);
Doc.trailing_comma;
Doc.soft_line;
Doc.rparen;
])
in
let return_doc =
let doc = print_mod_type ~state return_type cmt_tbl in
if Parens.mod_type_functor_return return_type then add_parens doc
else doc
in
Doc.group
(Doc.concat
[
parameters_doc;
Doc.group (Doc.concat [Doc.text " =>"; Doc.line; return_doc]);
])
| Pmty_typeof mod_expr ->
Doc.concat
[Doc.text "module type of "; print_mod_expr ~state mod_expr cmt_tbl]
| Pmty_extension extension ->
print_extension ~state ~at_module_lvl:false extension cmt_tbl
| Pmty_alias longident ->
Doc.concat
[Doc.text "module "; print_longident_location longident cmt_tbl]
| Pmty_with (mod_type, with_constraints) ->
let operand =
let doc = print_mod_type ~state mod_type cmt_tbl in
if Parens.mod_type_with_operand mod_type then add_parens doc else doc
in
Doc.group
(Doc.concat
[
operand;
Doc.indent
(Doc.concat
[
Doc.line;
print_with_constraints ~state with_constraints cmt_tbl;
]);
])
in
let attrs_already_printed =
match mod_type.pmty_desc with
| Pmty_functor _ | Pmty_signature _ | Pmty_ident _ -> true
| _ -> false
in
let doc =
Doc.concat
[
(if attrs_already_printed then Doc.nil
else print_attributes ~state mod_type.pmty_attributes cmt_tbl);
mod_type_doc;
]
in
print_comments doc cmt_tbl mod_type.pmty_loc
and print_with_constraints ~state with_constraints cmt_tbl =
let rows =
List.mapi
(fun i with_constraint ->
Doc.group
(Doc.concat
[
(if i == 0 then Doc.text "with " else Doc.text "and ");
print_with_constraint ~state with_constraint cmt_tbl;
]))
with_constraints
in
Doc.join ~sep:Doc.line rows
and print_with_constraint ~state (with_constraint : Parsetree.with_constraint)
cmt_tbl =
match with_constraint with
(* with type X.t = ... *)
| Pwith_type (longident, type_declaration) ->
Doc.group
(print_type_declaration ~state
~name:(print_lident_path longident cmt_tbl)
~equal_sign:"=" ~rec_flag:Doc.nil 0 type_declaration CommentTable.empty)
(* with module X.Y = Z *)
| Pwith_module ({txt = longident1}, {txt = longident2}) ->
Doc.concat
[
Doc.text "module ";
print_longident longident1;
Doc.text " =";
Doc.indent (Doc.concat [Doc.line; print_longident longident2]);
]
(* with type X.t := ..., same format as [Pwith_type] *)
| Pwith_typesubst (longident, type_declaration) ->
Doc.group
(print_type_declaration ~state
~name:(print_lident_path longident cmt_tbl)
~equal_sign:":=" ~rec_flag:Doc.nil 0 type_declaration
CommentTable.empty)
| Pwith_modsubst ({txt = longident1}, {txt = longident2}) ->
Doc.concat
[
Doc.text "module ";
print_longident longident1;
Doc.text " :=";
Doc.indent (Doc.concat [Doc.line; print_longident longident2]);
]
and print_signature ~state signature cmt_tbl =
match signature with
| [] -> print_comments_inside_file cmt_tbl
| signature ->
print_list
~get_loc:(fun s -> s.Parsetree.psig_loc)
~nodes:signature
~print:(print_signature_item ~state)
cmt_tbl
and print_signature_item ~state (si : Parsetree.signature_item) cmt_tbl =
match si.psig_desc with
| Parsetree.Psig_value value_description ->
print_value_description ~state value_description cmt_tbl
| Psig_type (rec_flag, type_declarations) ->
let rec_flag =
match rec_flag with
| Asttypes.Nonrecursive -> Doc.nil
| Asttypes.Recursive -> Doc.text "rec "
in
print_type_declarations ~state ~rec_flag type_declarations cmt_tbl
| Psig_typext type_extension ->
print_type_extension ~state type_extension cmt_tbl
| Psig_exception extension_constructor ->
print_exception_def ~state extension_constructor cmt_tbl
| Psig_module module_declaration ->
print_module_declaration ~state module_declaration cmt_tbl
| Psig_recmodule module_declarations ->
print_rec_module_declarations ~state module_declarations cmt_tbl
| Psig_modtype mod_type_decl ->
print_module_type_declaration ~state mod_type_decl cmt_tbl
| Psig_open open_description ->
print_open_description ~state open_description cmt_tbl
| Psig_include include_description ->
print_include_description ~state include_description cmt_tbl
| Psig_attribute attr ->
fst (print_attribute ~state ~standalone:true attr cmt_tbl)
| Psig_extension (extension, attrs) ->
Doc.concat
[
print_attributes ~state attrs cmt_tbl;
Doc.concat
[print_extension ~state ~at_module_lvl:true extension cmt_tbl];
]
| Psig_class _ | Psig_class_type _ -> Doc.nil
and print_rec_module_declarations ~state module_declarations cmt_tbl =
print_listi
~get_loc:(fun n -> n.Parsetree.pmd_loc)
~nodes:module_declarations
~print:(print_rec_module_declaration ~state)
cmt_tbl
and print_rec_module_declaration ~state md cmt_tbl i =
let body =
match md.pmd_type.pmty_desc with
| Parsetree.Pmty_alias longident ->
Doc.concat [Doc.text " = "; print_longident_location longident cmt_tbl]
| _ ->
let needs_parens =
match md.pmd_type.pmty_desc with
| Pmty_with _ -> true
| _ -> false
in
let mod_type_doc =
let doc = print_mod_type ~state md.pmd_type cmt_tbl in
if needs_parens then add_parens doc else doc
in