Introduction

Welcome to the treefmt book! This is a work-in-progress user guide for the treefmt project.

If you're new to treefmt, try the Quick Start tutorial first!

Contributing

The source repository for this book is hosted on GitHub. Please refer to Contributing guideline.

License

The treefmt bindings, and this user guide, are licensed under the MIT license.

Quickstart

  1. Install this tool.
  2. Run treefmt --init.
  3. edit the configuration in treefmt.toml
  4. Run treefmt.

Installation

You can install treefmt with two option below. The best option is to download the release binary as you will get the stable version of treefmt.

Download release binary

Download the stable version of treefmt from release binary.

Building from source

Non-Nix User

Install rust using rustup by following the instruction.

To try the project, run:

$ cargo run -- --help

If you want to build the project, run:

$ cargo build

and find the treefmt binary in the target folder.

Nix User

Non-flake user

This repository can be used using plain nix-build or nix-shell. To build the package, just run:

$ nix-build -A treefmt

Nix-flake user

If you want to use this repo with flakes feature, please enable it using the following method:

Linux and Windows Subsystem Linux 2 (WSL2)

Install Nix as instructed above. Next, install nixUnstable by running the following code:

nix-env -iA nixpkgs.nixFlakes

Lastly, open your ~/.config/nix/nix.conf or /etc/nix/nix.conf file and add:

experimental-features = nix-command flakes

NixOS

Add the following code into your configuration.nix:

{ pkgs, ... }: {
  nix = {
    package = pkgs.nixFlakes;
      extraOptions = ''
        experimental-features = nix-command flakes
      '';
  };
}

And finally, run the following command:

$ nix build

The treefmt binary will be available in the result folder.

Alternatively, you can run:

$ nix run . -- --help

From the root of this project.

Formatters

In order to catch up with all the formatter available for different programming language, we create two file as our guideline for each formatter's creator.

  1. Formatter Specifications - Guideline to have smooth integration with treefmt
  2. Formatter Example - List formatter available with the config example to be inserted in treefmt.tom

Formatter spec

In order to keep the design of treefmt simple, we want all of the formatters to adhere to a unified specification. This document outlines that spec.

Command-line interface

A formatter MUST adhere to the following interface:

<command> [options] [...<files>]

Where

  • <command> is the name of the formatter.
  • [options] is any number of flags and options that the formatter wants to provide.
  • [...<files>] is one or more files that the formatter should process.

Whenever the program is invoked with the list of files, it MUST only process all the files that are passed and format them. Files that are not passed should never be formatted.

If, and only if, a file format has changed, the formatter MUST write the new content in place of the original file.

Example

$ rustfmt --edition 2018 src/main.rs src/lib.rs

Formatting details

A formatted file MUST be valid. This is a strong contract; the syntax or semantic must never be broken by the formatter.

A formatted file SHOULD be idempotent. Meaning that if the formatter it run twice on a file, the file should not change on the second invocation.

The formatter MUST NOT do file traversal when a list of files is passed to it. This is the responsibility of treefmt.

Design notes

We assume that the code is stored in source control, which is why it's fine to write the new content in place.

A list of known formatters

Here is a list of all the formatters we tested. Feel free to send a PR to add other ones!

prettier

An opinionated code formatter that supports many languages.

command = "prettier"
options = ["--write"]
includes = [
    "*.css",
    "*.html",
    "*.js",
    "*.json",
    "*.jsx",
    "*.md",
    "*.mdx",
    "*.scss",
    "*.ts",
    "*.yaml",
]

Black

A python formatter.

command = "black"
includes = ["*.py"]

clang-format

A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf/C# code.

command = "clang-format"
options = [ "-i" ]
includes = [ "*.c", "*.cpp", "*.cc", "*.h", "*.hpp" ]

Note: This example focuses on C/C++ but can be modified to use with other languages.

Elm

command = "elm-format"
options = ["--yes"]
includes = ["*.elm"]

Go

command = "gofmt"
options = ["-w"]
includes = ["*.go"]

Ormolu

Haskell formatter. Make sure to use ormolu 0.1.4.0+ as older versions don't adhere to the spec.

command = "ormolu"
options = [
    "--ghc-opt", "-XBangPatterns",
    "--ghc-opt", "-XPatternSynonyms",
    "--ghc-opt", "-XTypeApplications",
    "--mode", "inplace",
    "--check-idempotence",
]
includes = ["*.hs"]

stylish-haskell

Another Haskell formatter.

command = "stylish-haskell"
options = [ "--inplace" ]
includes = [ "*.hs" ]

nixpkgs-fmt

Nix code formatter.

command = "nixpkgs-fmt"
includes = ["*.nix"]

rustfmt

