Skip to content

Commit b33c541

Browse files
authored
Fix formatting of comments in "disable" chunks (ocaml-ppx#2279)
1 parent 99a7bc4 commit b33c541

File tree

8 files changed

+176
-90
lines changed

8 files changed

+176
-90
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
- Avoid adding breaks inside `~label:(fun` and base the indentation on the label. (#2271, @Julow)
1010
- Fix non-stabilizing comments attached to private/virtual/mutable keywords (#2272, @gpetiot)
11+
- Fix formatting of comments in "disable" chunks (#2279, @gpetiot)
1112

1213
### Changes
1314

lib/Chunk.ml

Lines changed: 78 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,16 @@
1111

1212
open Extended_ast
1313

14-
type state = Enable | Disable of Location.t
14+
type 'a item =
15+
| Structure : Extended_ast.structure item
16+
| Signature : Extended_ast.signature item
17+
| Use_file : Extended_ast.use_file item
1518

1619
type 'a t =
17-
| Structure : structure t
18-
| Signature : signature t
19-
| Use_file : use_file t
20-
21-
let update_conf ?quiet c l = List.fold ~init:c l ~f:(Conf.update ?quiet)
22-
23-
let disabling (c : Conf.t) attr =
24-
(not c.opr_opts.disable.v)
25-
&& (update_conf ~quiet:true c [attr]).opr_opts.disable.v
26-
27-
let enabling (c : Conf.t) attr =
28-
c.opr_opts.disable.v
29-
&& not (update_conf ~quiet:true c [attr]).opr_opts.disable.v
20+
{ attr_loc: Location.t
21+
; chunk_loc: Location.t
22+
; state: [`Enable | `Disable]
23+
; items: 'a list }
3024

3125
let init_loc =
3226
let pos =
@@ -35,34 +29,84 @@ let init_loc =
3529
in
3630
Location.{loc_ghost= false; loc_start= pos; loc_end= pos}
3731

38-
let is_attr (type a) (fg : a list t) (x : a) =
32+
let is_attr (type a) (fg : a list item) (x : a) =
3933
match (fg, x) with
4034
| Structure, {pstr_desc= Pstr_attribute x; pstr_loc} -> Some (x, pstr_loc)
4135
| Signature, {psig_desc= Psig_attribute x; psig_loc} -> Some (x, psig_loc)
4236
| Use_file, Ptop_def ({pstr_desc= Pstr_attribute x; pstr_loc} :: _) ->
4337
Some (x, pstr_loc)
4438
| _ -> None
4539

46-
let is_state_attr fg ~f c x =
47-
match is_attr fg x with
48-
| Some (attr, loc) when f c attr -> Some loc
40+
let is_state_attr fg ~state x =
41+
let open Option.Monad_infix in
42+
is_attr fg x
43+
>>= fun (attr, loc) ->
44+
Conf.parse_state_attr attr
45+
>>= fun new_state ->
46+
match (state, new_state) with
47+
| `Enable, `Disable -> Some (`Disable, loc)
48+
| `Disable, `Enable -> Some (`Enable, loc)
4949
| _ -> None
5050

51-
let split fg c l =
52-
List.fold_left l ~init:([], c) ~f:(fun (acc, c) x ->
53-
match is_state_attr fg ~f:disabling c x with
54-
| Some loc -> ((Disable loc, [x]) :: acc, Conf.update_state c `Disable)
55-
| None -> (
56-
match is_state_attr fg ~f:enabling c x with
57-
| Some _ -> ((Enable, [x]) :: acc, Conf.update_state c `Enable)
51+
let last_loc (type a) (fg : a list item) (l : a list) =
52+
let open Option.Monad_infix in
53+
match fg with
54+
| Structure -> List.last l >>| fun x -> x.pstr_loc
55+
| Signature -> List.last l >>| fun x -> x.psig_loc
56+
| Use_file -> (
57+
List.last l
58+
>>= function
59+
| Ptop_def x -> List.last x >>| fun x -> x.pstr_loc
60+
| Ptop_dir x -> Some x.pdir_loc )
61+
62+
let mk ~attr_loc ~chunk_loc state items = {attr_loc; chunk_loc; state; items}
63+
64+
let mk_tmp ~loc state items = mk ~attr_loc:loc ~chunk_loc:loc state items
65+
66+
(* Build chunks of each disabled/enabled code spans. The [chunk_loc] of each
67+
chunk has an unprecise ending position that needs to be set after looking
68+
at the following chunk. *)
69+
let split_with_imprecise_locs fg ~state l =
70+
let init = ([], state) in
71+
let chunks, _ =
72+
List.fold_left l ~init ~f:(fun (acc, state) x ->
73+
match is_state_attr fg ~state x with
74+
| Some (state, loc) -> (mk_tmp ~loc state [x] :: acc, state)
5875
| None -> (
5976
match acc with
60-
| [] ->
61-
let chunk =
62-
if c.opr_opts.disable.v then (Disable init_loc, [x])
63-
else (Enable, [x])
64-
in
65-
(chunk :: acc, c)
66-
| (st, h) :: t -> ((st, x :: h) :: t, c) ) ) )
67-
|> fst
68-
|> List.rev_map ~f:(function state, lx -> (state, List.rev lx))
77+
(* first chunk *)
78+
| [] -> (mk_tmp ~loc:init_loc state [x] :: acc, state)
79+
| chunk :: t -> ({chunk with items= x :: chunk.items} :: t, state)
80+
) )
81+
in
82+
List.rev_map chunks ~f:(fun x -> {x with items= List.rev x.items})
83+
84+
(* Extend the [chunk_loc] to make it span until the start of [last_loc]. *)
85+
let extend_end_loc ~last_loc chunk =
86+
let loc_end = last_loc.Location.loc_start in
87+
let chunk_loc = {chunk.chunk_loc with loc_end} in
88+
{chunk with chunk_loc}
89+
90+
(* Update the [chunk_loc] of each chunk by using the loc of the following chunk. *)
91+
let extend_end_locs fg l =
92+
match List.rev l with
93+
| [] -> []
94+
| h :: t ->
95+
(* last chunk *)
96+
let init =
97+
let last_loc =
98+
Option.value (last_loc fg h.items) ~default:h.chunk_loc
99+
in
100+
let chunk_loc = {h.chunk_loc with loc_end= last_loc.loc_end} in
101+
let h = {h with chunk_loc} in
102+
(h.attr_loc, [h])
103+
in
104+
let _, chunks =
105+
List.fold_left t ~init ~f:(fun (last_loc, acc) chunk ->
106+
let chunk = extend_end_loc ~last_loc chunk in
107+
(chunk.attr_loc, chunk :: acc) )
108+
in
109+
chunks
110+
111+
let split ~state fg l =
112+
extend_end_locs fg @@ split_with_imprecise_locs fg ~state l

lib/Chunk.mli

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@
99
(* *)
1010
(**************************************************************************)
1111

12-
type state = Enable | Disable of Location.t
12+
type 'a item =
13+
| Structure : Extended_ast.structure item
14+
| Signature : Extended_ast.signature item
15+
| Use_file : Extended_ast.use_file item
1316

1417
type 'a t =
15-
| Structure : Extended_ast.structure t
16-
| Signature : Extended_ast.signature t
17-
| Use_file : Extended_ast.use_file t
18+
{ attr_loc: Location.t
19+
; chunk_loc: Location.t
20+
; state: [`Enable | `Disable]
21+
; items: 'a list }
1822

19-
val split : 'a list t -> Conf.t -> 'a list -> (state * 'a list) list
23+
val split :
24+
state:[`Enable | `Disable] -> 'a list item -> 'a list -> 'a t list

lib/Conf.ml

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,29 +1523,37 @@ let parse_line config ?(version_check = config.opr_opts.version_check.v)
15231523

15241524
open Parsetree
15251525

1526-
let update ?(quiet = false) c {attr_name= {txt; loc}; attr_payload; _} =
1527-
let result =
1528-
match txt with
1529-
| "ocamlformat" -> (
1530-
match attr_payload with
1531-
| PStr
1532-
[ { pstr_desc=
1533-
Pstr_eval
1534-
( { pexp_desc=
1535-
Pexp_constant
1536-
{pconst_desc= Pconst_string (str, strloc, None); _}
1537-
; pexp_attributes= []
1538-
; _ }
1539-
, [] )
1540-
; _ } ] ->
1541-
parse_line ~from:(`Attribute strloc) c str
1542-
|> Result.map_error ~f:Error.to_string
1543-
| _ -> Error "Invalid format: String expected" )
1544-
| _ when String.is_prefix ~prefix:"ocamlformat." txt ->
1545-
Error
1526+
let parse_attr {attr_name= {txt; loc= _}; attr_payload; _} =
1527+
match txt with
1528+
| "ocamlformat" -> (
1529+
match attr_payload with
1530+
| PStr
1531+
[ { pstr_desc=
1532+
Pstr_eval
1533+
( { pexp_desc=
1534+
Pexp_constant
1535+
{pconst_desc= Pconst_string (str, strloc, None); _}
1536+
; pexp_attributes= []
1537+
; _ }
1538+
, [] )
1539+
; _ } ] ->
1540+
Ok (str, strloc)
1541+
| _ -> Error (`Msg "Invalid format: String expected") )
1542+
| _ when String.is_prefix ~prefix:"ocamlformat." txt ->
1543+
Error
1544+
(`Msg
15461545
(Format.sprintf "Invalid format: Unknown suffix %S"
1547-
(String.chop_prefix_exn ~prefix:"ocamlformat." txt) )
1548-
| _ -> Ok c
1546+
(String.chop_prefix_exn ~prefix:"ocamlformat." txt) ) )
1547+
| _ -> Error `Ignore
1548+
1549+
let update ?(quiet = false) c ({attr_name= {txt; loc}; _} as attr) =
1550+
let result =
1551+
match parse_attr attr with
1552+
| Ok (str, strloc) ->
1553+
parse_line ~from:(`Attribute strloc) c str
1554+
|> Result.map_error ~f:Error.to_string
1555+
| Error (`Msg msg) -> Error msg
1556+
| Error `Ignore -> Ok c
15491557
in
15501558
match result with
15511559
| Ok conf -> conf
@@ -1565,6 +1573,12 @@ let update_state c state =
15651573
in
15661574
{c with opr_opts}
15671575

1576+
let parse_state_attr attr =
1577+
match parse_attr attr with
1578+
| Ok ("enable", _) -> Some `Enable
1579+
| Ok ("disable", _) -> Some `Disable
1580+
| _ -> None
1581+
15681582
let print_config = Decl.print_config options
15691583

15701584
let term = Decl.Store.to_term options

lib/Conf.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ val update_value : t -> name:string -> value:string -> (t, Error.t) Result.t
2323

2424
val update_state : t -> [`Enable | `Disable] -> t
2525

26+
val parse_state_attr : Parsetree.attribute -> [`Enable | `Disable] option
27+
2628
val parse_line :
2729
t
2830
-> ?version_check:bool

lib/Fmt_ast.ml

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4324,43 +4324,46 @@ let fmt_repl_file c _ itms =
43244324
module Chunk = struct
43254325
open Chunk
43264326

4327-
let fmt_item (type a) (fg : a list t) : c -> Ast.t -> a list -> Fmt.t =
4327+
let fmt_item (type a) (fg : a list item) : c -> Ast.t -> a list -> Fmt.t =
43284328
match fg with
43294329
| Structure -> fmt_structure
43304330
| Signature -> fmt_signature
43314331
| Use_file -> fmt_toplevel ?force_semisemi:None
43324332

4333-
let loc_end (type a) (fg : a list t) (l : a list) =
4334-
match fg with
4335-
| Structure -> (List.last_exn l).pstr_loc.loc_end
4336-
| Signature -> (List.last_exn l).psig_loc.loc_end
4337-
| Use_file ->
4338-
let item =
4339-
match List.last_exn l with
4340-
| Ptop_def x -> `Item (List.last_exn x)
4341-
| Ptop_dir x -> `Directive x
4342-
in
4343-
(Ast.location (Tli item)).loc_end
4344-
43454333
let update_conf c state = {c with conf= Conf.update_state c.conf state}
43464334

43474335
let fmt fg c ctx chunks =
4348-
List.foldi chunks ~init:(c, noop) ~f:(fun i (c, output) -> function
4349-
| Disable item_loc, lx ->
4350-
let c = update_conf c `Disable in
4351-
let loc_end = loc_end fg lx in
4352-
let loc = Location.{item_loc with loc_end} in
4353-
( c
4354-
, output
4355-
$ Cmts.fmt_before c item_loc ~eol:(fmt "\n@;<1000 0>")
4356-
$ fmt_if (i > 0) "\n@;<1000 0>"
4357-
$ str (String.strip (Source.string_at c.source loc)) )
4358-
| Enable, lx ->
4359-
let c = update_conf c `Enable in
4360-
(c, output $ fmt_if (i > 0) "@;<1000 0>" $ fmt_item fg c ctx lx) )
4361-
|> snd
4362-
4363-
let split_and_fmt fg c ctx l = fmt fg c ctx @@ split fg c.conf l
4336+
List.foldi chunks ~init:(c, noop, [])
4337+
~f:(fun i (c, output, locs) chunk ->
4338+
let c = update_conf c chunk.state in
4339+
let output, locs =
4340+
match chunk.state with
4341+
| `Disable ->
4342+
let output =
4343+
output
4344+
$ Cmts.fmt_before c chunk.attr_loc ~eol:(fmt "\n@;<1000 0>")
4345+
$ fmt_if (i > 0) "\n@;<1000 0>"
4346+
$ str
4347+
(String.strip
4348+
(Source.string_at c.source chunk.chunk_loc) )
4349+
in
4350+
(output, chunk.chunk_loc :: locs)
4351+
| `Enable ->
4352+
let output =
4353+
output
4354+
$ fmt_if (i > 0) "@;<1000 0>"
4355+
$ fmt_item fg c ctx chunk.items
4356+
in
4357+
(output, locs)
4358+
in
4359+
(c, output, locs) )
4360+
|> fun ((_ : c), output, locs) ->
4361+
List.iter locs ~f:(Cmts.drop_inside c.cmts) ;
4362+
output
4363+
4364+
let split_and_fmt fg c ctx l =
4365+
let state = if c.conf.opr_opts.disable.v then `Disable else `Enable in
4366+
fmt fg c ctx @@ split fg l ~state
43644367
end
43654368

43664369
let fmt_file (type a) ~ctx ~fmt_code ~debug (fragment : a Extended_ast.t)

test/passing/tests/comments_around_disabled.ml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,13 @@ let () =
55
()
66
[@@@ocamlformat "enable"]
77

8+
[@@@ocamlformat "disable"]
9+
(* x *)
10+
(* y *)
11+
let x =
12+
x
13+
(* z *)
14+
15+
[@@@ocamlformat "enable"]
16+
817
(* cmts *)

test/passing/tests/comments_around_disabled.ml.ref

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,12 @@ let () =
55
()
66
[@@@ocamlformat "enable"]
77

8+
[@@@ocamlformat "disable"]
9+
(* x *)
10+
(* y *)
11+
let x =
12+
x
13+
(* z *)
14+
[@@@ocamlformat "enable"]
15+
816
(* cmts *)

0 commit comments

Comments
 (0)