ErlAlign

buildHex.pm

A column-aligning code formatter for Erlang source code, inspired by Go's gofmt. Works as a post-processor on top of the erlfmt formatter.

What it does

ErlAlign scans consecutive lines that share the same indentation and pattern type, then pads them so their operators and values line up vertically. It aligns:

Features

Record field alignment

% before (erlfmt output)
User = #user{
  name = <<"Alice">>,
  age = 30,
  occupation = <<"developer">>
}.

% after (with ErlAlign)
User = #user{
  name = <<"Alice">>,
  age =  30,
  occupation = <<"developer">>
}.

Variable assignment alignment

% before
X = 1,
Foo = <<"bar">>,
SomethingLong = 42.

% after
X              = 1,
Foo            = <<"bar">>,
SomethingLong = 42.

Case arrow alignment

% before
case Result of
  {ok, Value} -> Value;
  {error, _} = Err -> Err
end.

% after
case Result of
  {ok, Value}      -> Value;
  {error, _} = Err -> Err
end.

Documentation conversion

ErlAlign also includes erlalign_docs module for converting EDoc @doc blocks to OTP-27 -doc attributes:

% before (EDoc format)
%% @doc
%% Returns the user record with the given ID.
-spec user(id()) -> {ok, user()} | {error, atom()}.
user(UserID) -> ...

% after (OTP-27 format)
-doc """
Returns the user record with the given ID.
""".
-spec user(id()) -> {ok, user()} | {error, atom()}.
user(UserID) -> ...

Installation

Requirements

As a rebar3 plugin

Add erlalign to your project's rebar.config to use it as a rebar3 plugin:

From GitHub (recommended for latest)

{plugins, [
  {erlalign, {git, "https://github.com/saleyn/erlalign.git", {branch, "main"}}}
]}.

From Hex.pm (when available)

{plugins, [
  {erlalign, "0.1.0"}
]}.

Using with rebar3

After adding erlalign as a plugin, you can use it in your project:

Format your project

# Format all Erlang files in src/
rebar3 format

# Format specific directory
rebar3 format src/

# Format specific file or files
rebar3 format src/mymodule.erl src/another.erl

# Format apps and lib directories
rebar3 format apps/ lib/

Check formatting without modifying files

# Check if files need formatting
rebar3 format --check src/

# Useful in CI/CD to fail if files aren't formatted
rebar3 format --check

Preview changes

# See what would change without writing files
rebar3 format --dry-run src/

# Also good for code review
rebar3 format --dry-run apps/myapp/src/

Advanced options

# Custom line length
rebar3 format --line-length 120 src/

# Suppress output
rebar3 format --silent src/

# Combine options
rebar3 format --line-length 100 --check src/

Converting documentation

erlalign also includes a documentation converter for converting EDoc @doc blocks to OTP-27 -doc attributes:

# Convert documentation in all files
rebar3 edoc-to-doc

# Check documentation conversion without modifying
rebar3 edoc-to-doc --check

# Preview documentation changes
rebar3 edoc-to-doc --dry-run

# Keep separator lines
rebar3 edoc-to-doc --keep-separators

# Custom line length for wrapped docs
rebar3 edoc-to-doc --line-length 100

Integration tips

Git hooks (pre-commit)

Add to your .git/hooks/pre-commit:

#!/bin/bash
set -e

# Check formatting before commit
rebar3 format --check

Make it executable:

chmod +x .git/hooks/pre-commit

GitHub Actions CI

Add to your .github/workflows/ci.yml:

- name: Check formatting
  run: rebar3 format --check

Gitlab CI

Add to your .gitlab-ci.yml:

format_check:
  script:
    - rebar3 format --check

Combine with erlfmt

For maximum code cleanliness, combine erlalign with erlfmt:

# First format with erlfmt (basic formatting)
rebar3 fmt

# Then align with erlalign (column alignment)
rebar3 format

Creating a Makefile target

Add to your Makefile:

.PHONY: format fmt check-fmt

fmt: format

format:
    rebar3 format

check-fmt:
    rebar3 format --check

Then use:

make format          # Format code
make check-fmt       # Check formatting

Configuration per project

Create a global config file at ~/.config/erlalign/.formatter.exs:

