Skip to content

Commit a2b2800

Browse files
authored
Format comments (#123)
* format comments Signed-off-by: David Vulakh <[email protected]> * dune build @gen_manpage Signed-off-by: David Vulakh <[email protected]> * fix doc comment toggling Signed-off-by: David Vulakh <[email protected]> * add more tests of code-like constructs Signed-off-by: David Vulakh <[email protected]> * fix code-like constructs Signed-off-by: David Vulakh <[email protected]> * add footnote-style lists added lists for numbers only; letters are likely to be code, I think Signed-off-by: David Vulakh <[email protected]> * delete editing artifact Signed-off-by: David Vulakh <[email protected]> * fix \[0\] regression Signed-off-by: David Vulakh <[email protected]> * fix another footnote-related bug [1] at start of line specifically Signed-off-by: David Vulakh <[email protected]> * recognize flower comments more generously Signed-off-by: David Vulakh <[email protected]> --------- Signed-off-by: David Vulakh <[email protected]>
1 parent 7628747 commit a2b2800

File tree

100 files changed

+1423
-785
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+1423
-785
lines changed

doc/manpage_ocamlformat.mld

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@ OPTIONS (CODE FORMATTING STYLE)
334334
--no-parse-toplevel-phrases
335335
Unset parse-toplevel-phrases.
336336

337+
--no-preserve-ambiguous-line-comment
338+
Unset preserve-ambiguous-line-comment.
339+
337340
--no-space-around-arrays
338341
Unset space-around-arrays.
339342

@@ -401,6 +404,11 @@ OPTIONS (CODE FORMATTING STYLE)
401404
Parse and format toplevel phrases and their output. The flag is
402405
unset by default.
403406

407+
--preserve-ambiguous-line-comment
408+
Do not format comments that are one-line long and may contain
409+
whitespace-sensitive code (e.g. strings) The flag is unset by
410+
default.
411+
404412
--sequence-blank-line={preserve-one|compact}
405413
Blank line between expressions of a sequence. preserve will keep a
406414
blank line between two expressions of a sequence if the input

lib/Cmt.ml

Lines changed: 28 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -93,36 +93,6 @@ type decoded_kind =
9393

9494
type decoded = {prefix: string; suffix: string; kind: decoded_kind}
9595

96-
(** [~content_offset] indicates at which column the body of the comment
97-
starts (1-indexed). [~max_idnent] indicates the maximum amount of
98-
indentation to trim. *)
99-
let unindent_lines ?(max_indent = Stdlib.max_int) ~content_offset first_line
100-
tl_lines =
101-
let tl_indent =
102-
List.fold_left ~init:max_indent
103-
~f:(fun acc s ->
104-
Option.value_map ~default:acc ~f:(min acc) (String.indent_of_line s) )
105-
tl_lines
106-
in
107-
(* The indentation of the first line must account for the location of the
108-
comment opening. Don't account for the first line if it's empty.
109-
[fl_trim] is the number of characters to remove from the first line. *)
110-
let fl_trim, fl_indent =
111-
match String.indent_of_line first_line with
112-
| Some i ->
113-
(max 0 (min i (tl_indent - content_offset)), i + content_offset - 1)
114-
| None -> (String.length first_line, max_indent)
115-
in
116-
let min_indent = min tl_indent fl_indent in
117-
let first_line = String.drop_prefix first_line fl_trim in
118-
first_line
119-
:: List.map ~f:(fun s -> String.drop_prefix s min_indent) tl_lines
120-
121-
let unindent_lines ?max_indent ~content_offset txt =
122-
match String.split ~on:'\n' txt with
123-
| [] -> []
124-
| hd :: tl -> unindent_lines ?max_indent ~content_offset hd tl
125-
12696
let is_all_whitespace s = String.for_all s ~f:Char.is_whitespace
12797

12898
let split_asterisk_prefixed =
@@ -147,18 +117,19 @@ let split_asterisk_prefixed =
147117
Some (fst_line :: List.map tl ~f:drop_prefix)
148118
| _ -> None
149119

120+
let ambiguous_line line =
121+
String.contains line '"' || String.contains line '{'
122+
|| String.contains line '}'
123+
150124
let mk ?(prefix = "") ?(suffix = "") kind = {prefix; suffix; kind}
151125

152-
let decode_comment ~parse_comments_as_doc txt loc =
126+
let decode_comment ~parse_comments_as_doc ~preserve_ambiguous_line_comments
127+
txt loc =
153128
let txt =
154129
(* Windows compatibility *)
155130
let f = function '\r' -> false | _ -> true in
156131
String.filter txt ~f
157132
in
158-
let opn_offset =
159-
let {Lexing.pos_cnum; pos_bol; _} = loc.Location.loc_start in
160-
pos_cnum - pos_bol + 1
161-
in
162133
if String.length txt >= 2 then
163134
match txt.[0] with
164135
| '$' when not (Char.is_whitespace txt.[1]) -> mk (Verbatim txt)
@@ -173,15 +144,25 @@ let decode_comment ~parse_comments_as_doc txt loc =
173144
| '=' -> mk (Verbatim txt)
174145
| _ when is_all_whitespace txt ->
175146
mk (Verbatim " ") (* Make sure not to format to [(**)]. *)
176-
| _ when parse_comments_as_doc -> mk (Doc txt)
177-
| _ -> (
147+
| c -> (
178148
let lines =
179-
let content_offset = opn_offset + 2 in
180-
unindent_lines ~content_offset txt
149+
txt |> String.split ~on:'\n'
150+
|> function
151+
| [] -> [] | hd :: tl -> hd :: List.map ~f:String.lstrip tl
181152
in
182-
match split_asterisk_prefixed lines with
183-
| Some deprefixed_lines -> mk (Asterisk_prefixed deprefixed_lines)
184-
| None -> mk (Normal txt) )
153+
match lines with
154+
| [line] when preserve_ambiguous_line_comments && ambiguous_line line
155+
->
156+
mk (Verbatim txt)
157+
| _ -> (
158+
match split_asterisk_prefixed lines with
159+
| Some deprefixed_lines -> mk (Asterisk_prefixed deprefixed_lines)
160+
| None ->
161+
if parse_comments_as_doc then
162+
match c with
163+
| '_' -> mk ~prefix:"_" (Doc (String.subo ~pos:1 txt))
164+
| _ -> mk (Doc txt)
165+
else mk (Normal txt) ) )
185166
else
186167
match txt with
187168
(* "(**)" is not parsed as a docstring but as a regular comment
@@ -197,6 +178,9 @@ let decode_docstring _loc = function
197178
| txt when is_all_whitespace txt -> mk (Verbatim " ")
198179
| txt -> mk ~prefix:"*" (Doc txt)
199180

200-
let decode ~parse_comments_as_doc = function
201-
| Comment {txt; loc} -> decode_comment ~parse_comments_as_doc txt loc
181+
let decode ~parse_comments_as_doc ~preserve_ambiguous_line_comments =
182+
function
183+
| Comment {txt; loc} ->
184+
decode_comment ~parse_comments_as_doc ~preserve_ambiguous_line_comments
185+
txt loc
202186
| Docstring {txt; loc} -> decode_docstring loc txt

lib/Cmt.mli

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,8 @@ type decoded =
4848
; suffix: string (** Just before the closing. *)
4949
; kind: decoded_kind }
5050

51-
val decode : parse_comments_as_doc:bool -> t -> decoded
51+
val decode :
52+
parse_comments_as_doc:bool
53+
-> preserve_ambiguous_line_comments:bool
54+
-> t
55+
-> decoded

lib/Cmts.ml

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -611,38 +611,32 @@ end
611611

612612
module Doc = struct
613613
let fmt ~pro ~epi ~fmt_code conf ~loc txt ~offset =
614-
(* Whether the doc starts and ends with an empty line. *)
615-
let pre_nl, trail_nl =
616-
let lines = String.split ~on:'\n' txt in
617-
match lines with
618-
| [] | [_] -> (false, false)
619-
| h :: _ ->
620-
let l = List.last_exn lines in
621-
(is_only_whitespaces h, is_only_whitespaces l)
614+
let trail_nl =
615+
String.split ~on:'\n' txt |> List.last_exn |> is_only_whitespaces
622616
in
623-
let txt = if pre_nl then String.lstrip txt else txt in
624-
let txt = if trail_nl then String.rstrip txt else txt in
617+
let trail_asterisk = String.is_suffix ~suffix:"*" txt in
618+
let txt = String.rstrip txt in
625619
let parsed = Docstring.parse ~loc ~pro txt in
626620
(* Disable warnings when parsing of code blocks fails. *)
627621
let quiet = Conf_t.Elt.make true `Default in
628622
let conf = {conf with Conf.opr_opts= {conf.Conf.opr_opts with quiet}} in
629-
let doc =
630-
Fmt_odoc.fmt_parsed conf ~actually_a_doc_comment:false ~fmt_code
631-
~input:txt ~offset parsed
632-
in
623+
let doc = Fmt_odoc.fmt_parsed conf ~fmt_code ~input:txt ~offset parsed in
633624
let open Fmt in
634-
hvbox 2
635-
( str pro
636-
$ fmt_if pre_nl "@;<1000 1>"
637-
$ doc
638-
$ fmt_if trail_nl "@;<1000 -2>"
639-
$ epi )
625+
let trailing_space =
626+
if trail_asterisk then noop else fmt_or trail_nl "@;<1000 -2>" " "
627+
in
628+
hvbox 2 (str pro $ doc $ trailing_space $ epi)
640629
end
641630

642631
let fmt_cmt (conf : Conf.t) cmt ~fmt_code =
643632
let open Fmt in
644633
let parse_comments_as_doc = conf.fmt_opts.ocp_indent_compat.v in
645-
let decoded = Cmt.decode ~parse_comments_as_doc cmt in
634+
let preserve_ambiguous_line_comments =
635+
conf.fmt_opts.preserve_ambiguous_line_comments.v
636+
in
637+
let decoded =
638+
Cmt.decode ~parse_comments_as_doc ~preserve_ambiguous_line_comments cmt
639+
in
646640
(* TODO: Offset should be computed from location. *)
647641
let offset = 2 + String.length decoded.prefix in
648642
let pro_str = "(*" ^ decoded.prefix

lib/Conf.ml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ let conventional_profile from =
112112
; type_decl= elt `Compact
113113
; type_decl_indent= elt 2
114114
; wrap_comments= elt false
115+
; preserve_ambiguous_line_comments= elt false
115116
; wrap_docstrings= elt true
116117
; wrap_fun_args= elt true }
117118

