{"id":13466490,"url":"https://github.com/WhatsApp/erlfmt","last_synced_at":"2025-03-25T21:32:18.072Z","repository":{"id":38523820,"uuid":"243469333","full_name":"WhatsApp/erlfmt","owner":"WhatsApp","description":"An automated code formatter for Erlang","archived":false,"fork":false,"pushed_at":"2024-09-03T10:51:14.000Z","size":1023,"stargazers_count":412,"open_issues_count":17,"forks_count":52,"subscribers_count":26,"default_branch":"main","last_synced_at":"2024-10-29T15:31:54.665Z","etag":null,"topics":["erlang","formatter","rebar3-plugin"],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WhatsApp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-27T08:30:53.000Z","updated_at":"2024-10-25T02:53:42.000Z","dependencies_parsed_at":"2023-02-09T12:15:34.086Z","dependency_job_id":"c1e5ddc3-f3f3-47bc-96be-51f38486a1a3","html_url":"https://github.com/WhatsApp/erlfmt","commit_stats":{"total_commits":522,"total_committers":26,"mean_commits":"20.076923076923077","dds":0.603448275862069,"last_synced_commit":"32b2f06cda4d7b5d59c86f4689fc9039a894730c"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WhatsApp%2Ferlfmt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WhatsApp%2Ferlfmt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WhatsApp%2Ferlfmt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WhatsApp%2Ferlfmt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WhatsApp","download_url":"https://codeload.github.com/WhatsApp/erlfmt/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245548354,"owners_count":20633565,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["erlang","formatter","rebar3-plugin"],"created_at":"2024-07-31T15:00:45.075Z","updated_at":"2025-03-25T21:32:17.557Z","avatar_url":"https://github.com/WhatsApp.png","language":"Erlang","funding_links":[],"categories":["Erlang"],"sub_categories":[],"readme":"# erlfmt\n\nerlfmt is an opinionated Erlang code formatter. By automating the process of\nformatting the code, it frees your team up to focus on what the code does,\ninstead of what it looks like.\n\nerlfmt is feature complete and released as version 1.0\nThis means only backwards compatible changes and bug fixes can be adopted without very serious consideration.\nWe do not want to put users in the position where they need to reformat code without a very good reason.\n\n## Before\n\nRemember reading code before erlfmt and having arguments with co workers :(\n\n```erl\nwhat_is(Erlang) -\u003e\ncase Erlang of movie-\u003e[hello(mike,joe,robert),credits]; language-\u003eformatting_arguments end\n.\n```\n\n## After\n\nNow, with the new erlfmt, code is readable and you get along with your co workers :D\n\n```erlang formatted demo2\nwhat_is(Erlang) -\u003e\n    case Erlang of\n        movie -\u003e [hello(mike, joe, robert), credits];\n        language -\u003e no_more_formatting_arguments\n    end.\n```\n\n*Disclaimer: erlfmt is just a code formatter, not a solution to all life's problems.*\n\n## Table of Contents\n\n  - [Comparison with other Erlang formatters](#comparison-with-other-erlang-formatters)\n  - [Usage](#usage)\n  - [Line length](#line-length)\n  - [Design principles](#design-principles)\n  - [Manual interventions](#manual-interventions)\n  - [Respecting original format](#respecting-original-format)\n  - [Ignoring Formatting](#ignoring-formatting)\n  - [Join the erlfmt community](#join-the-erlfmt-community)\n\n## Comparison with other Erlang formatters\n\n|                               |erlfmt                                                           |rebar3_format                |steamroller                                    |erl_tidy |\n|---                            |---                                                              |---                          |---                                            |--- |\n|File Types                     |.erl, .hrl, .app, .app.src, .config, .script, .escript           |.erl, .hrl\t                |.erl, .hrl, .app, .app.src, .config, .script\t|.erl |\n|Macros                         |No crashes formatting OTP                                        |Skips entire files sometimes\t|Skips entire files sometimes\t                |Crashes sometimes |\n|Comments                       |Preserves and moves to line before                               |Preserves but Floating       |Crashes sometimes and Reorders\t                |Crashes sometimes and Floating |\n|Configurable vs Opinionated    |Opinionated                                                      |Configurable                 |Opinionated                                    |Configurable |\n|Preserving Representation      |Yes                                                              |Some                         |Some                                             |No |\n|Line Break Hints               |Yes                                                              |No                           |No                                             |No |\n|Opt In/Out                     |per file, per top level expression                               |per file                     |No                                             |No |\n|Speed                          |OTP lib in 7s                                                    |N/A                          |N/A                                            |N/A |\n\nSee the [comparison with other erlang formatters document](./doc/ErlangFormatterComparison.md) for more details.\n\n## Usage\n\n### Rebar3\n\nThe easiest way to use erlfmt is as a rebar plugin, by adding to your\n`rebar.config`:\n\n```erlang formatted rebarconfig1\n{project_plugins, [erlfmt]}.\n```\n\nThis will provide a new `rebar3 fmt` task. All erlfmt command-line options\ncan be configured with defaults in your `rebar.config`, for example:\n\n```erlang formatted rebarconfig2\n{erlfmt, [write]}.\n```\n\nNow you can format all the files in your project by running:\n\n```\n$ rebar3 fmt\n```\n\nAnd you can add the following command in your CI to ensure your Erlang is formatted:\n\n```\n$ rebar3 fmt --check\n```\n\nFor more usage instructions, see [RebarUsage](./doc/RebarUsage.md)\n\n### Escript\n\nAlternatively, you can build a standalone and portable escript and use erlfmt\nwithout rebar (it still requires Erlang to be installed on the target system).\n\n```sh\n$ rebar3 as release escriptize\n$ _build/release/bin/erlfmt -h\n```\n\nYou can then run it from the command line:\n\n```sh\n$ erlfmt -w './otp/lib/*/{src,include}/*.{erl,hrl}'\n```\n\n### Requirements\n\nerlfmt requires Erlang/OTP 21+ and works on all platforms.\n\n### Integrations\n\n - Visual Studio Code's [Erlang Formatter](https://marketplace.visualstudio.com/items?itemName=szTheory.erlang-formatter) extension.\n - How to integrate with [doom emacs](https://github.com/WhatsApp/erlfmt/issues/46#issuecomment-655996639)\n - Use `erlfmt` through [`rebar3_format`](https://github.com/AdRoll/rebar3_format/blob/master/README.md#erlfmt)\n\nAdd your integration here, by making a pull request.\n\n## Line length\n\nerlfmt enforces a consistent style by parsing your code and re-printing it,\nwhile enforcing a selected maximum line-length.\n\nFor example, this line that exceeds the length limit:\n\n```erlang unformatted scenario\nscenario(dial_phone_number(),  ring(), hello(mike),hello(joe), hello(robert),   system_working(), seems_to_be())\n```\n\nwill be re-printed automatically in a vertical style:\n\n```erlang formatted scenario\nscenario(\n    dial_phone_number(),\n    ring(),\n    hello(mike),\n    hello(joe),\n    hello(robert),\n    system_working(),\n    seems_to_be()\n)\n```\n\nBut this snippet:\n\n```erlang formatted hello\nhello(mike, joe, robert)\n```\n\nwill be kept as-is, since it fits in a single line.\n\nNote: The enforcing of line-length is best effort and will sometimes overrun the selected line length, because the algorithm is greedy.\n\n## Design principles\n\nThe formatter was designed with these main principles in mind:\n\nFirst, the formatter never changes the semantics or structure of the code. This\nmeans the input AST and the output AST are equivalent. The formatter does not\ntry to arbitrarily \"improve\" the code. For the most part it limits its\nbehaviour to shifting whitespace around - it won't rewrite literals, add\nparentheses, reorder exports, etc.\n\nThe second principle is to provide as little configuration as possible. This\nremoves contention points and makes it easier to achieve the goal of consistent\ncode. Instead of providing configuration, the formatter respects a limited set\nof choices made in the original code to preserve intent and make it easier to\nachieve beautiful code based on contextual hints.\n\nFurthermore, the formatter avoids special cases as much as possible. For\nexample, there is no hard-coded behaviour specific to some function - all\nfunctions are laid out the same. There are some clear layout rules and general\nstructures that are re-used as much as possible between different constructs.\nFor example, the general layout of lists, functions, maps, records, and\nsimilar, all follow the same \"container\" rules.\n\nFinally, the formatter should be idempotent. Formatting the code once should\nproduce the same output as formatting it multiple times.\n\n## Manual interventions\n\nIn some cases, the formatter rules might lead to code that looks decent, but\nnot perfect. Therefore some manual intervention to help the formatter out might\nbe needed. For example, given the following code:\n\n```erlang unformatted split_tokens\nsplit_tokens([{TokenType, Meta, TokenValue} | Rest], TokenAcc, CommentAcc) -\u003e\n    split_tokens(Rest, [{TokenType, token_anno(erl_anno:to_term(Meta), #{}), TokenValue} | TokenAcc], CommentAcc).\n```\n\nBecause the line-length is exceeded, the formatter will produce the following:\n\n```erlang formatted split_tokens\nsplit_tokens([{TokenType, Meta, TokenValue} | Rest], TokenAcc, CommentAcc) -\u003e\n    split_tokens(\n        Rest,\n        [{TokenType, token_anno(erl_anno:to_term(Meta), #{}), TokenValue} | TokenAcc],\n        CommentAcc\n    ).\n```\n\nIt might be more desirable, though, to extract a variable and allow the call to\nstill be rendered in a single line, for example:\n\n```erlang formatted split_tokens2\nsplit_tokens([{TokenType, Meta, TokenValue} | Rest], TokenAcc, CommentAcc) -\u003e\n    Token = {TokenType, token_anno(erl_anno:to_term(Meta), #{}), TokenValue},\n    split_tokens(Rest, [Token | TokenAcc], CommentAcc).\n```\n\nA similar situation could happen with long patterns in function heads,\nfor example let's look at this function:\n\n```erlang\nmy_function(\n    #user{name: Name, age: Age, ...},\n    Arg2,\n    Arg3\n) -\u003e\n    ...\n```\n\nEven though the code is perfectly valid, you might prefer not to split the\narguments across multiple lines and move the pattern extraction into the\nfunction body instead:\n\n```erl\nmy_function(User, Arg2, Arg3) -\u003e\n    #user{name: Name, age: Age, ...} = User,\n    ...\n```\n\nSuch transformations cannot be automated since the formatter is not allowed to\nchange the AST of your program. After running the formatter, especially if\nrunning it for the first time on a sizeable codebase, it's recommended to\ninspect the code manually to correct similar sub-par layouts.\n\n## Respecting original format\n\nThe formatter keeps the original decisions in two key places\n\n  * when choosing between a \"collapsed\", \"semi-expanded\", and an \"expanded\" layout for containers\n  * when choosing between single-line and multi-line clauses.\n\n### In containers\n\nFor containers like lists, tuples, maps, records, function calls, etc,\nthere are three possible layouts - \"collapsed\" where the entire collection\nis printed in a single line; \"semi-expanded\" where the enclosing\nbrackets/breaces/parentheses are printed on a line of their own, but all elements\nare printed in a single line; and \"expanded\" where each element is printed on a\nseparate line. The formatter respects this choice, if possible. If there is a\nnewline between the opening bracket/brace/parenthesis and the first element,\nthe collection will be always printed \"semi-expanded\", for example:\n\n```erlang formatted semi-expanded\n[\n    Foo, Bar\n]\n```\n\nwill be preserved, even though it could fit on a single line.\n\nSimilarly, if there's a break between any elements, the container will be printed\nin the \"expanded\" format:\n```erlang formatted expanded\n[\n    Foo,\n    Bar\n]\n```\n\nThis is controlled by the newlines in the original version.\nFor example, merely deleting the newlines from the above sequence:\n\n```erlang unformatted collapsed\n[    Foo, Bar]\n```\n\nand re-running the formatter, will produce:\n\n```erlang formatted collapsed\n[Foo, Bar]\n```\n\nSimilarly, adding the single initial newline back:\n\n```erlang unformatted semi-expanded\n[\nFoo, Bar]\n```\n\nand re-running the formatter, will produce the \"semi-expanded\" format again.\n\nWhile adding a newline in the middle:\n\n```erlang unformatted expanded\n[Foo,\nBar]\n```\n\nand re-running the formatter, will produce the \"expanded\" format again.\n\n### In clauses\n\nA similar approach is followed, when laying our clause sequences in functions,\ncase expressions, receives, etc. The main choice there is simple - should\nthe clause body be printed directly after `-\u003e` or on a new line indented.\nThe formatter imposes one constraint - either all clauses are printed on\na single line, or in all clauses the body is printed on a new line.\nThis is controlled by the layout of the first clause, again allowing to change\nthe layout of the entire sequence with just one character, for example:\n\n```erlang formatted is_beautiful\ncase is_beautiful(Code) of\n    true -\u003e\n        ring_the_bell();\n    false -\u003e\n        dig_a_hole()\nend\n```\n\nEven though, the expressions could all fit on a single line, because there is a\nnewline in the first clause after `-\u003e`, this layout is preserved. If we'd like\nto \"collapse\" it, we can do that by removing the first newline:\n\n```erlang unformatted is_beautiful2\ncase is_beautiful(Code) of\n    true -\u003e        ring_the_bell();\n    false -\u003e\n        dig_a_hole()\nend\n```\n\nand re-running the formatter will produce:\n\n```erlang formatted is_beautiful2\ncase is_beautiful(Code) of\n    true -\u003e ring_the_bell();\n    false -\u003e dig_a_hole()\nend\n```\n\nTo go back to the original layout, we can insert the newline back again:\n\n```erlang unformatted is_beautiful\ncase is_beautiful(Code) of\n    true -\u003e\nring_the_bell();\n    false -\u003e dig_a_hole()\nend\n```\n\nwhich after re-formatting will result in the original layout again.\n\n## Ignoring Formatting\n\nWe found that mostly it is possible to format erlang code in an at least somewhat acceptable way, but exceptions do occur.\nWe 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. For documentation purposes, a reason for not formatting can be given..\n\n```erlang formatted matrix\n%% erlfmt:ignore I like it more this way\n-define(DELTA_MATRIX, [\n    [0,   0,   0,   0,   0,   0],\n    [0, -16,   0,   0,   0,   0],\n    [0,   0,  15,   0,   0,   0],\n    [0,   0,   0,   6,   0,   0],\n    [0, -16,   0,   0, -14,   0],\n    [0,   0,  15,   0,   0,   0]\n]).\n```\n\nYou can also encose multiple top-level forms in a `erlfmt:ignore-begin`, `erlfmt:ignore-end` section.\n```erlang formatted ignore-many\n%% erlfmt:ignore-begin\n-define(DELTA_MATRIX1, [\n    [0,   0,   0,   0,   0,   0]\n]).\n-define(DELTA_MATRIX2, [\n    [0,   0,   0,   0,   0,   0]\n]).\n%% erlfmt:ignore-end\n\n-define(THIS_IS_FORMATTED, ok).\n```\n\n**Only top-level expression are supported.**\nNested expressions are not supported, for example expressions inside functions.\n\nYou can also add a comment to `%%% % @noformat` at the top of your file to opt that file out of formatting.\nIt is also possible to use `%%% % @format` comments at the top of your files with the `--require-pragma` flag to only format opted in files.\n\n## Join the erlfmt community\n\nTo learn more about erlfmt internals, please explore the `doc/` directory\n\nSee the [CONTRIBUTING](.github/CONTRIBUTING.md) file for how to help out.\n\n### Test\n\n```sh\n$ rebar3 ct\n$ rebar3 dialyzer\n# or\n$ make check\n```\n\n### Local use\n\nTo format erlfmt itself:\n\n```sh\n$ make fmt\n```\n\n### Release Process\n\nThe [release process](./RELEASE.md) requires a few steps, updating the [CHANGELOG.md](./CHANGELOG.md), releasing to [hex](https://hex.pm/packages/erlfmt) and more.\n\n### Decision Documents\n\n[Formatting Decisions](https://github.com/WhatsApp/erlfmt/blob/main/doc/Readme.md) documents are intended to explain our reasoning for making certain formatting decisions.\n\n### License\n\nerlfmt is Apache 2.0 licensed, as found in the [LICENSE](./LICENSE) file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWhatsApp%2Ferlfmt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FWhatsApp%2Ferlfmt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWhatsApp%2Ferlfmt/lists"}