[
  {line_length, 100},
  {trim_eol_ws, true},
  {eol_at_eof, off}
].

This configuration will be used automatically by all projects using erlalign as a plugin or binary.

Usage

Formatting with rebar3

# Format all Erlang files in src/
rebar3 format

# Format specific directory
rebar3 format src/

# Use custom line length
rebar3 format --line-length 120

# Check mode (fail if formatting needed)
rebar3 format --check src/

# Dry run (preview changes)
rebar3 format --dry-run src/mymodule.erl

Options

Flag Default Description
--line-length N98 Maximum line length for alignment decisions
--check off Exit with error if any file would be changed
--dry-run off Print what would be changed without modifying files
-s, --silent off Suppress output
-h, --help Show help message

Converting documentation with rebar3

# Convert all EDoc @doc blocks to -doc attributes
rebar3 edoc-to-doc

# Custom line length for wrapped docs
rebar3 edoc-to-doc --line-length 100

# Keep separator lines (don't remove %%----)
rebar3 edoc-to-doc --keep-separators

# Check mode
rebar3 edoc-to-doc --check src/

# Dry run
rebar3 edoc-to-doc --dry-run src/

Options

Flag Default Description
--line-length N80 Line wrap width for formatted docs
--keep-separators off Preserve %%---- separator lines (removed by default)
--check off Check mode - fail if files would change
--dry-run off Preview changes without writing
-s, --silent off Suppress output
-h, --help Show help message

Command-line binary

Build the standalone erlalign binary:

# Build binary with make
make escriptize

# Or with rebar3
rebar3 escriptize

The binary will be located at _build/default/bin/erlalign.

Using the binary

Format Erlang files directly from the command line:

# Format a single file (modifies in place)
erlalign src/mymodule.erl

# Format multiple files
erlalign src/ lib/ test/

# Check formatting without modifying
erlalign --check src/

# Dry run (preview changes)
erlalign --dry-run src/mymodule.erl

# Trim trailing whitespace from end of lines (default)
erlalign --trim-eol-ws src/

# Disable trailing whitespace trimming
erlalign --no-trim-eol-ws src/

# Set line length
erlalign --line-length 120 src/

# Handle end-of-file newlines
erlalign --eol-at-eof add src/      # Add newline if missing
erlalign --eol-at-eof remove src/   # Remove trailing newline

# Convert @doc to -doc attributes (OTP 27+)
erlalign --doc src/

# Keep separator lines in documentation
erlalign --keep-separators src/

# Suppress output
erlalign --silent src/

# Show help
erlalign --help

Binary options

Flag Default Description
--line-length N98 Maximum line length for alignment decisions
--trim-eol-ws on Trim trailing whitespace from end of lines
--no-trim-eol-ws off Keep trailing whitespace
--eol-at-eof VALUE off Handle EOF newlines: add, remove, or off
--keep-separators off Preserve %%---- separator lines in docs
--doc off Convert @doc to -doc attributes (OTP 27+)
--check off Check mode - exit with error if unchanged needed
--dry-run off Preview changes without writing files
-s, --silent off Suppress output messages
-h, --help Show help message

Global configuration

The binary supports global configuration via ~/.config/erlalign/.formatter.exs:

[
  {line_length, 120},
  {trim_eol_ws, true},
  {eol_at_eof, off}
].

These default values can be overridden with command-line flags.

Programmatic usage

Use the modules directly from Erlang code:

% Format code
{ok, Code} = file:read_file("src/mymodule.erl"),
Formatted = erlalign:format(Code, [{line_length, 100}]),
ok = file:write_file("src/mymodule.erl", Formatted).

% Convert documentation
erlalign_docs:process_file("src/mymodule.erl", [
  {line_length, 100},
  {remove_doc_separators, true}
]).

% Or with output to different file
erlalign_docs:process_file("src/mymodule.erl", [
  {output, "src/mymodule_formatted.erl"},
  {line_length, 80}
]).

Module functions

erlalign module:

erlalign_docs module:

Configuration

Global configuration file: ~/.config/erlalign/.formatter.exs

[
  {line_length, 120}
].

Building

rebar3 compile

Testing

rebar3 eunit

License

MIT License - see LICENSE file

See Also