diff --git a/CHANGELOG.md b/CHANGELOG.md index c663eaf0..49950db2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,14 @@ ### Enhancements - - Handle try clauses in define - - Support formatting PIDs, Refs, Ports - - Handle ;-separated guards in macro expressions +- Handle try clauses in define +- Support formatting PIDs, Refs, Ports +- Handle ;-separated guards in macro expressions ### Fixes - - Ensure proper IO opts when reading from stdin - - Handle Int#{} edge case without crashing +- Ensure proper IO opts when reading from stdin +- Handle Int#{} edge case without crashing ## v1.0.0 (29.06.2021) @@ -23,7 +23,7 @@ ### Fixes - - Formatting Ranges support for opt-out pragmas #311 +- Formatting Ranges support for opt-out pragmas #311 ## v0.15.1 (23.06.2021) @@ -31,7 +31,7 @@ ### Enhancements - - Introduce semi-expanded format for containers #309 +- Introduce semi-expanded format for containers #309 ## v0.15.0 (18.06.2021) @@ -39,13 +39,13 @@ ### Enhancements - - Add `--range` as a command line option #299, #300, #295 - - Introduce semi-expanded format for function calls #305 - - Introduce noformat pragma #302 +- Add `--range` as a command line option #299, #300, #295 +- Introduce semi-expanded format for function calls #305 +- Introduce noformat pragma #302 ### Fixes - - Return exit code 2 when option isn't recognized. #304 +- Return exit code 2 when option isn't recognized. #304 ## v0.14.1 (10.05.2021) @@ -53,7 +53,7 @@ ### Fixes - - fix replace pragma to not crash on two blocks of comments +- fix replace pragma to not crash on two blocks of comments ## v0.14.0 (10.05.2021) @@ -61,16 +61,16 @@ ### Fixes - - comment `@format` pragma to avoid warnings from edoc - - fix place of comments for list cons #282 +- comment `@format` pragma to avoid warnings from edoc +- fix place of comments for list cons #282 ### Enhancements - - added `delete-pragma` command line flag #280 +- added `delete-pragma` command line flag #280 ### Formatting Decisions - - new module documentation analyses #283 +- new module documentation analyses #283 ## v0.13.0 (19.04.2021) @@ -78,11 +78,11 @@ ### Fixes - - Preserve newline in spec without guards #273 #275 - - Preserve comments in receive after after #271 - - Fix end_location in raw_string anno #274 - - Fix pre dot comments in function #267 - - Better error reporting for not_equivalent and tests #269 +- Preserve newline in spec without guards #273 #275 +- Preserve comments in receive after after #271 +- Fix end_location in raw_string anno #274 +- Fix pre dot comments in function #267 +- Better error reporting for not_equivalent and tests #269 ## v0.12.0 (30.03.2021) @@ -90,14 +90,14 @@ Tuples that are not tagged tuples are formatted as lists ### Enhancements - - Break untagged tuples #259 - - rewrite some binary operators using associativity #262 #264 #265 - - next break fits for dolon #246 +- Break untagged tuples #259 +- rewrite some binary operators using associativity #262 #264 #265 +- next break fits for dolon #246 ### Fixes - - double indent pattern matches in clauses #247 - - fix parsing of bad records #261 +- double indent pattern matches in clauses #247 +- fix parsing of bad records #261 ## v0.11.1 (10.03.2021) @@ -105,11 +105,11 @@ Support macros in type and spec names ### Enhancements - - Added shorthand `-i` for `--insert-pragma` #256 +- Added shorthand `-i` for `--insert-pragma` #256 ### Fixes - - Support macros in type and spec names #254 +- Support macros in type and spec names #254 ## v0.11.0 (12.02.2021) @@ -117,9 +117,9 @@ List comprehensions revisited ### Enhancements - - Updated formatting for list comprehensions #249 - - preserve newline after's arrow #245 - - preserve newlines after equal #248 +- Updated formatting for list comprehensions #249 +- preserve newline after's arrow #245 +- preserve newlines after equal #248 ## v0.10.0 (22.01.2021) @@ -127,17 +127,17 @@ Preserve a single empty line between attributes ### Enhancements - - Preserve a single empty line between attributes #239 - - Only write to file the formatted code when it is different to the current one #235 - - Export the format_nodes function for symmetry with read_nodes function. +- Preserve a single empty line between attributes #239 +- Only write to file the formatted code when it is different to the current one #235 +- Export the format_nodes function for symmetry with read_nodes function. ### Fixes - - no newline after spec #224 - - preserve breaks after dolon #225 - - fix: Crash formatting qlc_SUITE in OTP #240 - - Preserve newlines between separated guards #241 - - Adjust AST for try clauses such that they do not move comments around #242 +- no newline after spec #224 +- preserve breaks after dolon #225 +- fix: Crash formatting qlc_SUITE in OTP #240 +- Preserve newlines between separated guards #241 +- Adjust AST for try clauses such that they do not move comments around #242 ## v0.9.0 (4.11.2020) @@ -145,18 +145,18 @@ Default files and exclude_files option ### Enhancements - - Provide default value for `--files` when none is provided #204 - - Add `--exclude-files` flag to manually exclude some files from the formatter #213 - - Warn on overly long lines, given `--verbose` flag #209 +- Provide default value for `--files` when none is provided #204 +- Add `--exclude-files` flag to manually exclude some files from the formatter #213 +- Warn on overly long lines, given `--verbose` flag #209 ### Fixes - - Comments below shebang are pull upwards to support emulator args #207 - - Bind Pipe Tighter in parser #206 - - Fix record field type end location in parser #201 - - Properly handle comments inside concats #216 - - Properly handle trailing comments followed by post-comments #217 - - Preserve comments inside fun type arg list #218 +- Comments below shebang are pull upwards to support emulator args #207 +- Bind Pipe Tighter in parser #206 +- Fix record field type end location in parser #201 +- Properly handle comments inside concats #216 +- Properly handle trailing comments followed by post-comments #217 +- Preserve comments inside fun type arg list #218 ## v0.8.0 (5.10.2020) @@ -164,14 +164,14 @@ Better rebar integration ### Enhancements - - Better rebar integration #196 #183 #197 #200 - - Change formatting of pipes to prefixes #191 #194 +- Better rebar integration #196 #183 #197 #200 +- Change formatting of pipes to prefixes #191 #194 ### Fixes - - Parse attributes with empty parens #189 - - Do not crash with comments before a dot #184 - - Do not crash with post comments in catch #195 +- Parse attributes with empty parens #189 +- Do not crash with comments before a dot #184 +- Do not crash with post comments in catch #195 ## v0.7.0 (17.09.2020) @@ -179,17 +179,17 @@ Better rebar integration ### Enhancements - - `--check` flag #123 +- `--check` flag #123 ### Bug fixes - - Preserve newline in list & binary comprehensions #116 - - Preserve new lines in guards and specs #115 - - Make multi-line tuples & binaries next-break-fits #117 - - Use deterministic compiler option for releasing precompiled escript #127 - - Do not crash when formatting an empty file #130 - - Relax map syntax #137 - - Support macros with parens in concat #138 +- Preserve newline in list & binary comprehensions #116 +- Preserve new lines in guards and specs #115 +- Make multi-line tuples & binaries next-break-fits #117 +- Use deterministic compiler option for releasing precompiled escript #127 +- Do not crash when formatting an empty file #130 +- Relax map syntax #137 +- Support macros with parens in concat #138 ## v0.6.0 (18.08.2020) @@ -197,90 +197,102 @@ Configurable print-width and erlfmt-ignore comment ### Enhancements - - `print-width` is now configurable as a command line parameter #99 - - `% erlfmt-ignore` comment ignores the next form and does not format it #98 - - files are now formatted in parallel #101 +- `print-width` is now configurable as a command line parameter #99 +- `% erlfmt-ignore` comment ignores the next form and does not format it #98 +- files are now formatted in parallel #101 ### Bug fixes - - Preserve empty lines around if-like attributes #97 - - Don't break multiple clauses with comments #96 - - Make try and after always introduce a newline #107 - - Do not preserve operator newline with a next break fits expression #104 - - New format for multiline fun types #110 +- Preserve empty lines around if-like attributes #97 +- Don't break multiple clauses with comments #96 +- Make try and after always introduce a newline #107 +- Do not preserve operator newline with a next break fits expression #104 +- New format for multiline fun types #110 ## v0.5.1 (05.08.2020) ### Bug fixes - - correctly handle type unions inside of multiline containers (such as specs) #90 + +- correctly handle type unions inside of multiline containers (such as specs) #90 ## v0.5.0 (05.08.2020) ### Enhancements - - new `--insert-pragma` CLI option adds the `@format` pragma to all the formatted + +- new `--insert-pragma` CLI option adds the `@format` pragma to all the formatted files that didn't have it before #75 - - preserve empty lines in containers #83 +- preserve empty lines in containers #83 ### Bug fixes - - correctly handle `.script` files like `rebar.config.script` #79 - - `export_type` is formatted the same as `export` #86 - - print type unions either all on single line or each type separated by + +- correctly handle `.script` files like `rebar.config.script` #79 +- `export_type` is formatted the same as `export` #86 +- print type unions either all on single line or each type separated by `|` on a separate line #84 ## v0.4.2 (24.07.2020) ### Bug Fixes - - `--require-pragma` combined with stdio for files with high-codepoint unicode + +- `--require-pragma` combined with stdio for files with high-codepoint unicode ## v0.4.1 (23.07.2020) ### Bug Fixes - - Fix dialyzer - - `--require-pragma` for single-form and non-unicode files - - Compiles with rebar3 3.14 + +- Fix dialyzer +- `--require-pragma` for single-form and non-unicode files +- Compiles with rebar3 3.14 ## v0.4.0 (23.07.2020) ### Enhancements - - require-pragma prints out original file instead of formatting if no pragma is found. + +- require-pragma prints out original file instead of formatting if no pragma is found. This makes it easier to integrate into a CI pipeline. #57 - - Better exceptions #58 - - Group imports and exports with similar names on the same line. #65 - - Add format_string as a library call. #66 +- Better exceptions #58 +- Group imports and exports with similar names on the same line. #65 +- Add format_string as a library call. #66 ### Bug Fixes - - Preserve empty lines between attributes #67 - - Remove space between single clause fun and parens #68 + +- Preserve empty lines between attributes #67 +- Remove space between single clause fun and parens #68 ## v0.3.0 (09.07.2020) ### Enhancements - - Allow reading from stdin with command `$ erlfmt -` #46 - - Support Erlang version 23.0 + +- Allow reading from stdin with command `$ erlfmt -` #46 +- Support Erlang version 23.0 ### Bug Fixes - - Remove trailing spaces from comments #48 - - Make position of return type in specs consistent #47 - - Fix some parser failures in OTP #39 - - Concat converted from a string also forces breaks #43 + +- Remove trailing spaces from comments #48 +- Make position of return type in specs consistent #47 +- Fix some parser failures in OTP #39 +- Concat converted from a string also forces breaks #43 ## v0.2.0 (03.06.2020) ### New formatting algebra + Switched out formatting algorithm. This is now based on Elixir's greedy algorithm, instead of a lazy algorithm. This fixes performance issues with laying out larger tuples. ### Bug Fixes - - Preserve empty lines between comments and expressions - - Stop adding unnecessary empty lines between attributes - - Stop indenting list and binary comprehensions unnecessarily - - Fix formatting multiple files from command line - - Ensure all parsable OTP files format cleanly + +- Preserve empty lines between comments and expressions +- Stop adding unnecessary empty lines between attributes +- Stop indenting list and binary comprehensions unnecessarily +- Fix formatting multiple files from command line +- Ensure all parsable OTP files format cleanly ### Enhancements - - Support formatting escripts and "consult" files like rebar.config - - Add `--require-pragma` flag to only format files annotated with `@format` + +- Support formatting escripts and "consult" files like rebar.config +- Add `--require-pragma` flag to only format files annotated with `@format` ## v0.1.0 (06.04.2020) diff --git a/README.md b/README.md index e8b247bb..2d224bf3 100644 --- a/README.md +++ b/README.md @@ -34,14 +34,14 @@ what_is(Erlang) -> ## Table of Contents - - [Comparison with other Erlang formatters](#comparison-with-other-erlang-formatters) - - [Usage](#usage) - - [Line length](#line-length) - - [Design principles](#design-principles) - - [Manual interventions](#manual-interventions) - - [Respecting original format](#respecting-original-format) - - [Ignoring Formatting](#ignoring-formatting) - - [Join the erlfmt community](#join-the-erlfmt-community) +- [Comparison with other Erlang formatters](#comparison-with-other-erlang-formatters) +- [Usage](#usage) +- [Line length](#line-length) +- [Design principles](#design-principles) +- [Manual interventions](#manual-interventions) +- [Respecting original format](#respecting-original-format) +- [Ignoring Formatting](#ignoring-formatting) +- [Join the erlfmt community](#join-the-erlfmt-community) ## Comparison with other Erlang formatters @@ -78,14 +78,14 @@ can be configured with defaults in your `rebar.config`, for example: Now you can format all the files in your project by running: -``` -$ rebar3 fmt +```sh +rebar3 fmt ``` And you can add the following command in your CI to ensure your Erlang is formatted: -``` -$ rebar3 fmt --check +```sh +rebar3 fmt --check ``` For more usage instructions, see [RebarUsage](./doc/RebarUsage.md) @@ -96,14 +96,14 @@ Alternatively, you can build a standalone and portable escript and use erlfmt without rebar (it still requires Erlang to be installed on the target system). ```sh -$ rebar3 as release escriptize -$ _build/release/bin/erlfmt -h +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}' +erlfmt -w './otp/lib/*/{src,include}/*.{erl,hrl}' ``` ### Requirements @@ -112,9 +112,9 @@ erlfmt requires Erlang/OTP 21+ and works on all platforms. ### 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) - - Use `erlfmt` through [`rebar3_format`](https://github.com/AdRoll/rebar3_format/blob/master/README.md#erlfmt) +- 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) +- Use `erlfmt` through [`rebar3_format`](https://github.com/AdRoll/rebar3_format/blob/master/README.md#erlfmt) Add your integration here, by making a pull request. @@ -241,8 +241,8 @@ inspect the code manually to correct similar sub-par layouts. The formatter keeps the original decisions in two key places - * when choosing between a "collapsed", "semi-expanded", and an "expanded" layout for containers - * when choosing between single-line and multi-line clauses. +- when choosing between a "collapsed", "semi-expanded", and an "expanded" layout for containers +- when choosing between single-line and multi-line clauses. ### In containers @@ -265,6 +265,7 @@ will be preserved, even though it could fit on a single line. Similarly, if there's a break between any elements, the container will be printed in the "expanded" format: + ```erlang formatted expanded [ Foo, @@ -398,7 +399,7 @@ $ make check To format erlfmt itself: ```sh -$ make fmt +make fmt ``` ### Release Process diff --git a/RELEASE.md b/RELEASE.md index 893022b2..c16b3984 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,50 +3,60 @@ The release process requires a few steps. 1. Update the [CHANGELOG.md](https://github.com/WhatsApp/erlfmt/blob/main/CHANGELOG.md) file. -``` - ### v.. - Document features and Bug fixes since last release. - See https://github.com/WhatsApp/erlfmt/releases/ for link to commits since last release. - Add issue numbers or pull requests numbers using hashtags to the fixes and features +```sh +### v.. + +Document features and Bug fixes since last release. +See https://github.com/WhatsApp/erlfmt/releases/ for link to commits since last release. +Add issue numbers or pull requests numbers using hashtags to the fixes and features - If only bug fixes change the bug fix version - If features were added change the minor version - Major version changes should be a big conversation +If only bug fixes change the bug fix version +If features were added change the minor version +Major version changes should be a big conversation ``` + 2. Bump version in `erlfmt.app.src` 3. Create a pull request, with these two changes and include that this is a release in the commit message. 4. Wait until github action passes and merge. -5. Draft a new release https://github.com/WhatsApp/erlfmt/releases/new -``` - Tag Version: v.. - Release title: Pick most significant feature - Description: Copy ChangeLog contents +5. Draft a new release https://github.com/WhatsApp/erlfmt/releases/new. + +```sh +Tag Version: v.. +Release title: Pick most significant feature +Description: Copy ChangeLog contents ``` + 6. Release to WhatsApp. This could include building with an older version of erlang. Also update the `rebar.config.script` with new version tag and reformat code base. Here is an example of building with erlang version 22 on mac: + ```bash -$ brew install erlang@22 -$ rm -rf _build/ -$ PATH="/usr/local/opt/erlang@22/bin:$PATH" rebar3 as release escriptize +brew install erlang@22 +rm -rf _build/ +PATH="/usr/local/opt/erlang@22/bin:$PATH" rebar3 as release escriptize + # check that it runs with erlang version 22 -$ PATH="/usr/local/opt/erlang@22/bin:$PATH" _build/release/bin/erlfmt -h +PATH="/usr/local/opt/erlang@22/bin:$PATH" _build/release/bin/erlfmt -h + # check that it runs with current erlang version -$ _build/release/bin/erlfmt -h +_build/release/bin/erlfmt -h ``` + 7. Release to hex: `rebar3 hex publish`. If you have not used hex before, create `~/.config/rebar3/rebar.config` and include the contents `{plugins, [rebar3_hex]}`: + ```bash -$ mkdir -p ~/.config/rebar3/ && echo "{plugins, [rebar3_hex]}." >> ~/.config/rebar3/rebar.config +mkdir -p ~/.config/rebar3/ && echo "{plugins, [rebar3_hex]}." >> ~/.config/rebar3/rebar.config ``` If you forgot your local password: + ```bash -$ rebar3 hex user deauth -$ rebar3 hex user auth +rebar3 hex user deauth +rebar3 hex user auth ``` Visit https://hex.pm/packages/erlfmt to see that it was published. diff --git a/StyleGuide.md b/StyleGuide.md index f1a6c020..d803d2f4 100644 --- a/StyleGuide.md +++ b/StyleGuide.md @@ -8,16 +8,19 @@ This style guide represents guidelines for writing good Erlang code according to the authors of erlfmt. It consists of two sections: - * Linting - with guidelines about code content that are considered good - practice and need to be applied manually - (or using [`rebar3_lint`](https://hex.pm/packages/rebar3_lint) / [`elvis`](https://github.com/inaka/elvis)) - * Formatting - with guidelines about code layout that can be applied - automatically by using `erlfmt` + +* Linting - with guidelines about code content that are considered good + practice and need to be applied manually + (or using [`rebar3_lint`](https://hex.pm/packages/rebar3_lint) / [`elvis`](https://github.com/inaka/elvis)) +* Formatting - with guidelines about code layout that can be applied + automatically by using `erlfmt` ## Linting ### Naming + * Use `CamelCase` for variables (acronyms are fine both ways) + ```erlang %% Bad File_name @@ -25,6 +28,7 @@ File_Name _file Stored_id ``` + ```erlang %% Good FileName @@ -32,54 +36,66 @@ _File StoredID StoredId ``` + [Related `elvis` configuration](https://github.com/inaka/elvis_core/wiki/Rules#variable-naming-convention): - ```erlang - {elvis_style, variable_naming_convention, #{regex => "^([A-Z][0-9a-zA-Z]*)$"}} - ``` + +```erlang +{elvis_style, variable_naming_convention, #{regex => "^([A-Z][0-9a-zA-Z]*)$"}} +``` * Use `snake_case` for functions, attributes, and atoms + ```erlang %% Bad 'Error' badReturn read_File ``` + ```erlang %% Good error bad_return read_file ``` + [Related `elvis` configuration](https://github.com/inaka/elvis_core/wiki/Rules#module-naming-convention): - ```erlang - {elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}}, - {elvis_style, function_naming_convention, #{regex => "^([a-z]*_?)*$"}} - ``` + +```erlang +{elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}}, +{elvis_style, function_naming_convention, #{regex => "^([a-z]*_?)*$"}} +``` * Use `SCREAMING_SNAKE_CASE` for macro definitions + ```erlang %% Bad -define(some_value, 100). -define(someMacro(), hidden:call()). -define(AnotherMacro, "another macro"). ``` + ```erlang %% Good -define(SOME_VALUE, 100). -define(SOME_MACRO(), hidden:call()). -define(ANOTHER_MACRO, "another macro"). ``` + [Related `elvis` configuration](https://github.com/inaka/elvis_core/wiki/Rules#macro-names): - ```erlang - {elvis_style, macro_names} - ``` + +```erlang +{elvis_style, macro_names} +``` * Omit `()` in macro names only if they represent constants + ```erlang %% Bad -define(NOT_CONSTANT, application:get_env(myapp, key)). -define(CONSTANT(), 100). ``` + ```erlang %% Good -define(NOT_CONSTANT(), application:get_env(myapp, key)). @@ -88,10 +104,12 @@ read_file * Start names of predicate functions (functions that return a boolean) with `is_` or `has_` prefix + ```erlang %% Bad evenp(Num) -> ... ``` + ```erlang %% Good is_even(Num) -> ... @@ -99,37 +117,45 @@ is_even(Num) -> ... * Avoid using one-letter variable names and `do_` prefixes for functions, prefer descriptive names + ```erlang %% Bad validate([X | Xs]) -> do_validate(X), validate(Xs). ``` + ```erlang %% Good validate_all([Key | Keys]) -> validate(Key), validate_all(Keys). ``` + [Related `elvis` configuration](https://github.com/inaka/elvis_core/wiki/Rules#variable-naming-convention): - ```erlang - {elvis_style, variable_naming_convention, #{regex => "^[A-Z][a-zA-Z]([0-9a-zA-Z]+)$"}} - ``` -* Use `snake_case` for naming applications and modules +```erlang +{elvis_style, variable_naming_convention, #{regex => "^[A-Z][a-zA-Z]([0-9a-zA-Z]+)$"}} ``` + +* Use `snake_case` for naming applications and modules + +```sh %% Good myapp/src/task_server.erl ``` + [Related `elvis` configuration](https://github.com/inaka/elvis_core/wiki/Rules#module-naming-convention): - ```erlang - {elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}} - ``` + +```erlang +{elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}} +``` ### Booleans -* avoid `and` and `or` operators, prefer` andalso` and `orelse`, or +* avoid `and` and `or` operators, prefer `andalso` and `orelse`, or in guards, where possible, combine expressions with `,` and `;` + ```erlang %% Bad is_atom(Name) and Name =/= undefined. @@ -159,16 +185,19 @@ The erlfmt formatter enforces the following rules automatically. * end each file with a newline * use single space after comma + ```erlang unformatted comma-space %% Bad application:get_env(kernel,start_pg2) ``` + ```erlang formatted comma-space %% Good application:get_env(kernel, start_pg2) ``` * use single space before and after binary operator (for example: `=` , `-`, `|`, `||`, `!`, `->`, or `=>`) + ```erlang unformatted binary-space %% Bad Values=[ @@ -178,6 +207,7 @@ Values=[ Server!Message ] ``` + ```erlang formatted binary-space %% Good Values = [ @@ -189,11 +219,13 @@ Values = [ ``` * do not use space after symbolic unary operators (for example: `+`, `-`) + ```erlang unformatted unary-space %% Bad Angle = - 45, WithParens = - ( + 45 ). ``` + ```erlang formatted unary-space %% Good Angle = -45, @@ -201,6 +233,7 @@ WithParens = -(+45). ``` * do not put spaces before or after `( )`, `{ }`, or `[]` for function/record/maps/lists (declarations, definitions, calls) + ```erlang unformatted paren-spaces %% Bad function_definition( { ok, Argument } ) -> @@ -208,6 +241,7 @@ function_definition( { ok, Argument } ) -> sort( [ 1,2,3 ] ). ``` + ```erlang formatted paren-spaces %% Good function_definition({ok, Argument}) -> @@ -217,11 +251,13 @@ sort([1, 2, 3]). ``` * do not put spaces around segment definitions in binary patterns + ```erlang unformatted binary-pattern %% Bad <<102 : 8 / unsigned-big-integer, Rest / binary>>, <<255/unsigned - big - integer, Rest/binary>>. ``` + ```erlang formatted binary-pattern %% Good <<102:8/unsigned-big-integer, Rest/binary>>, @@ -229,6 +265,7 @@ sort([1, 2, 3]). ``` * avoid aligning expression groups + ```erlang unformatted groups %% Bad Module = Env#env.module, @@ -238,6 +275,7 @@ inspect(false) -> "false"; inspect(true) -> "true"; inspect(ok) -> "ok". ``` + ```erlang formatted groups %% Good Module = Env#env.module, @@ -263,6 +301,7 @@ reverse_lookup(one) -> 1; reverse_lookup(two) -> 2. ``` + ```erlang formatted clauses %% Good lookup(1) -> one; @@ -275,6 +314,7 @@ reverse_lookup(two) -> ``` * always break comma-separated expressions across multiple lines: + ```erlang unformatted comma %% Bad run() -> compile(), execute(). @@ -289,6 +329,7 @@ catch throw:error -> error end. ``` + ```erlang formatted comma %% Good run() -> @@ -312,6 +353,7 @@ end. ``` * Indent the right-hand side of a binary operator if it is on a different line + ```erlang unformatted binary-op %% Bad f(Op, Atom) -> @@ -319,6 +361,7 @@ f(Op, Atom) -> Atom =/= '!' andalso Atom =/= '='. ``` + ```erlang formatted binary-op %% Good f(Op, Atom) -> @@ -328,6 +371,7 @@ f(Op, Atom) -> ``` * When assigning the result of a multi-line expression, begin the expression on a new line + ```erlang unformatted newline-assign %% Bad Prefix = case Base of @@ -336,6 +380,7 @@ Prefix = case Base of hex -> "16#" end. ``` + ```erlang formatted newline-assign %% Good Prefix = @@ -363,6 +408,7 @@ different arity can be put on the same line, following the definition: ### Strings * do not use multilne strings + ```erlang unformatted string-to-concat %% Bad Message = "erlfmt is a code formatter for Erlang. @@ -372,6 +418,7 @@ Arguments: files -- files to format ..." ``` + ```erlang formatted string-to-concat %% Good Message = @@ -382,6 +429,7 @@ Message = " files -- files to format\n" "..." ``` + ```erlang formatted string-to-concat2 %% Also good Message = diff --git a/doc/ErlangFormatterComparison.md b/doc/ErlangFormatterComparison.md index f1277a5c..33151efc 100644 --- a/doc/ErlangFormatterComparison.md +++ b/doc/ErlangFormatterComparison.md @@ -22,17 +22,17 @@ but in this document, when we mention `rebar3_format` we are referring to the re One of the biggest lacking features with current Erlang formatters is the handling of macros. For example: -* `erl_tidy` - * Crashes on some macros (`argument`, `?NAME`, and `compute`) - * On some, prints the whole containing function in a huge, single, ugly line (`match`) -* `streamroller` - * Requires that all macros are defined - * Skips the whole file, if it contains a marco it can’t handle. -* `rebar3_format` - * Cannot handle most macros in the example (functions `argument`, `match`, `?NAME` and `compute`) - * Skips the whole file in these cases - * Syntax error on double question mark - * Loses parenthesis in `IMPORTANT_PARENS`, which changes the answer of `?IMPORTANT_PARENS(2 + 1)` from 9 to 5. +- `erl_tidy` + - Crashes on some macros (`argument`, `?NAME`, and `compute`) + - On some, prints the whole containing function in a huge, single, ugly line (`match`) +- `streamroller` + - Requires that all macros are defined + - Skips the whole file, if it contains a marco it can’t handle. +- `rebar3_format` + - Cannot handle most macros in the example (functions `argument`, `match`, `?NAME` and `compute`) + - Skips the whole file in these cases + - Syntax error on double question mark + - Loses parenthesis in `IMPORTANT_PARENS`, which changes the answer of `?IMPORTANT_PARENS(2 + 1)` from 9 to 5. `erlfmt` forked the erlang parser to make sure that it can handle macros and can handle all of the following macros and format them. diff --git a/doc/FormattingDecisionAssociative.md b/doc/FormattingDecisionAssociative.md index 52a8897c..dd9935ad 100644 --- a/doc/FormattingDecisionAssociative.md +++ b/doc/FormattingDecisionAssociative.md @@ -354,7 +354,7 @@ ThisVar = Diff for enabling rewriting right to left associativity for `=` and `::` too. -``` +```sh diff --git a/src/erlfmt_format.erl b/src/erlfmt_format.erl index 66e32b4..698189e 100644 --- a/src/erlfmt_format.erl @@ -380,7 +380,7 @@ index 66e32b4..698189e 100644 The diff for binary operator detection requires the diff of assoc to also be applied. -``` +```sh diff --git a/src/erlfmt_format.erl b/src/erlfmt_format.erl index 66e32b4..bc95541 100644 --- a/src/erlfmt_format.erl diff --git a/doc/FormattingDecisionCommas.md b/doc/FormattingDecisionCommas.md index 673d767d..ad1d46f3 100644 --- a/doc/FormattingDecisionCommas.md +++ b/doc/FormattingDecisionCommas.md @@ -1,4 +1,4 @@ -## Formatting Decision: Commas in Lists +# Formatting Decision: Commas in Lists This is a document explaining our reasoning behind the formatting decision for commas in lists. @@ -30,6 +30,7 @@ We have decided to analyse the OTP and WhatsApp code base, since this was too bi We `grep` `.hrl` and `.erl` files recursively. This requires grepping for two patterns: + 1. lists with commas at the end of the line (suffixes) 2. lists with commas at the start of the line (prefixes) @@ -38,6 +39,7 @@ This requires grepping for two patterns: Here are examples of the suffix pattern. One that starts with a newline after the opening bracket: + ```erlang [ a, @@ -45,22 +47,25 @@ One that starts with a newline after the opening bracket: ``` One that has no newline after the opening bracket: + ```erlang [ a/1, ... ``` The Suffix pattern: - - starts with open bracket `(\[)`, followed by - - optional white space `(\s)*`, followed by - - optional an new line `(\n)?`, followed by - - optional white space `(\s)*`, again followed by - - characters that are not a comma, close bracket, new line or opening call or opening record or tuple `([^,\n\]\{\(])*`, followed by - - a comma and some optional white space before the new line `,(\s*)\n` + +- starts with open bracket `(\[)`, followed by +- optional white space `(\s)*`, followed by +- optional an new line `(\n)?`, followed by +- optional white space `(\s)*`, again followed by +- characters that are not a comma, close bracket, new line or opening call or opening record or tuple `([^,\n\]\{\(])*`, followed by +- a comma and some optional white space before the new line `,(\s*)\n` So we end up with the following `pcregrep` pattern, where: - - `-M` allows us to match over multiple lines. - - `grep "\["` allows us to only count once per list + +- `-M` allows us to match over multiple lines. +- `grep "\["` allows us to only count once per list ```sh pcregrep --include=".*\.erl" --include=".*\.hrl" -rM "(\[)(\s)*(\n)?(\s)*([^,\n\]\{\(])*,(\s*)\n" . | grep "\[" | wc -l @@ -69,6 +74,7 @@ pcregrep --include=".*\.erl" --include=".*\.hrl" -rM "(\[)(\s)*(\n)?(\s)*([^,\n\ ### Prefix Pattern One that ends with a newline before the closing bracket: + ```erlang ... , a @@ -76,12 +82,14 @@ One that ends with a newline before the closing bracket: ``` One that has no newline before the closing bracket: + ```erlang ... , a/1 ] ``` The Prefix Pattern: + - starts with a newline, followed by some white space and a comma. `(\n(\s)*,)`, followed by - characters that are not a comma, closing bracket, new line or opening bracket `([^,\n\]\[])*`, followed by - an optional newline `(\n)?`, followed by @@ -89,8 +97,9 @@ The Prefix Pattern: - and ending with a closing bracket `(\])` So we end up with the following `pcregrep` pattern, where: - - `-M` allows us to match over multiple lines. - - `grep "\]"` allows us to only count once per list + +- `-M` allows us to match over multiple lines. +- `grep "\]"` allows us to only count once per list ```sh pcregrep --include=".*\.erl" --include=".*\.hrl" -rM "(\n(\s)*,)([^,\n\]\[])*(\n)?(\s)*(\])" . | grep "\]" | wc -l @@ -101,28 +110,34 @@ pcregrep --include=".*\.erl" --include=".*\.hrl" -rM "(\n(\s)*,)([^,\n\]\[])*(\n We found that both OTP and WhatsApp prefer commas as a suffix. OTP: - - Commas as a Suffix: 4533 - - Commas as a Prefix: 20 + +- Commas as a Suffix: 4533 +- Commas as a Prefix: 20 WhatsApp: - - Commas as a Suffix: 50x - - Commas as a Prefix: x + +- Commas as a Suffix: 50x +- Commas as a Prefix: x [Kazoo](https://github.com/2600hz/kazoo): - - Commas as a Suffix: 124 - - Commas as a Prefix: 3116 + +- Commas as a Suffix: 124 +- Commas as a Prefix: 3116 [MongooseIM](https://github.com/esl/MongooseIM): - - Commas as a Suffix: 852 - - Commas as a Prefix: 1 + +- Commas as a Suffix: 852 +- Commas as a Prefix: 1 [ejabberd](https://github.com/processone/ejabberd): - - Commas as a Suffix: 173 - - Commas as a Prefix: 0 + +- Commas as a Suffix: 173 +- Commas as a Prefix: 0 [inaka repos](./clone_inaka.sh): - - Commas as a Suffix: 244 - - Commas as a Prefix: 442 + +- Commas as a Suffix: 244 +- Commas as a Prefix: 442 We tried some other code bases too: [luerl](https://github.com/rvirding/luerl) and [circuitbreak](https://github.com/klarna/circuit_breaker), but these data sets where too small in comparison. @@ -132,9 +147,10 @@ Another consideration is possibly alienating new comers. We would love to attract more talent to the erlang community. The following style guides from other languages use ending commas exclusively: - - [Guido van Rossum's style guide for Python](https://www.python.org/dev/peps/pep-0008/#multiline-if-statements) (consistent ending) - - [Google's Java style guide](https://google.github.io/styleguide/javaguide.html#s4.8.3.1-array-initializers) (consistent or interspersed endings permitted, but not leading) - - [Mozilla's JavaScript style guide](https://firefox-source-docs.mozilla.org/code-quality/coding-style/coding_style_js.html) (interspersed ending; old JS did not allow trailing commas) + +- [Guido van Rossum's style guide for Python](https://www.python.org/dev/peps/pep-0008/#multiline-if-statements) (consistent ending) +- [Google's Java style guide](https://google.github.io/styleguide/javaguide.html#s4.8.3.1-array-initializers) (consistent or interspersed endings permitted, but not leading) +- [Mozilla's JavaScript style guide](https://firefox-source-docs.mozilla.org/code-quality/coding-style/coding_style_js.html) (interspersed ending; old JS did not allow trailing commas) ## Decision diff --git a/doc/FormattingDecisionComments.md b/doc/FormattingDecisionComments.md index e3aa2a36..df5f5cd3 100644 --- a/doc/FormattingDecisionComments.md +++ b/doc/FormattingDecisionComments.md @@ -6,8 +6,10 @@ Currently `erlfmt` moves all trailing comments to the line above, so that all co After looking at documentation and doing an analyses we have concluded that [directly following comments](#single-comments-on-the-same-line) should also be supported in future. This does not include aligning comments, since: - - we could not find a popular enough convention of aligning to a specific column number and - - aligning comments can cause large diffs, from changing a single line for comments to be re-aligned. + +- we could not find a popular enough convention of aligning to a specific column number and +- aligning comments can cause large diffs, from changing a single line for comments to be re-aligned. + More details on this future change can be found in issue [#219](https://github.com/WhatsApp/erlfmt/issues/219) ## Documentation @@ -21,6 +23,7 @@ More details on this future change can be found in issue [#219](https://github.c > 3. Comments about the module shall be without indentation and start with three percent characters (%%%) ### Erlang in Action (Book) + Page 43, Section COMMENTS > Style-wise, comments that follow code on the same line are usually written with only a single % character, whereas comments that are on lines of their own are typically written starting with two % characters ... @@ -43,24 +46,25 @@ Page 43, Section COMMENTS These are just examples: - - Emacs formats single line comments to align. - - In some places of OTP and RabbitMQ double percentage comments are used instead of single percentage comments: - * https://github.com/erlang/otp/blob/master/lib/compiler/src/beam_discasm.hrl#L32 - * https://github.com/erlang/otp/blob/master/lib/stdlib/src/gen_server.erl#L562 - * https://github.com/erlang/otp/blob/master/lib/stdlib/src/erl_tar.hrl#L32-L39 - * https://github.com/rabbitmq/rabbitmq-server/blob/master/src/rabbit_memory_monitor.erl#L26-L32 - * https://github.com/rabbitmq/rabbitmq-server/blob/master/src/rabbit_mirror_queue_misc.erl#L492 - - Elixir, in the erlang code, single percentage comments are used as double percentage comments: - * https://github.com/elixir-lang/elixir/blob/master/lib/elixir/src/elixir.erl#L309-L323 +- Emacs formats single line comments to align. +- In some places of OTP and RabbitMQ double percentage comments are used instead of single percentage comments: + - https://github.com/erlang/otp/blob/master/lib/compiler/src/beam_discasm.hrl#L32 + - https://github.com/erlang/otp/blob/master/lib/stdlib/src/gen_server.erl#L562 + - https://github.com/erlang/otp/blob/master/lib/stdlib/src/erl_tar.hrl#L32-L39 + - https://github.com/rabbitmq/rabbitmq-server/blob/master/src/rabbit_memory_monitor.erl#L26-L32 + - https://github.com/rabbitmq/rabbitmq-server/blob/master/src/rabbit_mirror_queue_misc.erl#L492 +- Elixir, in the erlang code, single percentage comments are used as double percentage comments: + - https://github.com/elixir-lang/elixir/blob/master/lib/elixir/src/elixir.erl#L309-L323 ### Analysis We have done a more thorough analysis of how comments are used in practice. It shows that mostly: - - `%%%` is used for standalone comments before the module attribute, - - `%%` is used for standalone comments, while - - `%` is used with comments that share a line with code. + +- `%%%` is used for standalone comments before the module attribute, +- `%%` is used for standalone comments, while +- `%` is used with comments that share a line with code. We also see a preference for the numbers of: `standalone > directly following > aligned` comments, where directly following comments still has a significant market share. We tried, but struggled to find enough of a pattern where comments are aligned at a specific column number in practice. @@ -69,18 +73,18 @@ You can find the details of the analysis [here](./FormattingDecisionComments/Rea ## Goals - - Minimize diff when changing a single line, impacts surrounding context. - - Currently popular style - - Welcoming to new comers +- Minimize diff when changing a single line, impacts surrounding context. +- Currently popular style +- Welcoming to new comers ## Aligning single percentage comments Sometimes single percentage comments are aligned. We have decided not to support this, as it violates one of our goals. - - ❌ Minimize diff when changing a single line - - ✅ Currently popular style - - ❌ Welcoming to new comers +- ❌ Minimize diff when changing a single line +- ✅ Currently popular style +- ❌ Welcoming to new comers Currently this is a relatively popular style in erlang, but not in too many communities outside of erlang. @@ -141,9 +145,9 @@ We have also [seen](https://github.com/erlang/otp/blob/master/lib/stdlib/src/erl ## Erlang mode for Emacs - - ✅ Minimize diff when changing a single line - - ❌ Currently popular style - - ❌ Welcoming to new comers +- ✅ Minimize diff when changing a single line +- ❌ Currently popular style +- ❌ Welcoming to new comers The standard erlang mode for emacs puts the single quoted comments by default at position 48 on the line. This means that comments are not realigned based on line lengths, but always aligned at the same fixed column. @@ -337,9 +341,9 @@ f() -> ## All comments always on a newline - - ✅ Minimize diff when changing a single line - - ✅ Currently popular style - - ❌ Welcoming to new comers +- ✅ Minimize diff when changing a single line +- ✅ Currently popular style +- ❌ Welcoming to new comers This was the easiest formatting of comments to implemented in a consistent manner. Standalone comments are by far the most popular comments in erlang, although it could be argued that moving following comments to the line above is not. @@ -397,9 +401,9 @@ X = ## Single comments on the same line - - ✅ Minimize diff when changing a single line - - ✅ Currently popular style - - ✅ Welcoming to new comers +- ✅ Minimize diff when changing a single line +- ✅ Currently popular style +- ✅ Welcoming to new comers This is not only currently a popular style in the erlang community, but also in other languages. We believe this should be the chosen layout, but it will require a significant change to the formatting algorithm as stated at the top of this document. diff --git a/doc/FormattingDecisionDefaultWidth.md b/doc/FormattingDecisionDefaultWidth.md index 652d945e..8627be8b 100644 --- a/doc/FormattingDecisionDefaultWidth.md +++ b/doc/FormattingDecisionDefaultWidth.md @@ -54,8 +54,8 @@ We did a survey of what other formatters and style guides have chosen. - HTML: [80](https://developers.google.com/style/html-formatting) - Facebook General: 80 - Google: - * Java Formatter: [100](https://google.github.io/styleguide/javaguide.html#:~:text=Java%20code%20has%20a%20column%20limit%20of%20100%20characters) - * Python: [80](https://google.github.io/styleguide/pyguide.html) + - Java Formatter: [100](https://google.github.io/styleguide/javaguide.html#:~:text=Java%20code%20has%20a%20column%20limit%20of%20100%20characters) + - Python: [80](https://google.github.io/styleguide/pyguide.html) - GitHub: at 120 a horizontal scrollbar is added - ReSharper: [100](https://www.jetbrains.com/help/resharper/Using_EditorConfig.html#what_is) - CPP: [80](https://google.github.io/styleguide/cppguide.html#Line_Length) @@ -89,4 +89,3 @@ for i in {70..120}; do rg '@generated|yeccpre\.hrl' --files-without-match $@ | xargs erlfmt --print-width $i | wc -l done ``` - diff --git a/doc/FormattingDecisionIgnore.md b/doc/FormattingDecisionIgnore.md index dfa5aa57..ff806f50 100644 --- a/doc/FormattingDecisionIgnore.md +++ b/doc/FormattingDecisionIgnore.md @@ -150,30 +150,31 @@ Given we are now motivated to make it possible to exclude some things from the f Goals: - - Compatible: Usable inside all erlang files, including for example `rebar.config` - - Future Proof: We cannot foresee all exceptions. - - Localized: Encourages formatting most of the file +- Compatible: Usable inside all erlang files, including for example `rebar.config` +- Future Proof: We cannot foresee all exceptions. +- Localized: Encourages formatting most of the file ### 1. Comment Off/On Use a comment that can turn formatting off and then later it back on. Prior Art: - - [Python Black](https://github.com/psf/black#the-black-code-style) comments around expression - - [YaPF](https://github.com/google/yapf#why-does-yapf-destroy-my-awesome-formatting) comments around or after expression - - ✅ Compatible: Comments can be applied inside rebar.config files, etc. - - ✅ Future Proof: Could be “abused” for not formatting other things - - ❌ Localized: It is very easy to forget to turn the formatter on again and makes it easier to not format large pieces of code. +- [Python Black](https://github.com/psf/black#the-black-code-style) comments around expression +- [YaPF](https://github.com/google/yapf#why-does-yapf-destroy-my-awesome-formatting) comments around or after expression + +- ✅ Compatible: Comments can be applied inside rebar.config files, etc. +- ✅ Future Proof: Could be “abused” for not formatting other things +- ❌ Localized: It is very easy to forget to turn the formatter on again and makes it easier to not format large pieces of code. ### 2. Comment Off Use a comment that can turn formatting off for the next top-level expression to which the comment is attached to. Prior Art includes: Javascript's [Prettier](https://prettier.io/docs/en/ignore.html) comments before expression. - - ✅ Compatible: Comments can be applied inside rebar.config files, etc. - - ✅ Future Proof: Could be “abused” for not formatting other things - - ✅ Localized: Less Comments than Off/On Alternative +- ✅ Compatible: Comments can be applied inside rebar.config files, etc. +- ✅ Future Proof: Could be “abused” for not formatting other things +- ✅ Localized: Less Comments than Off/On Alternative This is the chosen option. @@ -183,34 +184,34 @@ We already have `@format` pragma comment to opt in and out of formatting per fil We could reuse this and disable formatting for the entire file. Prior Art: This is what elixir formatter suggests to do in cases like this. - - ✅ Compatible: Comments can be applied inside rebar.config files, etc. - - ✅ Future Proof: Could be “abused” for not formatting other things - - ❌ Localized: A whole file is not formatted +- ✅ Compatible: Comments can be applied inside rebar.config files, etc. +- ✅ Future Proof: Could be “abused” for not formatting other things +- ❌ Localized: A whole file is not formatted ### 4. No Line Breakings Strictly follow the user-provided line breaks inside lists, don’t remove them when re-formatting. Prior Art: gofmt, has very little opinion about new lines in general. - - ✅ Compatible: Comments can be applied inside rebar.config files, etc. - - ❌ Future Proof: This only covers a specific case. - - ❌ Localized: This affects all lists everywhere, which gives rise to more formatting discussion between users, which exactly contradicts the project goal. +- ✅ Compatible: Comments can be applied inside rebar.config files, etc. +- ❌ Future Proof: This only covers a specific case. +- ❌ Localized: This affects all lists everywhere, which gives rise to more formatting discussion between users, which exactly contradicts the project goal. ### 5. Limited no line breaking A fusion between option 1 and 4 - strictly follow user-provided line breaks inside lists only in regions delimited by a special comment. - - ✅ Compatible: Comments can be applied inside rebar.config files, etc. - - ❌ Future Proof: For example this doesn't work with the DELTA_MATRIX - - ✅ Localized: This only affects a specific list. +- ✅ Compatible: Comments can be applied inside rebar.config files, etc. +- ❌ Future Proof: For example this doesn't work with the DELTA_MATRIX +- ✅ Localized: This only affects a specific list. ### 6. Marco or function call A macro could also change the indentation. - - ❌ Compatible: This will require an include file and can not use macro inside rebar.config and other files - - ✅ Future Proof: It can wrap any expression. - - ✅ Localized: Easy to target an exact expression. +- ❌ Compatible: This will require an include file and can not use macro inside rebar.config and other files +- ✅ Future Proof: It can wrap any expression. +- ✅ Localized: Easy to target an exact expression. ### 7. macro with magic name @@ -230,12 +231,11 @@ gen_part({constructed, bif}, TypeName, {_Name, parts, Tag, _Type}) -> " end"])). ``` - - ❌ Compatible: A macro inside rebar.config and other files - - ✅ Future Proof: It can wrap any expression. - - ✅ Localized: Easy to target an exact expression. - +- ❌ Compatible: A macro inside rebar.config and other files +- ✅ Future Proof: It can wrap any expression. +- ✅ Localized: Easy to target an exact expression. ## References - - The discussion started in [issue 19](https://github.com/WhatsApp/erlfmt/issues/19), formatting of DSLish lists. - - Example of work around for DSLish lists and more examples like matrices, were discussed in [pull request 93](https://github.com/WhatsApp/erlfmt/pull/93) +- The discussion started in [issue 19](https://github.com/WhatsApp/erlfmt/issues/19), formatting of DSLish lists. +- Example of work around for DSLish lists and more examples like matrices, were discussed in [pull request 93](https://github.com/WhatsApp/erlfmt/pull/93) diff --git a/doc/FormattingDecisionListComprehensions.md b/doc/FormattingDecisionListComprehensions.md index b0d03142..49ce3e9b 100644 --- a/doc/FormattingDecisionListComprehensions.md +++ b/doc/FormattingDecisionListComprehensions.md @@ -6,27 +6,28 @@ This document conforms to the decisions already made for [lists](./FormattingDec and does not rehash decisions already taken in that document. This document focuses on list comprehensions that are broken up over multiple lines, because of: - - a long function name, - - long parameters, - - long generators - - long filters - - etc. + +- a long function name, +- long parameters, +- long generators +- long filters +- etc. We will choose a format that best conforms to our goals: - - Create/keep differentiation between the generator, body and filters. - - Consistent with formatting of [Lists](./FormattingDecisionLists.md) - - Handles multiline expressions consistently. - - Consistent indenting by 4, to aid writability +- Create/keep differentiation between the generator, body and filters. +- Consistent with formatting of [Lists](./FormattingDecisionLists.md) +- Handles multiline expressions consistently. +- Consistent indenting by 4, to aid writability Here you can see all the other candidates we evaluated against our goals. ## Expanded - - ✅ Differentiating - - ✅ Consistent Lists - - ✅ Multiline expressions - - ❌ Indenting by 4 +- ✅ Differentiating +- ✅ Consistent Lists +- ✅ Multiline expressions +- ❌ Indenting by 4 We considered indenting the filter expressions by 3. @@ -62,10 +63,10 @@ Here is an example with a multiline expression, that shows what a long argument ## Expanded with consistent 4 spacing - - ✅ Differentiating - - ✅ Consistent with lists - - ✅ Handles multiline expression consistently - - ❌ Indenting by 4 +- ✅ Differentiating +- ✅ Consistent with lists +- ✅ Handles multiline expression consistently +- ❌ Indenting by 4 The one negative in the previous example is inconsistent indentation, where `filter(B)` was indented by 3 spaces. This alternative tries to correct that, but now we have a misalignment between `{A, B}` and `filter(B)` by a single space. @@ -80,10 +81,10 @@ This alternative tries to correct that, but now we have a misalignment between ` ## Dedent double pipes - - ✅ Differentiating - - ❌ Consistent with lists - - ✅ Handles multiline expression consistently - - ✅ Indenting by 4 +- ✅ Differentiating +- ❌ Consistent with lists +- ✅ Handles multiline expression consistently +- ✅ Indenting by 4 We also considered dedenting the double pipes to the left. @@ -106,10 +107,10 @@ This doesn't seem very consistent with how lists are formatted. ## Dedent double pipes, just a little - - ✅ Differentiating - - ✅ Consistent with lists - - ✅ Handles multiline expression consistently - - ✅ Indenting by 4 +- ✅ Differentiating +- ✅ Consistent with lists +- ✅ Handles multiline expression consistently +- ✅ Indenting by 4 This is the formatting `erlfmt` chose. Another alternative is dedenting the double pipes to the left, but just a little: @@ -146,10 +147,10 @@ Here is an example with a multiline expression, that shows what a long argument ## Two spaces after double pipes - - ✅ Differentiating - - ✅ Consistent with lists - - ✅ Handles multiline expression consistently - - ✅ Indenting by 4 +- ✅ Differentiating +- ✅ Consistent with lists +- ✅ Handles multiline expression consistently +- ✅ Indenting by 4 `erlfmt` chose the above format over this one, purely because this alternative had much larger complexity and seems to require inventing a new operator in the algebra. We can also considered dedenting the pipes, with two spaces after, to keep expressions aligned: @@ -186,10 +187,10 @@ Here is an example with a multiline expression, that shows what a long argument ## Double pipes on their own line - - ✅ Differentiating - - ❌ Consistent with lists - - ✅ Handles multiline expression consistently - - ✅ Indenting by 4 +- ✅ Differentiating +- ❌ Consistent with lists +- ✅ Handles multiline expression consistently +- ✅ Indenting by 4 We also considered giving the double pipes their own line. @@ -212,10 +213,10 @@ We also considered giving the double pipes their own line. ## Compressed - - ✅ Differentiating - - ❌ Consistent with lists - - ❌ Handles multiline expression consistently - - ✅ Indenting by 4 +- ✅ Differentiating +- ❌ Consistent with lists +- ❌ Handles multiline expression consistently +- ✅ Indenting by 4 The compressed option is inconsistent with how `erlfmt` formats lists, but does make differentiation clear. @@ -239,10 +240,10 @@ There is also a problem with how to indent multiline expressions consistently: ## Compressed with 4 spaces - - ✅ Differentiating - - ❌ Consistent with lists - - ❌ Handles multiline expression consistently - - ✅ Indenting by 4 +- ✅ Differentiating +- ❌ Consistent with lists +- ❌ Handles multiline expression consistently +- ✅ Indenting by 4 We could also consider an option where indentations are made with four spaces. diff --git a/doc/FormattingDecisionLists.md b/doc/FormattingDecisionLists.md index 5783fa0c..08ee9a6d 100644 --- a/doc/FormattingDecisionLists.md +++ b/doc/FormattingDecisionLists.md @@ -11,9 +11,9 @@ We chose to format multi line lists with [each element on their own line](each-e Goals: - - Minimize the diff when changing a single line. - - [Welcoming to new comers](#welcoming-to-new-comers), because we want erlang to be growing community. - - [Popular with current users](#analysis), it would be great if the style we pick is already a popular style +- Minimize the diff when changing a single line. +- [Welcoming to new comers](#welcoming-to-new-comers), because we want erlang to be growing community. +- [Popular with current users](#analysis), it would be great if the style we pick is already a popular style Readability would also be a goal, but it seems depending on who we ask each style can be seen as more readable than the other, which makes it seem as though this is subjective and would require a study to resolve. @@ -24,9 +24,9 @@ Here you follows all the candidates we evaluated against our goals. ## Each Element On Their Own Line - - ✅ Welcoming to new comers - - ✅ Minimize the diff when changing a single line - - ✅ Popular with current users +- ✅ Welcoming to new comers +- ✅ Minimize the diff when changing a single line +- ✅ Popular with current users `erlfmt` formats multiline lists with each element on a separate line: @@ -63,9 +63,9 @@ I_Decided_To_Change_My_Variable_Name = [ ### IO Format Tilde P - - ✅ Popular with current users - - ❌ Minimize the diff when changing a single line - - ❌ Welcoming to new comers +- ✅ Popular with current users +- ❌ Minimize the diff when changing a single line +- ❌ Welcoming to new comers `IO Format Tilde P` represents the format of lists printed by `io:format("~p", [MyList])`. @@ -108,28 +108,34 @@ $ pcregrep -r --include=".*\.erl" --include=".*\.hrl" "\[(.*),(\s)*$" . | grep - The results were inconclusive, they show that both styles `io:format ~p` and having each element on their own line are both relatively popular, with no clear winner. WhatsApp: - - tildep: x - - newline: 5x + +- tildep: x +- newline: 5x OTP: - - tildep: 18095 - - newline: 3415 + +- tildep: 18095 +- newline: 3415 [Inaka repos](#inaka): - - tildep: 115 - - newline: 408 + +- tildep: 115 +- newline: 408 [Kazoo](https://github.com/2600hz/kazoo): - - tildep: 229 - - newline: 36 + +- tildep: 229 +- newline: 36 [MongooseIM](https://github.com/esl/MongooseIM): - - tildep: 1991 - - newline: 700 + +- tildep: 1991 +- newline: 700 [ejabberd](https://github.com/processone/ejabberd): - - tildep: 1599 - - newline: 32 + +- tildep: 1599 +- newline: 32 ## Welcoming to new comers @@ -138,10 +144,10 @@ Making our format familiar to programmers from other languages, this could help The following style guides from other languages exclusively places each element on their own line: - - [Guido van Rossum's style guide for Python](https://www.python.org/dev/peps/pep-0008/#multiline-if-statements) - - [Google's Java style guide](https://google.github.io/styleguide/javaguide.html#s4.8.3.1-array-initializers) - - [Javascript's Prettier](https://prettier.io/docs/en/rationale.html#multi-line-objects) - - [Elixir Style Guide](https://github.com/christopheradams/elixir_style_guide) +- [Guido van Rossum's style guide for Python](https://www.python.org/dev/peps/pep-0008/#multiline-if-statements) +- [Google's Java style guide](https://google.github.io/styleguide/javaguide.html#s4.8.3.1-array-initializers) +- [Javascript's Prettier](https://prettier.io/docs/en/rationale.html#multi-line-objects) +- [Elixir Style Guide](https://github.com/christopheradams/elixir_style_guide) ## Appendix @@ -184,4 +190,4 @@ git clone https://github.com/inaka/sumo_db_mongo git clone https://github.com/inaka/lsl git clone https://github.com/inaka/niffy git clone https://github.com/inaka/toy_kv -``` \ No newline at end of file +``` diff --git a/doc/FormattingDecisionPipes.md b/doc/FormattingDecisionPipes.md index 76e3e189..fb0c539b 100644 --- a/doc/FormattingDecisionPipes.md +++ b/doc/FormattingDecisionPipes.md @@ -1,4 +1,4 @@ -## Formatting Decision: Pipes +# Formatting Decision: Pipes This is a document explaining our reasoning behind the formatting decision for pipes. @@ -28,11 +28,13 @@ Our analysis is very naive and we hope it is good enough, but we are open to con We have decided to analyse the OTP and WhatsApp code base, since this was too big code bases we had access to. We `grep` `.hrl` and `.erl` files recursively for lines that end or start with a `|` character, disregarding: - - whitespace `\s*` - - lines that contain more than one `|` on the same line: `grep -v -E '\|.*\|'` - - lines that contain lists or comments `grep -v -E '(\]|\[|%)'` + +- whitespace `\s*` +- lines that contain more than one `|` on the same line: `grep -v -E '\|.*\|'` +- lines that contain lists or comments `grep -v -E '(\]|\[|%)'` Here follows our naive analysis script for reproducibility: + ```sh # count lines that end with a pipe (suffix) grep -r -E --include "*\.hrl" --include "*\.erl" '\w+\s*\|\s*$' . | grep -v -E '\|.*\|' | grep -v -E '(\]|\[|%)' | wc -l @@ -43,12 +45,14 @@ grep -r -E --include "*\.hrl" --include "*\.erl" '^\s+\|\s*\w+' . | grep -v -E ' We found that both OTP and WhatsApp prefer pipes as a prefix. OTP: - - Pipes as a Suffix: 531 - - Pipes as a Prefix: 734 + +- Pipes as a Suffix: 531 +- Pipes as a Prefix: 734 WhatsApp - - Pipes as a Suffix: x - - Pipes as a Prefix: 50x + +- Pipes as a Suffix: x +- Pipes as a Prefix: 50x ## History diff --git a/doc/FormattingDecisionSpaces.md b/doc/FormattingDecisionSpaces.md index 9454962a..a019f676 100644 --- a/doc/FormattingDecisionSpaces.md +++ b/doc/FormattingDecisionSpaces.md @@ -1,4 +1,4 @@ -## Formatting Decision: Number of Spaces +# Formatting Decision: Number of Spaces This is a document explaining our reasoning behind the formatting decision for the default number of spaces, representing an indentation. This document assumes that indentation is not aligned and that indentation requires a fixed number of spaces. @@ -21,28 +21,34 @@ $ pcregrep -Mr --include=".*\.erl" --include=".*\.hrl" "\->\n\s\s\w" . | grep "\ ## Results OTP: - - 2 spaces: 16366 - - 4 spaces: 113481 + +- 2 spaces: 16366 +- 4 spaces: 113481 WhatsApp: - - 2 spaces: x - - 4 spaces: 20x + +- 2 spaces: x +- 4 spaces: 20x [Kazoo](https://github.com/2600hz/kazoo): - - 2 spaces: 0 - - 4 spaces: 37355 + +- 2 spaces: 0 +- 4 spaces: 37355 [MongooseIM](https://github.com/esl/MongooseIM): - - 2 spaces: 30 - - 4 spaces: 13740 + +- 2 spaces: 30 +- 4 spaces: 13740 [ejabberd](https://github.com/processone/ejabberd): - - 2 spaces: 349 - - 4 spaces: 9010 + +- 2 spaces: 349 +- 4 spaces: 9010 [Inaka repos](./clone_inaka.sh): - - 2 spaces: 2800 - - 4 spaces: 1349 + +- 2 spaces: 2800 +- 4 spaces: 1349 ## Decision diff --git a/doc/FormattingDecisionWhenMultilineGuards.md b/doc/FormattingDecisionWhenMultilineGuards.md index 15d8bc4e..2ad4ab5e 100644 --- a/doc/FormattingDecisionWhenMultilineGuards.md +++ b/doc/FormattingDecisionWhenMultilineGuards.md @@ -8,10 +8,10 @@ as this most closely matched our goals. Goals: - - Create/keep differentiation between the guards and the body of the function. - - Minimize diff when changing a single line, impacts surrounding context. - - Minimize indentation, as this causes more lines not to fit and more line breaks. - - Consistent indenting by 4, to aid writability +- Create/keep differentiation between the guards and the body of the function. +- Minimize diff when changing a single line, impacts surrounding context. +- Minimize indentation, as this causes more lines not to fit and more line breaks. +- Consistent indenting by 4, to aid writability Here you can see all the other candidates we evaluated against our goals. @@ -20,10 +20,10 @@ Here you can see all the other candidates we evaluated against our goals. The first idea was for `when` to be placed on a new line and doubly-indented. This worked well, except for the case of extremely small functions and multiline guards (for example, because of a comment): - - ✅ Differentiation between guards and body - - ✅ Minimize diff when changing a single line - - ❌ Minimize indentation - - ❌ Indenting by 4 +- ✅ Differentiation between guards and body +- ✅ Minimize diff when changing a single line +- ❌ Minimize indentation +- ❌ Indenting by 4 ```erlang bar(X) @@ -48,10 +48,10 @@ insert_nested({Field, Meta, Key0, Value0}, Comments0) We tried to indent `when` with one full indentation. This is good in for multiline guards, but fails the guard-body distinction check when guard is on a separate line, but single-line - - ❌ Differentiation between guards and body - - ✅ Minimize diff when changing a single line - - ✅ Minimize indentation - - ✅ Indenting by 4 +- ❌ Differentiation between guards and body +- ✅ Minimize diff when changing a single line +- ✅ Minimize indentation +- ✅ Indenting by 4 ```erlang bar(Some, Very, Long, Arguments) @@ -65,10 +65,10 @@ Another idea was to indent `when` by half the indentation. This is preserves the differentiation between guards and body. We find this rather awkward as we don't use half-indentation anywhere else in the formatter. - - ✅ Differentiation between guards and body - - ✅ Minimize diff when changing a single line - - ✅ Minimize indentation - - ❌ Indenting by 4 +- ✅ Differentiation between guards and body +- ✅ Minimize diff when changing a single line +- ✅ Minimize indentation +- ❌ Indenting by 4 ```erlang bar(X) @@ -93,10 +93,10 @@ insert_nested({Field, Meta, Key0, Value0}, Comments0) We tried not to indent `when`. This creates relatively little differentiation between guards and body of the function. - - ❌ Differentiation between guards and body - - ✅ Minimize diff when changing a single line - - ✅ Minimize indentation - - ❌ Indenting by 4 +- ❌ Differentiation between guards and body +- ✅ Minimize diff when changing a single line +- ✅ Minimize indentation +- ❌ Indenting by 4 ```erlang bar(X) @@ -110,10 +110,10 @@ when is_tuple(X); Small functions and multiline guards look great with "floating" guards. - - ✅ Differentiation between guards and body - - ❌ Minimize diff when changing a single line - - ❌ Minimize indentation - - ❌ Indenting by 4 +- ✅ Differentiation between guards and body +- ❌ Minimize diff when changing a single line +- ❌ Minimize indentation +- ❌ Indenting by 4 ```erlang bar(X) when is_tuple(X); @@ -150,10 +150,10 @@ insert_nested(KeyValue, Comments0) when Field =:= map_field_assoc; We tried to treat `when` and `->` as a sort-of parenthesis. This preserves clarity, by creating differentiation between guards and body of the function, but has relatively low current usage. - - ✅ Differentiation between guards and body - - ✅ Minimize diff when changing a single line - - ✅ Minimize indentation - - ✅ Indenting by 4 +- ✅ Differentiation between guards and body +- ✅ Minimize diff when changing a single line +- ✅ Minimize indentation +- ✅ Indenting by 4 ```erlang formatted newlinewhen bar(X) when @@ -182,10 +182,10 @@ insert_nested({Field, Meta, Key0, Value0}, Comments0) when We tried to bring back the `->` onto the same line for a more compact representation, compared to `Arrow on newline`, but this loses the differentiation between guards and body. - - ❌ Differentiation between guards and body - - ✅ Minimize diff when changing a single line - - ✅ Minimize indentation - - ✅ Indenting by 4 +- ❌ Differentiation between guards and body +- ✅ Minimize diff when changing a single line +- ✅ Minimize indentation +- ✅ Indenting by 4 ```erlang unformatted newlinewhen bar(X) when @@ -200,10 +200,10 @@ bar(X) when We tried to bring back the body onto the same line for a more compact representation, compared to `Arrow on newline`, but this also loses the differentiation between guards and body. - - ❌ Differentiation between guards and body - - ✅ Minimize diff when changing a single line - - ✅ Minimize indentation - - ✅ Indenting by 4 +- ❌ Differentiation between guards and body +- ✅ Minimize diff when changing a single line +- ✅ Minimize indentation +- ✅ Indenting by 4 ```erlang bar(X) when @@ -219,10 +219,10 @@ We can move `when` to the same line as guards and still keep the `->` on a separ This creates an even greater differentiation between guards and body, than when only the `->` is on a separate line. We find this rather awkward to type when we have multiline guards: - - ✅ Differentiation between guards and body - - ✅ Minimize diff when changing a single line - - ❌ Minimize indentation - - ❌ Indenting by 4 +- ✅ Differentiation between guards and body +- ✅ Minimize diff when changing a single line +- ❌ Minimize indentation +- ❌ Indenting by 4 ```erlang post({Generate, _L, _Pattern, _Expr}, St, expr) @@ -256,10 +256,10 @@ insert_nested({Field, Meta, Key0, Value0}, Comments0) We can indent `when` by 3 to cause constant indentation by 4 for the other multiline guards. This creates an even greater differentiation between guards and body, than when only the `->` is on a separate line. - - ✅ Differentiation between guards and body - - ✅ Minimize diff when changing a single line - - ✅ Minimize indentation - - ✅ Indenting by 4 +- ✅ Differentiation between guards and body +- ✅ Minimize diff when changing a single line +- ✅ Minimize indentation +- ✅ Indenting by 4 ```erlang insert_nested({Field, Meta, Key0, Value0}, Comments0) @@ -292,5 +292,5 @@ bar(X) ## References - - Some of the discussion started on [github issue 20](https://github.com/WhatsApp/erlfmt/issues/20). - - This [insert_nested](https://github.com/WhatsApp/erlfmt/blob/487d2ab216ddbadd5b42c8d9eb7ad7a8cfe0e504/src/erlfmt_recomment.erl#L142) function was found in erlfmt. +- Some of the discussion started on [github issue 20](https://github.com/WhatsApp/erlfmt/issues/20). +- This [insert_nested](https://github.com/WhatsApp/erlfmt/blob/487d2ab216ddbadd5b42c8d9eb7ad7a8cfe0e504/src/erlfmt_recomment.erl#L142) function was found in erlfmt. diff --git a/doc/Readme.md b/doc/Readme.md index 0998ed55..b2b61bc7 100644 --- a/doc/Readme.md +++ b/doc/Readme.md @@ -1,4 +1,4 @@ -## Formatting Decisions +# Formatting Decisions Formatting decision documents are intended to explain our reasoning for making certain formatting decisions. Typically these documents contain a set of goals, which are found close to the top of the document, @@ -11,13 +11,15 @@ After version 1.0.0 these decisions will be much harder to back track on, but what might be unclear is that before version 1.0.0 these decisions are still very much up for debate. Debating on formatting decisions is easy: - - Read the document - - If you see that we unfairly judged an alternative against our goals, this is a feedback opportunity - - If you think that the goals that we created are not fair, this is a feedback opportunity + +- Read the document +- If you see that we unfairly judged an alternative against our goals, this is a feedback opportunity +- If you think that the goals that we created are not fair, this is a feedback opportunity Feedback can be given by: - - [Creating an github issue](https://github.com/WhatsApp/erlfmt/issues/new) - - Or commenting on the active pull request + +- [Creating an github issue](https://github.com/WhatsApp/erlfmt/issues/new) +- Or commenting on the active pull request See [Pull Requests](https://github.com/WhatsApp/erlfmt/pulls) for incoming formatting decisions and the list below for merged formatting decisions. @@ -25,13 +27,13 @@ As always, you can preferably provide feedback, by simply trying out the `erlfmt ## Merged Formatting Decisions - - [DefaultWidth](./FormattingDecisionDefaultWidth.md) - - [Ignore](./FormattingDecisionIgnore.md) - - [Comments](./FormattingDecisionComments.md) - - [Pipes](./FormattingDecisionPipes.md) - - [Commas in Lists](./FormattingDecisionCommas.md) - - [Number of Spaces](./FormattingDecisionSpaces.md) - - [Multiline Lists](./FormattingDecisionLists.md) - - [List Comprehensions](./FormattingDecisionListComprehensions.md) - - [When with Multiline Guards](./FormattingDecisionWhenMultilineGuards.md) - - [Formatting Decision: Left Associative Equal and Dolon Operators](./FormattingDecisionAssociative.md) +- [DefaultWidth](./FormattingDecisionDefaultWidth.md) +- [Ignore](./FormattingDecisionIgnore.md) +- [Comments](./FormattingDecisionComments.md) +- [Pipes](./FormattingDecisionPipes.md) +- [Commas in Lists](./FormattingDecisionCommas.md) +- [Number of Spaces](./FormattingDecisionSpaces.md) +- [Multiline Lists](./FormattingDecisionLists.md) +- [List Comprehensions](./FormattingDecisionListComprehensions.md) +- [When with Multiline Guards](./FormattingDecisionWhenMultilineGuards.md) +- [Formatting Decision: Left Associative Equal and Dolon Operators](./FormattingDecisionAssociative.md) diff --git a/doc/RebarUsage.md b/doc/RebarUsage.md index 1fb66082..fd2574fa 100644 --- a/doc/RebarUsage.md +++ b/doc/RebarUsage.md @@ -1,4 +1,4 @@ -## Using erlfmt with Rebar +# Using erlfmt with Rebar The easiest way to use erlfmt is as a rebar plugin, by adding to your `rebar.config`: @@ -20,13 +20,13 @@ All erlfmt command-line options can be configured with defaults in your `rebar.c Now you can format the files in your project by running: ```sh -$ rebar3 fmt +rebar3 fmt ``` And you can add the following command in your CI to ensure your Erlang is formatted: ```sh -$ rebar3 fmt --check +rebar3 fmt --check ``` This means that `--check` overwrites `--write`. @@ -37,16 +37,17 @@ For example, if you want to format in place as specified in the `rebar.config` w but only format a single file, then you can overwrite the file list: ```sh -$ rebar3 fmt ./src/myfile.erl +rebar3 fmt ./src/myfile.erl ``` You could also setup your rebar3 config to: - - only format files that include a `%%% % @format` comment, using `require_pragma`, - - only check files and not format them, using `check` instead of `write`, - - output which files are being checked, using `verbose` and - - set the default width, using `{print_width, 100}` - - If you do not specify files, then the default is `{files, ["{src,include,test}/*.{hrl,erl,app.src}", "rebar.config"]}` - - Exclude generated files, like `src/erlfmt_parse.erl` + +- only format files that include a `%%% % @format` comment, using `require_pragma`, +- only check files and not format them, using `check` instead of `write`, +- output which files are being checked, using `verbose` and +- set the default width, using `{print_width, 100}` +- If you do not specify files, then the default is `{files, ["{src,include,test}/*.{hrl,erl,app.src}", "rebar.config"]}` +- Exclude generated files, like `src/erlfmt_parse.erl` ```erlang formatted rebarconfig3 {erlfmt, [ @@ -60,8 +61,10 @@ You could also setup your rebar3 config to: ``` See the command line help for a complete list of options: + ```sh -$ rebar3 --help +rebar3 --help ``` + Simply convert dashes to underscores as appropriate, for example `--insert-pragma`, becomes `insert_pragma`.