command = "rustfmt"
options = ["--edition", "2018"]
includes = ["*.rs"]

rufo

Rufo is an opinionated ruby formatter. By default it exits with status 3 on file change so we have to pass the -x option.

command = "rufo"
options = ["-x"]
includes = ["*.rb"]

cargo fmt

cargo fmt is not supported as it doesn't follow the spec. It doesn't allow to pass arbitrary files to be formatted, which treefmt relies on. Use rustfmt instead (which is what cargo fmt uses under the hood).

shfmt

A shell code formatter.

command = "shfmt"
options = [
  "-i",
  "2",  # indent 2
  "-s",  # simplify the code
  "-w",  # write back to the file
]
includes = ["*.sh"]

terraform

terraform fmt only supports formatting one file at the time. See https://github.com/hashicorp/terraform/pull/28191

Configuration format

treefmt depends on the treefmt.toml to map file extensions to actual code formatters. That file is searched for recursively from the current folder and up unless the --config <path> option is passed.

[formatter.<name>]

This section describes the integration between a single formatter and treefmt.

  • command: A list of arguments to execute the formatter. This will be composed with the options attribute during invocation. The first argument is the name of the executable to run.

  • options: A list of extra arguments to add to the command. This is typically project-specific arguments.

  • includes: A list of glob patterns used to select files. Usually this would be something like [ "*.sh" ] to select all the shell scripts. Sometimes, full filenames can be passed. Eg: [ "Makefile" ].

  • excludes: A list of glob patterns to deny. If any of these patterns match, the file will be excluded.

[global]

This section describes the config that applies to every formatters.

  • excludes: A list of glob patterns to deny. If any of these patterns match, the file will be excluded. This list is appended to individual formatters' exclude lists.

Contribution Guidelines for the treefmt Project

Thank you for considering contributing to treefmt. This file contains instructions that will help you to make a contribution.

Before Contributing

Before sending your pull requests, make sure that you read the whole guidelines. If you have any doubt on the contributing guide, please feel free to state it clearly in an issue or ask the community in treefmt matrix server.

Use your best judgement, and feel free to propose changes to this document in a pull request.

Guidelines for Developers

First contribution?

Please take a few minutes to read GitHub's guide on How to Contribute to Open Source. It's a quick read, and it's a great way to introduce yourself to how things work behind the scenes in open-source projects.

Getting started

  • Make sure you have a GitHub account.
  • Take a look at existing issues.
  • If you need to create an issue:
    • Make sure to clearly describe it.
    • Including steps to reproduce when it is a bug.
    • Include the version of treefmt used.
    • Include the database driver and version.
    • Include the database version.

Making changes

  • Fork the repository on GitHub.
  • Create a branch on your fork.
    • You can usually base it on the master branch.
    • Make sure not to commit directly to master.
  • Make commits of logical and atomic units.
  • Make sure you have added the necessary tests for your changes.
  • Push your changes to a topic branch in your fork of the repository.
  • Submit a pull request to the original repository.

Examples of git history

Git history that we want to have
*   e3ed88b (HEAD -> contribution-guide, upstream/master, origin/master, origin/HEAD, master) Merge pull request #470 from zimbatm/fix_lru_cache
|\
| * 1ab7d9f Use rayon for multithreading command
|/
*   e9c5bb4 Merge pull request #468 from zimbatm/multithread
|\
| * de2d6cf Add lint property for Formatter struct
| * cd2ed17 Fix impl on Formatter get_command() function
|/
*   028c344 Merge pull request #465 from rayon/0.15.0-release
|\
| * 7b619d6 0.15.0 release
|/
*   acdf7df Merge pull request #463 from zimbatm/support-multi-part-namespaces
Git history that we are trying to avoid
*   4c8aca8 Merge pull request #120 from zimbatm/add-rayon
|\
| * fc2b449 use rayon for engine now
| * 2304683 add rayon config
| * 5285bd3 bump base image to F30
* |   4d0fbe2 Merge pull request #114 from rizary/create_method_create_release
|\ \
| * | 36a9396 test changed
| * | 22f681d method create release for github created
* | |   2ef4ea1 Merge pull request #119 from rizary/config.rs
|\ \ \
| |/ /
|/| |
| * | 5f1b8f0 unused functions removed
* | |   a93c361 Merge pull request #117 from zimbatm/add-getreleases-to-abstract
|\ \ \
| |/ /
|/| |
| * | 0a97236 add get_releses for Cargo
| * | 55e4c57 add get_releases/get_release into engine.rs
|/ /
* |   badeddd Merge pull request #101 from zimbatm/extreme-cachin

Checkers/linters/formatters

Run treefmt in the source directory before you commit your changes.


Additionally, it's always good to work on improving/adding examples and documentation.

Thank you for your interest!