Release of OCamlFormat 0.9
We are pleased to announce the release of OCamlFormat (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.8.
Additional dependencies
OCamlFormat now requires:
- ocaml >= 4.06 (up from 4.04.1)
- dune >= 1.1.1
- octavius >= 1.2.0
- uutf
OCamlFormat_Reason now requires:
- ocaml >= 4.06
- dune >= 1.1.1
- ocaml-migrate-parsetree >= 1.0.10 (up from 1.0.6)
- octavius >= 1.2.0
- uutf
- reason >= 3.2.0 (up from 1.13.4)
New preset profiles
The ocamlformat
profile aims to take advantage of the strengths of a parsetree-based auto-formatter, and to limit the consequences of the weaknesses imposed by the current implementation. This is a style which optimizes for what the formatter can do best, rather than to match the style of any existing code.
General guidelines that have directed the design include:
- Legibility, in the sense of making it as hard as possible for quick visual parsing to give the wrong interpretation, is of highest priority;
- Whenever possible the high-level structure of the code should be obvious by looking only at the left margin, in particular, it should not be necessary to visually jump from left to right hunting for critical keywords, tokens, etc;
- All else equal compact code is preferred as reading without scrolling is easier, so indentation or white space is avoided unless it helps legibility;
- Attention has been given to making some syntactic gotchas visually obvious.
ocamlformat
is the new default profile.
The conventional
profile aims to be as familiar and “conventional” appearing as the available options allow.
The default
profile is ocamlformat
with break-cases=fit
. default
is deprecated and will be removed in version 0.10.
OCamlFormat diff tool
ocamlformat-diff
is a tool that uses OCamlFormat to apply the same formatting to compared OCaml files, so that the formatting differences between the two files are not displayed.
Note that ocamlformat-diff
comes in a separate opam package and is not included in the ocamlformat
package.
The file comparison is then performed by any diff backend. The options’ documentation is available through ocamlformat-diff --help
. The option --diff
allows you to configure the diff command that is used to compare the formatted files. The default value is the vanilla diff
, but you can also use patdiff
or any other similar comparison tool.
ocamlformat-diff
can be integrated with git diff
, as explained in the online documentation.
Formatting docstrings
Previously, the docstrings (** This is a docstring *)
could only be formatted like regular comments, a new option --parse-docstrings
has been added so that docstrings can be nicely formatted.
Here is a small example:
(** {1 Printers and escapes used by Cmdliner module} *)
val subst_vars : subst:(string -> string option) -> Buffer.t -> string -> string
(** [subst b ~subst s], using [b], substitutes in [s] variables of the form
"$(doc)" by their [subst] definition. This leaves escapes and markup
directives $(markup,...) intact.
@raise Invalid_argument in case of illegal syntax. *)
Note that this option is disabled by default and you have to set it manually by adding --parse-docstrings
to your command line or parse-docstrings=true
to your .ocamlformat
file.
If you get the following error message:
Error: Formatting of (** ... *) is unstable (e.g. parses as a list or not depending on the margin), please tighten up this comment in the source or disable the formatting using the option --no-parse-docstrings.
It means the original docstring cannot be formatted (e.g. because it does not comply with the odoc syntax) and you have to edit it or disable the formatting of docstrings. Of course if you think your docstring complies with the odoc syntax and there might be a bug in OCamlFormat, feel free to file an issue on github.
Print the configuration
The new --print-config
flag prints the configuration determined by the environment variable, the configuration files, preset profiles and command line. Attributes are not considered. It provides the full list of options with the values they are set to, and the source of this value.
For example ocamlformat --print-config
prints:
profile=ocamlformat (file .ocamlformat:1)
quiet=false (profile ocamlformat (file .ocamlformat:1))
max-iters=10 (profile ocamlformat (file .ocamlformat:1))
comment-check=true (profile ocamlformat (file .ocamlformat:1))
wrap-fun-args=true (profile ocamlformat (file .ocamlformat:1))
wrap-comments=true (file .ocamlformat:5)
type-decl=compact (profile ocamlformat (file .ocamlformat:1))
space-around-collection-expressions=false (profile ocamlformat (file .ocamlformat:1))
single-case=compact (profile ocamlformat (file .ocamlformat:1))
sequence-style=separator (profile ocamlformat (file .ocamlformat:1))
parse-docstrings=true (file .ocamlformat:4)
parens-tuple-patterns=multi-line-only (profile ocamlformat (file .ocamlformat:1))
parens-tuple=always (profile ocamlformat (file .ocamlformat:1))
parens-ite=false (profile ocamlformat (file .ocamlformat:1))
ocp-indent-compat=false (profile ocamlformat (file .ocamlformat:1))
module-item-spacing=sparse (profile ocamlformat (file .ocamlformat:1))
margin=77 (file .ocamlformat:3)
let-open=preserve (profile ocamlformat (file .ocamlformat:1))
let-binding-spacing=compact (profile ocamlformat (file .ocamlformat:1))
let-and=compact (profile ocamlformat (file .ocamlformat:1))
leading-nested-match-parens=false (profile ocamlformat (file .ocamlformat:1))
infix-precedence=indent (profile ocamlformat (file .ocamlformat:1))
indicate-nested-or-patterns=space (profile ocamlformat (file .ocamlformat:1))
indicate-multiline-delimiters=true (profile ocamlformat (file .ocamlformat:1))
if-then-else=compact (profile ocamlformat (file .ocamlformat:1))
field-space=tight (profile ocamlformat (file .ocamlformat:1))
extension-sugar=preserve (profile ocamlformat (file .ocamlformat:1))
escape-strings=preserve (profile ocamlformat (file .ocamlformat:1))
escape-chars=preserve (profile ocamlformat (file .ocamlformat:1))
doc-comments-tag-only=default (profile ocamlformat (file .ocamlformat:1))
doc-comments-padding=2 (profile ocamlformat (file .ocamlformat:1))
doc-comments=after (profile ocamlformat (file .ocamlformat:1))
disable=false (profile ocamlformat (file .ocamlformat:1))
cases-exp-indent=4 (profile ocamlformat (file .ocamlformat:1))
break-struct=force (profile ocamlformat (file .ocamlformat:1))
break-string-literals=wrap (profile ocamlformat (file .ocamlformat:1))
break-sequences=false (profile ocamlformat (file .ocamlformat:1))
break-separators=before (profile ocamlformat (file .ocamlformat:1))
break-infix-before-func=true (profile ocamlformat (file .ocamlformat:1))
break-infix=wrap (profile ocamlformat (file .ocamlformat:1))
break-fun-decl=wrap (profile ocamlformat (file .ocamlformat:1))
break-collection-expressions=fit-or-vertical (profile ocamlformat (file .ocamlformat:1))
break-cases=fit (file .ocamlformat:2)
If many input files are specified, only print the configuration for the first file. If no input file is specified, print the configuration for the root directory if specified, or for the current working directory otherwise.
Parentheses around if-then-else branches
A new option parens-ite
has been added to decide whether to use parentheses around if-then-else branches that spread across multiple lines.
If this option is set, the following function:
let rec loop count a =
if count >= self#len
then a
else
let a' = f cur#get count a in
cur#incr ();
loop (count + 1) a'
will be formatted as:
let rec loop count a =
if count >= self#len
then a
else (
let a' = f cur#get count a in
cur#incr ();
loop (count + 1) a' )
Parentheses around tuple patterns
A new option parens-tuple-patterns
has been added, that mimics parens-tuple
but only applies to patterns, whereas parens-tuples
only applies to expressions. parens-tuple-patterns=multi-line-only
mode will try to skip parentheses for single-line tuple patterns,
this is the default value. parens-tuple-patterns=always
always uses parentheses around tuples patterns.
For example:
(* with parens-tuple-patterns=always *)
let (a, b) = (1, 2)
(* with parens-tuple-patterns=multi-line-only *)
let a, b = (1, 2)
Single-case pattern-matching expressions
The new option single-case
defines the style of pattern-matching expressions with only a single case. single-case=compact
will try to format a single case on a single line, this is the default value. single-case=sparse
will always break the line before a single case.
For example:
(* with single-case=compact *)
try some_irrelevant_expression
with Undefined_recursive_module _ -> true
(* with single-case=sparse *)
try some_irrelevant_expression
with
| Undefined_recursive_module _ -> true
Space around collection expressions
The new option space-around-collection-expressions
decides whether to add a space
inside the delimiters of collection expressions (lists, arrays, records).
For example:
(* by default *)
type wkind = {f : 'a. 'a tag -> 'a kind}
let l = ["Nil", TCnoarg Thd; "Cons", TCarg (Ttl Thd, tcons)]
(* with space-around-collection-expressions *)
type wkind = { f : 'a. 'a tag -> 'a kind }
let l = [ "Nil", TCnoarg Thd; "Cons", TCarg (Ttl Thd, tcons) ]
Break separators
The new option break-separators
decides whether to break before or after separators such as ;
in list or record expressions, *
in tuples or ->
in arrow types. break-separators=before
breaks the expressions before the separator, this is the default value.
break-separators=after
breaks the expressions after the separator. break-separators=after-and-docked
breaks the expressions after the separator and docks the brackets for records.
For example:
(* with break-separators=before *)
type t =
{ foooooooooooooooooooooooo: foooooooooooooooooooooooooooooooooooooooo
; fooooooooooooooooooooooooooooo: fooooooooooooooooooooooooooo }
(* with break-separators=after *)
type t =
{ foooooooooooooooooooooooo: foooooooooooooooooooooooooooooooooooooooo;
fooooooooooooooooooooooooooooo: fooooooooooooooooooooooooooo }
(* with break-separators=after-and-docked *)
type t = {
foooooooooooooooooooooooo: foooooooooooooooooooooooooooooooooooooooo;
fooooooooooooooooooooooooooooo: fooooooooooooooooooooooooooo
}
Not breaking before bind/map operators
The new option break-infix-before-func
decides whether to break infix operators
whose right arguments are anonymous functions specially. This option is set by default, if you disable it with --no-break-infix-before-func
, it will not break before the operator so that the first line of the function appears docked at the end of line after the operator.
For example:
(* by default *)
f x
>>= fun y ->
g y
>>= fun () ->
f x >>= fun y -> g y >>= fun () -> f x >>= fun y -> g y >>= fun () -> y ()
(* with break-infix-before-func = false *)
f x >>= fun y ->
g y >>= fun () ->
f x >>= fun y -> g y >>= fun () -> f x >>= fun y -> g y >>= fun () -> y ()
Break toplevel cases
There is a new value for the break-cases
option: toplevel
, that forces top-level cases (i.e. not nested or-patterns) to break across lines, otherwise breaks naturally at the margin.
For example:
let f =
let g = function
| H when x y <> k -> 2
| T | P | U -> 3
in
fun x g t h y u ->
match x with
| E -> 4
| Z | P | M -> (
match y with
| O -> 5
| P when h x -> (
function
| A -> 6 ) )
Number of spaces before docstrings
The new option doc-comments-padding
controls how many spaces are printed before doc comments in type declarations. The default value is 2.
For example:
(* with doc-comments-padding = 2 *)
type t = {a: int (** a *); b: int (** b *)}
(* with doc-comments-padding = 1 *)
type t = {a: int (** a *); b: int (** b *)}
Ignore files
An .ocamlformat-ignore
file specifies files that OCamlFormat should ignore. Each line in an .ocamlformat-ignore
file specifies a filename relative to the directory containing the .ocamlformat-ignore
file. Lines starting with #
are ignored and can be used as comments.
Here is an example of such .ocamlformat-ignore
file:
#This is a comment
dir2/ignore_1.ml
Tag-only docstrings
The new option doc-comments-tag-only
controls the position of doc comments only containing tags. doc-comments-tag-only=default
means no special treatment is done, this is the default value. doc-comments-tag-only=fit
puts doc comments on the same line if it fits.
For example:
(* with doc-comments-tag-only = default *)
(** @deprecated *)
open Module
(* with doc-comments-tag-only = fit *)
open Module (** @deprecated *)
Fit or vertical mode for if-then-else
There is a new value for the option if-then-else
: fit-or-vertical
. fit-or-vertical
vertically breaks all branches if they do not fit on a single line. Compared to the compact
(default) value, it breaks all branches if at least one of them does not fit on a single line.
For example:
(* with if-then-else = compact *)
let _ =
if foo then
let a = 1 in
let b = 2 in
a + b
else if foo then 12
else 0
(* with if-then-else = fit-or-vertical *)
let _ =
if foo then
let a = 1 in
let b = 2 in
a + b
else if foo then
12
else
0
Check mode
A new --check
flag has been added. It checks whether the input files already are formatted. This flag is mutually exclusive with --inplace
and --output
. It returns 0
if the input files are indeed already formatted, or 1
otherwise.
Break function declarations
The new option break-fun-decl
controls the style for function declarations and types. break-fun-decl=wrap
breaks only if necessary, this is the default value. break-fun-decl=fit-or-vertical
vertically breaks arguments if they do not fit on a single line. break-fun-decl=smart
is like fit-or-vertical
but try to fit arguments on their line if they fit. The wrap-fun-args
option now only controls the style for function calls, and no more for function declarations.
For example:
(* with break-fun-decl = wrap *)
let ffffffffffffffffffff aaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccc =
g
(* with break-fun-decl = fit-or-vertical *)
let ffffffffffffffffffff
aaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccc =
g
(* with break-fun-decl = smart *)
let ffffffffffffffffffff
aaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccc =
g
Disable configuration in files and attributes
Two new options have been added so that .ocamlformat
configuration files and attributes in OCaml files do not change the configuration. These options can be useful if you use some preset profile and you do not want attributes and .ocamlformat
files to interfere with your preset configuration. --disable-conf-attrs
disables the configuration in attributes, and --disable-conf-files
disables .ocamlformat
configuration files.
Preserve module items spacing
There is a new value for the option module-item-spacing
: preserve
, that will not leave open lines between one-liners of similar sorts unless there is an open line in the input.
For example the line breaks are preserved in the following code:
let cmos_rtc_seconds = 0x00
let cmos_rtc_seconds_alarm = 0x01
let cmos_rtc_minutes = 0x02
let x = o
let log_other = 0x000001
let log_cpu = 0x000002
let log_fpu = 0x000004
Breaking changes
- When
--disable-outside-detected-project
is set, disable ocamlformat when no.ocamlformat
file is found. - Files are not parsed when ocamlformat is disabled.
- Disallow
-
with other input files. - The
wrap-fun-args
option now only controls the style for function calls, and no more for function declarations. - The default profile is now named
ocamlformat
. - The deprecated syntax for
.ocamlformat
files:option value
is no more supported anymore and you should use theoption = value
syntax instead.
Miscellaneous bugfixes
- Preserve shebang (e.g.
#!/usr/bin/env ocaml
) at the beginning of a file. - Improve the formatting when
ocp-indent-compat
is set. - UTF8 characters are now correctly printed in comments.
- Add parentheses around a constrained any-pattern (e.g.
let (_ : int) = x1
). - Emacs: the temporary buffer is now killed.
- Emacs: add the keybinding in tuareg’s map instead of merlin’s.
- Lots of improvements on the comments, docstrings, attributes formatting.
- Lots of improvements on the formatting of modules.
- Lots of improvements in the Reason support.
- Do not rely on the file-system to format sources.
- The
--debug
mode is more user-friendly.
Credits
This release also contains many other changes and bug fixes that we cannot detail here.
Special thanks to our maintainers and contributors for this release: Jules Aguillon, Mathieu Barbin, Josh Berdine, Jérémie Dimino, Hugo Heuzard, Ludwig Pacifici, Guillaume Petiot, Nathan Rebours and Louis Roché.
If you wish to get involved with OCamlFormat development or file an issue, please read the contributing guide, any contribution is welcomed.