@@ -183,6 +184,7 @@ let ocamlformat_profile from =
183184
; type_decl= elt `Compact
184185
; type_decl_indent= elt 2
185186
; wrap_comments= elt false
187+
; preserve_ambiguous_line_comments= elt false
186188
; wrap_docstrings= elt true
187189
; wrap_fun_args= elt true }
188190

@@ -253,6 +255,7 @@ let janestreet_profile from =
253255
; type_decl= elt `Sparse
254256
; type_decl_indent= elt 2
255257
; wrap_comments= elt true
258+
; preserve_ambiguous_line_comments= elt true
256259
; wrap_docstrings= elt true
257260
; wrap_fun_args= elt false }
258261

@@ -1310,6 +1313,19 @@ module Formatting = struct
13101313
(fun conf elt -> update conf ~f:(fun f -> {f with wrap_comments= elt}))
13111314
(fun conf -> conf.fmt_opts.wrap_comments)
13121315

1316+
let preserve_ambiguous_line_comments =
1317+
let doc =
1318+
"Do not format comments that are one-line long and may contain \
1319+
whitespace-sensitive code (e.g. strings)"
1320+
in
1321+
Decl.flag ~default
1322+
~names:["preserve-ambiguous-line-comment"]
1323+
~doc ~kind
1324+
(fun conf elt ->
1325+
update conf ~f:(fun f ->
1326+
{f with preserve_ambiguous_line_comments= elt} ) )
1327+
(fun conf -> conf.fmt_opts.preserve_ambiguous_line_comments)
1328+
13131329
let wrap_fun_args =
13141330
let doc = "Style for function call." in
13151331
let names = ["wrap-fun-args"] in
@@ -1378,6 +1394,7 @@ module Formatting = struct
13781394
; elt type_decl
13791395
; elt type_decl_indent
13801396
; elt wrap_comments
1397+
; elt preserve_ambiguous_line_comments
13811398
; elt wrap_fun_args
13821399
; (* removed options *)
13831400
elt align_cases

