We are pleased to announce the release of OCamlFormat 0.10 (available on opam).
There have been numerous changes since the last release, so here is a comprehensive list of the new features and breaking changes to help the transition from OCamlFormat 0.9.
ocamlformat-0.10
now works on the 4.08 AST, although the formatting should not differ greatly from the one of ocamlformat-0.9
in this regard.
Please note that it is necessary to build ocamlformat
with 4.08 to be able to parse new features like let*
.
Upgrading from ocamlformat-0.9
requires to install the following dependencies:
- ocaml-migrate-parsetree >= 1.3.1 (upgrade)
- uuseg >= 10.0.0 (new)
- uutf >= 1.0.1 (upgrade)
This release focuses on preserving the style of the original source and on handling more ocp-indent
options.
Style preservation
Expression grouping
The new option exp-grouping
has been added to preserve the keywords begin
/end
that are used to delimit expressions instead of parentheses. exp-grouping=parens
always uses parentheses to delimit expressions. exp-grouping=preserve
preserves the original grouping syntax (parentheses or begin
/end
).
Horizontal alignment
Horizontal alignment is something that users often use to make pattern-matching or type declarations easier to read, and it is a feature that has been requested many times. Three new options have been added to horizontally align the lines.
align-cases
horizontally aligns the match/try cases:
let fooooooooooo =
match foooooooooooooooooooooooo with
| Bfooooooooooooooooo -> foooooooooooo
| C (a, b, c, d) -> fooooooooooooooooooo
| _ -> fooooooooooooooooooo
align-constructors-decl
horizontally aligns type declarations:
type t =
| ( :: ) of a * b
| [] of looooooooooooooooooooooooooooooooooooooong_break
align-variants-decl
horizontally aligns variants type declarations:
type x =
[ `Foooooooo of int
| `Fooooooooooooo of int ]
Preserve blank lines in sequences
The new option sequence-blank-line
decides whether a blank line is preserved between expressions of a sequence. sequence-blank-line=compact
will not keep any blank line between expressions of a sequence, this is still the default behavior. sequence-blank-line=preserve
will keep a blank line between two expressions of a sequence if the input contains at least one.
This option can help preserving the readability of the code in this situation:
let foo x y =
do_some_setup y ;
important_function x
Supporting more ocp-indent
options
The long term goal of ocamlformat
is to handle every ocp-indent
option, this release got closer to this goal as the following ocp-indent
options are now supported by ocamlformat
:
- max_indent
- with
- strict_with
- ppx_stritem_ext
- base
- in
- type
Offset added to a new line
The new option max-indent
sets the maximum offset (number of columns) added to a new line in addition to the offset of the previous line. If this offset is set to 2 columns, then each new line can only be indented by 2 columns more in addition to the previous line, for example:
let () =
fooooo
|> List.iter (fun x ->
let x = x $ y in
fooooooooooo x)
This option is equivalent to the max_indent
option of ocp-indent
, and it will be set if max_indent
is set in an .ocp-indent
configuration file.
Indentation of pattern matching cases
The new options funtion-indent
and match-indent
respectively decide the indentation of function cases and the indentation of match/try cases.
These options are equivalent to the with
option of ocp-indent
, and they will be set if with
is set in an ocp-indent
configuration file.
If the indentation is set to 4 columns, cases are formatted like this:
let foooooooo = function
| fooooooooooooooooooooooo -> foooooooooooooooooooooooooo
let foooooooo =
match fooooooooooooooooooooooo with
| fooooooooooooooooooooooo -> foooooooooooooooooooooooooo
The new options function-indent-nested
and match-indent-nested
respectively decide whether the function-indent
and the match-indent
parameters should be applied even when in a sub-block. If these options are set to never
, it only applies function-indent
or match-indent
if the function or match block starts a line. If these options are set to always
, then the indent parameters are always applied. The auto
value applies the indentation parameter when seen fit.
These options are equivalent to the strict_with
option of ocp-indent
, and they will be set if strict_with
is set in an ocp-indent
configuration file.
Indentation inside extension nodes
The new option extension-indent
sets the indentation of items (that are not at structure level) inside extension nodes.
The new option stritem-extension-indent
sets the indentation of structure items inside extension nodes. This option is equivalent to the ppx_stritem_ext
option of ocp-indent
, and it will be set if ppx_stritem_ext
is set in an .ocp-indent
configuration file.
For example if extension-indent
is set to 5 and stritem-extension-indent
is set to 3:
let foo =
[%foooooooooo
fooooooooooooooooooooooooooo foooooooooooooooooooooooooooooooooo
foooooooooooooooooooooooooooo]
[@@foooooooooo
fooooooooooooooooooooooooooo foooooooooooooooooooooooooooooooooo
foooooooooooooooooooooooooooo]
[@@@foooooooooo
fooooooooooooooooooooooooooo foooooooooooooooooooooooooooooooooo
foooooooooooooooooooooooooooo]
Let-binding indentation
The new option let-binding-indent
sets the indentation of let binding expressions if they do not fit on a single line. This option is equivalent to the base
option of ocp-indent
.
The new option indent-after-in
sets the indentation after let ... in
, unless followed by another let
. This option is equivalent to the in
option of ocp-indent
.
The new option type-decl-indent
sets the indentation of type declarations if they do not fit on a single line. This option is equivalent to the type
option of ocp-indent
.
These options will be set if their ocp-indent
counterparts are set in an .ocp-indent
configuration file.
Miscellaneous features
This release also brings some new options, new values for existing features, or corrects erroneous behaviours.
Indicate multiline delimiters
The former indicate-multiline-delimiters
boolean option is now a 3-valued option:
-
indicate-multiline-delimiters=space
(was equivalent totrue
) prints a space inside the delimiter to indicate the matching one is on a different line. -
indicate-multiline-delimiters=no
(was equivalent tofalse
) doesn't do anything special to indicate the closing delimiter. -
indicate-multiline-delimiters=closing-on-separate-line
is the new feature of this option, it makes sure that the closing delimiter is on its own line.
On this example we can see the closing parenthesis delimiting the nested pattern-matchings are on their own line and are aligned with the matching opening parenthesis:
let () =
match v with
| None -> None
| Some x ->
( match x with
| None -> None
| Some x ->
( match x with
| None -> None
| Some x -> x
)
)
Formatting of literal strings
break-string-literals=newlines
now takes into account pretty-printing commands like @,
, @;
and @\n
to produce more readable strings. A new value for this option has been added, break-string-literals=newlines-and-wrap
, to break lines at newlines delimiters (including pretty-printing commands) and also wrap the string literals at the margin.
Here is how break-string-literals=newlines-and-wrap
formats a string:
let fooooooooooo =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \
tempor incididunt ut labore et dolore magna aliqua.@;\
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi \
ut aliquip ex ea commodo consequat.@;\
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum \
dolore eu fugiat nulla pariatur.@;\
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui \
officia deserunt mollit anim id est laborum."
Warning: the break-string-literals
will likely be removed in the next release and the default behavior would be newlines-and-wrap
.
Break before the in
keyword
The new option break-before-in
has been added to decide whether the line should break before the in
keyword of a let
binding. break-before-in=fit-or-vertical
will always break the line before the in
keyword if the whole let
binding does not fit on a single line, it is still the default behavior. break-before-in=auto
will only break the line if the in
keyword does not fit on the previous line.
For example:
let _ =
let short = this is short in
let fooo =
(this is very long) but (the in keyword can fit) on the same line in
foooooo
Indentation of nested pattern-matching
The new option nested-match
defines the style of pattern-matchings nested in the last case of another pattern-matching. nested-match=wrap
wraps the nested pattern-matching with parentheses and adds indentation, this is still the default behavior. nested-match=align
vertically aligns the nested pattern-matching under the encompassing pattern-matching, for example:
let () =
match v with
| None -> None
| Some x ->
match x with
| None -> None
| Some x -> x
The new option cases-matching-exp-indent
decides the indentation of cases right-hand sides which are match
or try
expressions. cases-matching-exp-indent=compact
forces an indentation of 2, unless nested-match
is set to align
and this is the last case of the pattern matching. compact
is the default behavior. cases-matching-exp-indent=normal
indents as it would any other expression.
Whitelist of files to format
A new kind of configuration files is now handled by ocamlformat
: .ocamlformat-enable
files.
If the disable
option is set, an .ocamlformat-enable
file can list the files that ocamlformat
should format even when the disable
option is set. Each line in an .ocamlformat-enable
file specifies a filename relative to the directory containing the .ocamlformat-enable
file.
The .ocamlformat-enable
files are using the same syntax as the .ocamlformat-ignore
files: lines starting with #
are ignored and can be used as comments.
These new configuration files do not contradict the existing .ocamlformat-ignore
files, as .ocamlformat-enable
are only considered when disable
is set, and .ocamlformat-ignore
are only considered when disable
is not set.
Disable outside detected project
The disable-outside-detected-project
option is now set by default.
When the option --enable-outside-detected-project
is not set, .ocamlformat
files outside of the project (including the one in XDG_CONFIG_HOME
) are not read. The project root of an input file is taken to be the nearest ancestor directory that contains a .git or .hg or dune-project file. If no config file is found, formatting is disabled.
Space around collection-expressions
The former option space-around-collection-expressions
that was deciding whether a space should be added inside the delimiters of collection expressions (lists, arrays, records, variants) has been replaced by 4 new options: space-around-arrays
, space-around-lists
, space-around-records
and space-around-variants
, to allow a finer grain customization.
Fit-or-vertical mode for pattern matching
The break-cases
option that decides the shape of pattern matching has a new value fit-or-vertical
. break-cases=fit-or-vertical
tries to fit all or-patterns on the same line, otherwise breaks each or-pattern (they are wrapped in other modes).
For example if this set of or-patterns does not fit on a single line, we get the following output:
let ffffff =
match foooooooooooo with
| Aaaaaaaaaaaaaaaaa
| Bbbbbbbbbbbbbbbbb
| Ccccccccccccccccc
| Ddddddddddddddddd
| Eeeeeeeeeeeeeeeee -> foooooooooooooooooooo
| Fffffffffffffffff -> fooooooooooooooooo
K&R style for if-then-else
The if-then-else
option now has a new value k-r
that uses parentheses (when necessary) to reproduce a formatting close to the K&R style. For example:
let _ =
if b then (
something loooooooooooooooooooooooooooooooong enough to_trigger a break ;
this is more
) else if b1 then (
something loooooooooooooooooooooooooooooooong enough to_trigger a break ;
this is more
) else
e
Breaking changes
-
the
indicate-multiline-delimiters
option is no longer a boolean option but now has 3 values:space
,no
andclosing-on-separate-line
that are detailed in this patch note. -
the
disable-outside-detected-project
option is now set by default. -
the
default
preset profile has been removed (it was equivalent to theocamlformat
profile withbreak-cases=fit
). -
the
space-around-collection-expressions
option has been replaced by 4 new options:space-around-arrays
,space-around-lists
,space-around-records
andspace-around-variants
.
What's next?
We strongly encourage our users to try out the conventional
preset profile, as we plan to make it the default profile in a future release. This profile's purpose is to reproduce the most commonly encountered styles, and it may be more pleasing to the eye than the current default options.
As stated previously, the break-string-literals
will likely be removed in the next release and the default behavior would be newlines-and-wrap
.
Credits
This release also contains many other changes and bug fixes that we cannot detail here.
We would like to thank our maintainers and contributors for this release: Jules Aguillon, Josh Berdine, Hugo Heuzard, Guillaume Petiot and Thomas Refis, and especially our industrial users Jane Street, Ahrefs and Nomadic Labs that made this work possible by funding this project and providing helpful contributions and feedback.
We would be happy to provide support for more customers, please contact us.
If you wish to get involved with OCamlFormat development or file an issue, please read the contributing guide, any contribution is welcomed.