efmt 0.2.0

Erlang code formatter
Documentation
How Macros and Directives are Handled
=====================================

Note that this document doesn't explain Erlang macros and directives themselves.
So if you want to know the detail of them, please refer to
[Erlang Reference Manual - 9. Preprocessor](https://www.erlang.org/doc/reference_manual/macros.html).


Macros
------

A unique point of `efmt` among various Erlang formatters is that it can handle macros correctly.

For example, `efmt` can format the following code containing unusual macros without errors.

### Original code

```erlang
-module(weird_macro).

-define(FOO, /).
-define(BAR, :format().
-define(baz(A), A).
-define(qux, -> [1, 2, 3], [).
-define(quux, )], [2,).
-define(a(A, B), A).

-export([?baz(?baz(main))?FOO 0]).

hello(A)->io?BAR "hello ~p\n",[A]).
main()?a(?qux a, b), c],[1, hello(world?quux 3].
```

### Formatted code

`$ efmt weird_macro.erl`

```erlang
-module(weird_macro).

-define(FOO, /).
-define(BAR, :format().
-define(baz(A), A).
-define(qux, -> [1, 2, 3], [).
-define(quux, )], [2,).
-define(a(A, B), A).

-export([?baz(?baz(main))?FOO 0]).

hello(A) ->
    io?BAR "hello ~p\n", [A]).
main() ?a(?qux a, b)
    , c],
    [1, hello(world?quux
    3].
```

To make it possible, during the parse phase, `efmt` collects macro definitions (i.e., `-define` directives) and expands macro calls (i.e., `?MACRO_NAME`) to build an abstract syntax tree-like structure from the input text.
Then, during the format phase, `efmt` traverses the tree and emits the formatted text representing each tree node.
When it visits tree nodes expanded from a macro, the formatted text of the original macro call is emitted instead.


`-include` and `-include_lib` Directives
----------------------------------------

To collect macro definitions needed to parse an input file, `efmt` try to process `-include` and `-include_lib` directives in the file as much as possible as the Erlang preprocessor does.
If `efmt` fails to find the include target file, it just ignores the directive.
Note that when `efmt` encounters an unknown macro, that might be defined in the ignored file, during the parse phase, the macro is replaced with the `'EFMT_DUMMY'` atom token.

Resolving the file path specified by `-include_lib` is more complicated than `-include` as the first path component can be the name of an Erlang application rather than a plain directory name.
So, `efmt` invokes `erl -noshell -eval 'io:format("~s", [code:lib_dir($APP_NAME)]), halt().'` command to try to resolve the application directory path.

### Include cache

The macro definitions collected during processing a `-include` or `-include_lib` directive is cached as a JSON file under `$PWD/.efmt/cache/v0/` directory (`v0` is the current cache format version).
The cache file is used when processing the same include target file next time to reduce the overhead of parsing the whole file from scratch.

### `efmt` options

Note that `efmt` provides some options to control how to handle those directives as follows:
- `--include-search-dir` (`-I` in short)
- `--include-cache-dir`
- `--disable-include`
- `--disable-include-cache`

Please run `$ efmt --help` to see the descriptions of those options.


Flow Control Directives
-----------------------

`efmt` doesn't recognize the following directives relating to flow control:
- `-undef(Macro)`
- `-ifdef(Macro)`
- `-ifndef(Macro)`
- `-else`
- `-endif`
- `-if(Condition)`
- `-elif(Condigion)`

Those are treated as ordinal attributes.
That is, both the "then" and "else" branch forms are always processed.
In many cases, this behavior doesn't cause a problem.
However, if either of the branches contains corrupted code, `efmt` would fail to format the file (see the example code below).

```erlang
-define(FOO, foo).

-ifdef(FOO).

%% As the `FOO` macro is defined in the same file, the Erlang compiler always evaluates this branch.
foo() -> ok.

-else.

%% On the other hand, this branch is always removed by the preprocessor.
%% But, `efmt` tries to parse this branch. Then it fails as the following function declaration is invalid.
foo()

-endif.
```