lib/Conf_t.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ type fmt_opts =
120120
; type_decl: [`Compact | `Sparse] elt
121121
; type_decl_indent: int elt
122122
; wrap_comments: bool elt
123+
; preserve_ambiguous_line_comments: bool elt
123124
; wrap_docstrings: bool elt
124125
; wrap_fun_args: bool elt }
125126

lib/Conf_t.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ type fmt_opts =
118118
; type_decl: [`Compact | `Sparse] elt
119119
; type_decl_indent: int elt
120120
; wrap_comments: bool elt (** Wrap comments at margin. *)
121+
; preserve_ambiguous_line_comments: bool elt
122+
(** If a comment's contents may contain code whose semantics depend on whitespace, do
123+
not wrap it. *)
121124
; wrap_docstrings: bool elt
122125
; wrap_fun_args: bool elt }
123126

lib/Fmt_ast.ml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -424,13 +424,15 @@ let fmt_parsed_docstring c ~loc ?pro ~epi input parsed =
424424
pos.pos_cnum - pos.pos_bol + 3
425425
and fmt_code = c.fmt_code in
426426
let doc =
427-
if
428-
c.conf.fmt_opts.parse_docstrings.v
429-
&& String.for_all ~f:Char.is_whitespace input
430-
then noop
431-
else
432-
Fmt_odoc.fmt_parsed c.conf ~actually_a_doc_comment:true ~fmt_code
433-
~offset ~input parsed
427+
match input with
428+
| "/*" ->
429+
(* Special-case the form that toggles odoc: [(**/**)] *) str input
430+
| _ ->
431+
if
432+
c.conf.fmt_opts.parse_docstrings.v
433+
&& String.for_all ~f:Char.is_whitespace input
434+
then noop
435+
else Fmt_odoc.fmt_parsed c.conf ~fmt_code ~offset ~input parsed
434436
in
435437
let closing_space =
436438
match parsed with

lib/Fmt_odoc.ml

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,24 @@ let escape_balanced_brackets s =
7474
in
7575
insert_ats s "\\" (brackets_to_escape [] [] 0)
7676

77+
let looks_like_number w =
78+
let w =
79+
match String.chop_suffix w ~suffix:")" with
80+
| Some w -> Some (String.chop_prefix_if_exists w ~prefix:"(")
81+
| None -> (
82+
match String.chop_suffix w ~suffix:"]" with
83+
| Some w -> String.chop_prefix w ~prefix:"["
84+
| None -> String.chop_suffix w ~suffix:"." )
85+
in
86+
match w |> Option.map ~f:String.to_list with
87+
| Some [c] -> Char.is_alphanum c && not Char.(equal c '0')
88+
| Some (leading :: _ as w) ->
89+
List.for_all ~f:Char.is_digit w && not Char.(equal leading '0')
90+
| Some [] | None -> false
91+
7792
let escape_all s =
7893
let escapeworthy = function '{' | '}' | '[' | ']' -> true | _ -> false in
79-
ensure_escape ~escapeworthy s
94+
if looks_like_number s then s else ensure_escape ~escapeworthy s
8095

8196
let split_on_whitespaces =
8297
String.split_on_chars ~on:['\t'; '\n'; '\011'; '\012'; '\r'; ' ']
@@ -211,17 +226,6 @@ let space_elt c : inline_element with_location =
211226
let sp = if c.conf.fmt_opts.wrap_docstrings.v then "" else " " in
212227
Loc.(at (span []) (`Space sp))
213228

214-
let looks_like_number w =
215-
let w =
216-
match String.chop_suffix w ~suffix:")" with
217-
| Some w -> Some (String.chop_prefix_if_exists w ~prefix:"(")
218-
| None -> String.chop_suffix w ~suffix:"."
219-
in
220-
match w |> Option.map ~f:String.to_list with
221-
| Some [c] -> Char.is_alphanum c
222-
| Some (_ :: _ as w) -> List.for_all ~f:Char.is_digit w
223-
| Some [] | None -> false
224-
225229
let non_wrap_space sp = if String.contains sp '\n' then fmt "@\n" else str sp
226230

227231
let rec fmt_inline_elements c elements =
@@ -249,7 +253,10 @@ let rec fmt_inline_elements c elements =
249253
(non_wrap_space sp)
250254
$ aux t
251255
| `Word w :: t ->
252-
fmt_if (String.is_prefix ~prefix:"@" w) "\\"
256+
fmt_if
257+
( String.is_prefix ~prefix:"@" w
258+
&& List.mem Odoc_parser.tag_list ~equal:String.equal w )
259+
"\\"
253260
$ str_normalized c w $ aux t
254261
| `Code_span s :: t -> fmt_code_span s $ aux t
255262
| `Math_span s :: t -> fmt_math_span s $ aux t
@@ -407,8 +414,7 @@ let beginning_offset (conf : Conf.t) input =
407414
whitespace_count
408415
else min whitespace_count 1
409416

410-
let fmt_parsed (conf : Conf.t) ~actually_a_doc_comment ~fmt_code ~input
411-
~offset parsed =
417+
let fmt_parsed (conf : Conf.t) ~fmt_code ~input ~offset parsed =
412418
let open Fmt in
413419
let begin_offset = beginning_offset conf input in
414420
(* The offset is used to adjust the margin when formatting code blocks. *)
@@ -420,9 +426,7 @@ let fmt_parsed (conf : Conf.t) ~actually_a_doc_comment ~fmt_code ~input
420426
str (String.make begin_offset ' ') $ fmt_ast conf ~fmt_code parsed
421427
in
422428
match parsed with
423-
| _ when not (conf.fmt_opts.parse_docstrings.v && actually_a_doc_comment)
424-
->
425-
str input
429+
| _ when not conf.fmt_opts.parse_docstrings.v -> str input
426430
| Ok parsed -> fmt_parsed parsed
427431
| Error msgs ->
428432
if (not conf.opr_opts.quiet.v) && conf.opr_opts.check_odoc_parsing.v

lib/Fmt_odoc.mli

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ val fmt_ast : Conf.t -> fmt_code:fmt_code -> Odoc_parser.Ast.t -> Fmt.t
2222

2323
val fmt_parsed :
2424
Conf.t
25-
-> actually_a_doc_comment:bool
2625
-> fmt_code:fmt_code
2726
-> input:string
2827
-> offset:int

0 commit comments

Comments
 (0)