Skip to content

Add module indent option #2711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@ profile. This started with version 0.26.0.

## unreleased

### Added
### Added

- Support for OCaml 5.4 (#2717, @Julow)

- Added option `module-indent` option (#2711, @HPRIOR) to control the indentation
of items within modules. This affects modules and signatures. For example,
module-indent=4:
```ocaml
module type M = sig
type t

val f : (string * int) list -> int
end
```
### Deprecated

- Starting in this release, ocamlformat can use cmdliner >= 2.0.0. When that is
Expand Down
4 changes: 4 additions & 0 deletions doc/manpage_ocamlformat.mld
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ OPTIONS (CODE FORMATTING STYLE)
the offset of the previous line. The default value is 68. Cannot
be set in attributes.

--module-indent=COLS
Indentation of items within struct ... end and sig ... end (COLS
columns). The default value is 2.

--module-item-spacing={compact|sparse|preserve}
Spacing between items of structures and signatures. compact will
not leave open lines between one-liners of similar sorts. sparse
Expand Down
12 changes: 12 additions & 0 deletions lib/Conf.ml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ let conventional_profile from =
; match_indent= elt 0
; match_indent_nested= elt `Never
; max_indent= elt None
; module_indent= elt 2
; module_item_spacing= elt `Compact
; nested_match= elt `Wrap
; ocp_indent_compat= elt false
Expand Down Expand Up @@ -160,6 +161,7 @@ let ocamlformat_profile from =
; match_indent= elt 0
; match_indent_nested= elt `Never
; max_indent= elt None
; module_indent= elt 2
; module_item_spacing= elt `Sparse
; nested_match= elt `Wrap
; ocp_indent_compat= elt false
Expand Down Expand Up @@ -228,6 +230,7 @@ let janestreet_profile from =
; match_indent= elt 0
; match_indent_nested= elt `Never
; max_indent= elt None
; module_indent= elt 2
; module_item_spacing= elt `Compact
; nested_match= elt `Wrap
; ocp_indent_compat= elt true
Expand Down Expand Up @@ -1063,6 +1066,14 @@ module Formatting = struct
(fun conf elt -> update conf ~f:(fun f -> {f with max_indent= elt}))
(fun conf -> conf.fmt_opts.max_indent)

let module_indent =
let docv = "COLS" in
let doc = "Indentation of items within struct ... end and sig ... end ($(docv) columns)." in
let names = ["module-indent"] in
Decl.int ~names ~default ~doc ~docv ~kind
(fun conf elt -> update conf ~f:(fun f -> {f with module_indent= elt}))
(fun conf -> conf.fmt_opts.module_indent)

let module_item_spacing =
let doc = "Spacing between items of structures and signatures." in
let names = ["module-item-spacing"] in
Expand Down Expand Up @@ -1344,6 +1355,7 @@ module Formatting = struct
; elt match_indent
; elt match_indent_nested
; elt max_indent
; elt module_indent
; elt module_item_spacing
; elt nested_match
; elt ocp_indent_compat
Expand Down
1 change: 1 addition & 0 deletions lib/Conf_t.ml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type fmt_opts =
; match_indent: int elt
; match_indent_nested: [`Always | `Auto | `Never] elt
; max_indent: int option elt
; module_indent: int elt
; module_item_spacing: [`Compact | `Preserve | `Sparse] elt
; nested_match: [`Wrap | `Align] elt
; ocp_indent_compat: bool elt
Expand Down
1 change: 1 addition & 0 deletions lib/Conf_t.mli
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ type fmt_opts =
; match_indent: int elt
; match_indent_nested: [`Always | `Auto | `Never] elt
; max_indent: int option elt
; module_indent: int elt
; module_item_spacing: [`Compact | `Preserve | `Sparse] elt
; nested_match: [`Wrap | `Align] elt
; ocp_indent_compat: bool elt (** Try to indent like ocp-indent *)
Expand Down
10 changes: 6 additions & 4 deletions lib/Fmt_ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3822,8 +3822,9 @@ and fmt_module_type c ?(rec_ = false) ({ast= mty; _} as xmty) =
; pro= Some (before $ str "sig" $ fmt_if empty (str " "))
; psp=
fmt_if (not empty)
( if c.conf.fmt_opts.break_struct.v then break 1000 2
else break 1 2 )
( if c.conf.fmt_opts.break_struct.v then
break 1000 c.conf.fmt_opts.module_indent.v
else break 1 c.conf.fmt_opts.module_indent.v )
; bdy= (within $ if empty then noop else fmt_signature c ctx s)
; cls= noop
; esp=
Expand Down Expand Up @@ -4469,8 +4470,9 @@ and fmt_module_expr ?(dock_struct = true) c ({ast= m; ctx= ctx0} as xmod) =
; pro= Some (before $ str "struct" $ fmt_if empty (str " "))
; psp=
fmt_if (not empty)
( if c.conf.fmt_opts.break_struct.v then break 1000 2
else break 1 2 )
( if c.conf.fmt_opts.break_struct.v then
break 1000 c.conf.fmt_opts.module_indent.v
else break 1 c.conf.fmt_opts.module_indent.v )
; bdy= within $ fmt_structure c ctx sis
; cls= noop
; esp=
Expand Down
3 changes: 3 additions & 0 deletions test/cli/print_config.t
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ No redundant values:
match-indent=0 (profile conventional (file .ocamlformat:1))
match-indent-nested=never (profile conventional (file .ocamlformat:1))
max-indent=68 (profile conventional (file .ocamlformat:1))
module-indent=2 (profile conventional (file .ocamlformat:1))
module-item-spacing=compact (profile conventional (file .ocamlformat:1))
nested-match=wrap (profile conventional (file .ocamlformat:1))
ocp-indent-compat=false (profile conventional (file .ocamlformat:1))
Expand Down Expand Up @@ -142,6 +143,7 @@ Redundant values from the conventional profile:
match-indent=0 (profile conventional (file .ocamlformat:1))
match-indent-nested=never (profile conventional (file .ocamlformat:1))
max-indent=68 (profile conventional (file .ocamlformat:1))
module-indent=2 (profile conventional (file .ocamlformat:1))
module-item-spacing=compact (profile conventional (file .ocamlformat:1))
nested-match=wrap (profile conventional (file .ocamlformat:1))
ocp-indent-compat=false (profile conventional (file .ocamlformat:1))
Expand Down Expand Up @@ -221,6 +223,7 @@ Redundant values from the ocamlformat profile:
match-indent=0 (profile ocamlformat (file .ocamlformat:1))
match-indent-nested=never (profile ocamlformat (file .ocamlformat:1))
max-indent=68 (profile ocamlformat (file .ocamlformat:1))
module-indent=2 (profile ocamlformat (file .ocamlformat:1))
module-item-spacing=sparse (profile ocamlformat (file .ocamlformat:1))
nested-match=wrap (profile ocamlformat (file .ocamlformat:1))
ocp-indent-compat=false (profile ocamlformat (file .ocamlformat:1))
Expand Down
18 changes: 18 additions & 0 deletions test/passing/gen/dune.inc
Original file line number Diff line number Diff line change
Expand Up @@ -3993,6 +3993,24 @@
(package ocamlformat)
(action (diff module_attributes.ml.err module_attributes.ml.stderr)))

(rule
(deps .ocamlformat)
(package ocamlformat)
(action
(with-stdout-to module_indent.ml.stdout
(with-stderr-to module_indent.ml.stderr
(run %{bin:ocamlformat} --name module_indent.ml --margin-check --module-indent=4 %{dep:../tests/module_indent.ml})))))

(rule
(alias runtest)
(package ocamlformat)
(action (diff module_indent.ml.ref module_indent.ml.stdout)))

(rule
(alias runtest)
(package ocamlformat)
(action (diff module_indent.ml.err module_indent.ml.stderr)))

(rule
(deps .ocamlformat)
(package ocamlformat)
Expand Down
Empty file.
66 changes: 66 additions & 0 deletions test/passing/refs.ahrefs/module_indent.ml.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
(* Signature module type with attributes and comments *)
module type M = sig
(** Type of elements *)
type t
[@@ocaml.doc
"Abstract type....................................................."]

(** Function operating on a list of (string * int) *)
val f : (string * int) list -> int
[@@ocaml.doc "Processes a list of (string * int) pairs"]

(** Inner module type *)
module type Inner = sig
type inner
[@@ocaml.doc
"Inner abstract \
type.................................................."]

val f : int -> int
[@@deprecated "Use the outer module’s `f` instead"]
[@@ocaml.doc "Deprecated inner function"]
end
end

(* Module implementing part of M *)
module A : sig
type t

val f : (string * int) list -> int
[@@ocaml.doc "Dummy implementation of M.f"]
end = struct
type t

(* Function with extra params *)
let f s l =
(* ignore params and return dummy result *)
0
end

(* Core module hierarchy with attributes *)
module Core = struct
module Int = struct
module T = struct
type t = int
[@@ocaml.doc
"Int alias with core extensions..........................."]

let compare = compare [@inline always]

let ( + ) x y = x + y
[@@ocaml.doc "Addition for core ints..........................."]
end

include T

(* Map functor application with inline doc *)
module Map = Map.Make (T)
[@@ocaml.doc "Map over core ints......................"]
end

(* Re-export with comment *)
module Std = struct
module Int = Int
[@@ocaml.doc "Standard Int re-export......................"]
end
end
Empty file.
66 changes: 66 additions & 0 deletions test/passing/refs.default/module_indent.ml.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
(* Signature module type with attributes and comments *)
module type M = sig
type t
[@@ocaml.doc
"Abstract type....................................................."]
(** Type of elements *)

val f : (string * int) list -> int
[@@ocaml.doc "Processes a list of (string * int) pairs"]
(** Function operating on a list of (string * int) *)

(** Inner module type *)
module type Inner = sig
type inner
[@@ocaml.doc
"Inner abstract \
type.................................................."]

val f : int -> int
[@@deprecated "Use the outer module’s `f` instead"]
[@@ocaml.doc "Deprecated inner function"]
end
end

(* Module implementing part of M *)
module A : sig
type t

val f : (string * int) list -> int
[@@ocaml.doc "Dummy implementation of M.f"]
end = struct
type t

(* Function with extra params *)
let f s l =
(* ignore params and return dummy result *)
0
end

(* Core module hierarchy with attributes *)
module Core = struct
module Int = struct
module T = struct
type t = int
[@@ocaml.doc
"Int alias with core extensions..........................."]

let compare = compare [@inline always]

let ( + ) x y = x + y
[@@ocaml.doc "Addition for core ints..........................."]
end

include T

(* Map functor application with inline doc *)
module Map = Map.Make (T)
[@@ocaml.doc "Map over core ints......................"]
end

(* Re-export with comment *)
module Std = struct
module Int = Int
[@@ocaml.doc "Standard Int re-export......................"]
end
end
62 changes: 62 additions & 0 deletions test/passing/refs.janestreet/module_indent.ml.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
(* Signature module type with attributes and comments *)
module type M = sig
(** Type of elements *)
type t
[@@ocaml.doc "Abstract type....................................................."]

(** Function operating on a list of (string * int) *)
val f : (string * int) list -> int
[@@ocaml.doc "Processes a list of (string * int) pairs"]

(** Inner module type *)
module type Inner = sig
type inner
[@@ocaml.doc
"Inner abstract type.................................................."]

val f : int -> int
[@@deprecated "Use the outer module’s `f` instead"]
[@@ocaml.doc "Deprecated inner function"]
end
end

(* Module implementing part of M *)
module A : sig
type t

val f : (string * int) list -> int [@@ocaml.doc "Dummy implementation of M.f"]
end = struct
type t

(* Function with extra params *)
let f s l =
(* ignore params and return dummy result *)
0
;;
end

(* Core module hierarchy with attributes *)
module Core = struct
module Int = struct
module T = struct
type t = int
[@@ocaml.doc "Int alias with core extensions..........................."]

let compare = compare [@inline always]

let ( + ) x y = x + y
[@@ocaml.doc "Addition for core ints..........................."]
;;
end

include T

(* Map functor application with inline doc *)
module Map = Map.Make (T) [@@ocaml.doc "Map over core ints......................"]
end

(* Re-export with comment *)
module Std = struct
module Int = Int [@@ocaml.doc "Standard Int re-export......................"]
end
end
Empty file.
Loading