Current section
Files
Jump to
Current section
Files
README.md
# erlfmterlfmt is an opinionated Erlang code formatter. By automating the process offormatting the code, it frees your team up to focus on what the code does,instead of how it looks like.## BeforeRemember reading code before erlfmt and having arguments with co workers :(```erlwhat_is(Erlang) ->case Erlang of movie->[hello(mike,joe,robert),credits]; language->formatting_arguments end.```## AfterNow, with the new erlfmt, code is readable and you get along with your co workers :D```erl formatted demo2what_is(Erlang) -> case Erlang of movie -> [hello(mike, joe, robert), credits]; language -> no_more_formatting_arguments end.```*Disclaimer: erlfmt is just a code formatter, not a solution to all life's problems.*## Line lengtherlfmt enforces a consistent style by parsing your code and re-printing it,while enforcing a selected maximum line-length.For example, this line that exceeds the length limit:```erl unformatted scenarioscenario(dial_phone_number(), ring(), hello(mike),hello(joe), hello(robert), system_working(), seems_to_be())```will be re-printed automatically in a vertical style:```erl formatted scenarioscenario( dial_phone_number(), ring(), hello(mike), hello(joe), hello(robert), system_working(), seems_to_be())```But this snippet:```erl formatted hellohello(mike, joe, robert)```will be kept as-is, since it fits in a single line.## Usage### Rebar3The easiest way to use erlfmt is as a rebar plugin, by adding to your`rebar.config`:```erl formatted rebarconfig1{plugins, [erlfmt]}.```This will provide a new `rebar3 fmt` task. All erlfmt command-line optionscan be configured with defaults in your `rebar.config`, for example:```erl formatted rebarconfig2{erlfmt, [ write, {files, "{src,include,test}/*.{hrl,erl}"}]}.```Now you can format all the files in your project by running:```$ rebar3 fmt```### EscriptAlternatively, you can build a standalone and portable escript and use erlfmtwithout rebar (it still requires Erlang to be installed on the target system).```sh$ rebar3 as release escriptize$ _build/release/bin/erlfmt -h```You can then run it from the command line:```sh$ erlfmt -w './otp/lib/*/{src,include}/*.{erl,hrl}'```## Requirementserlfmt requires Erlang/OTP 21+ and works on all platforms.## Design principlesThe formatter was designed with these main principles in mind:First, the formatter never changes the semantics or structure of the code. Thismeans the input AST and the output AST are equivalent. The formatter does nottry to arbitrarily "improve" the code. For the most part it limits itsbehaviour to shifting whitespace around - it won't rewrite literals, addparentheses, reorder exports, etc.The second principle is to provide as little configuration as possible. Thisremoves contention points and makes it easier to achieve the goal of consistentcode. Instead of providing configuration, the formatter respects a limited setof choices made in the original code to preserve intent and make it easier toachieve beautiful code based on contextual hints.Furthermore, the formatter avoids special cases as much as possible. Forexample, there is no hard-coded behaviour specific to some function - allfunctions are laid out the same. There are some clear layout rules and generalstructures that are re-used as much as possible between different constructs.For example, the general layout of lists, functions, maps, records, andsimilar, all follow the same "container" rules.Finally, the formatter should be idempotent. Formatting the code once shouldproduce the same output as formatting it multiple times.## Manual interventionsIn some cases, the formatter rules might lead to code that looks decent, butnot perfect. Therefore some manual intervention to help the formatter out mightbe needed. For example, given the following code:```erl unformatted split_tokenssplit_tokens([{TokenType, Meta, TokenValue} | Rest], Acc, CAcc) -> split_tokens(Rest, [{TokenType, token_anno(erl_anno:to_term(Meta), #{}), TokenValue} | Acc], CAcc).```Because the line-length is exceeded, the formatter will produce the following:```erl formatted split_tokenssplit_tokens([{TokenType, Meta, TokenValue} | Rest], Acc, CAcc) -> split_tokens( Rest, [{TokenType, token_anno(erl_anno:to_term(Meta), #{}), TokenValue} | Acc], CAcc ).```It might be more desirable, though, to extract a variable and allow the call tostill be rendered in a single line, for example:```erl formatted split_tokens2split_tokens([{TokenType, Meta, TokenValue} | Rest], Acc, CAcc) -> Token = {TokenType, token_anno(erl_anno:to_term(Meta), #{}), TokenValue}, split_tokens(Rest, [Token | Acc], CAcc).```A similar situation could happen with long patterns in function heads,for example let's look at this function:```erlmy_function( #user{name: Name, age: Age, ...}, Arg2, Arg3) -> ...```Even though the code is perfectly valid, you might prefer not to split thearguments across multiple lines and move the pattern extraction into thefunction body instead:```erlmy_function(User, Arg2, Arg3) -> #user{name: Name, age: Age, ...} = User, ...```Such transformations cannot be automated since the formatter is not allowed tochange the AST of your program. After running the formatter, especially ifrunning it for the first time on a sizeable codebase, it's recommended toinspect the code manually to correct similar sub-par layouts.## Respecting original formatThe formatter keeps the original decisions in two key places * when choosing between a "collapsed" and an "expanded" layout for containers * when choosing between single-line and multi-line clauses.### In containersFor containers like lists, tuples, maps, records, function calls, macro calls,etc, there are two possible layouts - "collapsed" where the entire collectionis printed in a single line; and "expanded" where each element is printed on aseparate line. The formatter respects this choice, if possible. If there is anewline between the opening bracket/brace/parenthesis and the first element,the collection will be always printed "expanded", for example:```erl formatted foobar[ Foo, Bar]```will be preserved, even though it could fit on a single line.This is controlled by whether the user has included a newline in the original version.For example, merely deleting the newlines from the above sequence:```erl unformatted foobar1[ Foo, Bar]```and re-running the formatter, will produce:```erl formatted foobar1[Foo, Bar]```Similarly, adding the single newline back:```erl unformatted foobar[Foo, Bar]```and re-running the formatter, will produce the initial format again.### In clausesA similar approach is followed, when laying our clause sequences in functions,case expressions, receives, etc. The main choice there is simple - shouldthe clause body be printed directly after `->` or on a new line indented.The formatter imposes one constraint - either all clauses are printed ona single line, or in all clauses the body is printed on a new line.This is controlled by the layout of the first clause, again allowing to changethe layout of the entire sequence with just one character, for example:```erl formatted is_beautifulcase is_beautiful(Code) of true -> ring_the_bell(); false -> dig_a_hole()end```Even though, the expressions could all fit on a single line, because there is anewline in the first clause after `->`, this layout is preserved. If we'd liketo "collapse" it, we can do that by removing the first newline:```erl unformatted is_beautiful2case is_beautiful(Code) of true -> ring_the_bell(); false -> dig_a_hole()end```and re-running the formatter will produce:```erl formatted is_beautiful2case is_beautiful(Code) of true -> ring_the_bell(); false -> dig_a_hole()end```To go back to the original layout, we can insert the newline back again:```erl unformatted is_beautifulcase is_beautiful(Code) of true ->ring_the_bell(); false -> dig_a_hole()end```which after re-formatting will result in the original layout again.## Ignoring FormattingWe found that mostly it is possible to format erlang code in an at least somewhat acceptable way, but exceptions do occur.We have introduced the `erlfmt-ignore` comment, which when placed before a top-level expression, will indicate to `erlfmt` to skip over that expression, leave it as is and move on to the next expression.```erlang formatted matrix%% erlfmt-ignore-define(DELTA_MATRIX, [ [0, 0, 0, 0, 0, 0], [0, -16, 0, 0, 0, 0], [0, 0, 15, 0, 0, 0], [0, 0, 0, 6, 0, 0], [0, -16, 0, 0, -14, 0], [0, 0, 15, 0, 0, 0]]).```**Only top-level expression are supported.**Nested expressions are not supported, for example expressions inside functions.## Integrations - Visual Studio Code's [Erlang Formatter](https://marketplace.visualstudio.com/items?itemName=szTheory.erlang-formatter) extension. - How to integrate with [doom emacs](https://github.com/WhatsApp/erlfmt/issues/46#issuecomment-655996639)Add your integration here, by making a pull request.## Internal DocumentationTo learn more about erlfmt internals, please explore the `doc/` directory## Test```sh$ rebar3 ct$ rebar3 dialyzer```## Local useTo format erlfmt itself:```sh$ rebar3 as release escriptize$ _build/release/bin/erlfmt -w "{src,include,test}/*.{hrl,erl}" "rebar.config"```## Join the erlfmt communitySee the [CONTRIBUTING](.github/CONTRIBUTING.md) file for how to help out.Also see [Formatting Decisions](https://github.com/WhatsApp/erlfmt/blob/master/doc/Readme.md)## Licenseerlfmt is Apache 2.0 licensed, as found in the LICENSE file.