diff options
Diffstat (limited to 'docs/handbook/ci/formatting.md')
| -rw-r--r-- | docs/handbook/ci/formatting.md | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/docs/handbook/ci/formatting.md b/docs/handbook/ci/formatting.md new file mode 100644 index 0000000000..9d2ddb35a4 --- /dev/null +++ b/docs/handbook/ci/formatting.md @@ -0,0 +1,298 @@ +# Code Formatting + +## Overview + +Project Tick uses [treefmt](https://github.com/numtide/treefmt) orchestrated through +[treefmt-nix](https://github.com/numtide/treefmt-nix) to enforce consistent code formatting +across the entire monorepo. The formatting configuration lives in `ci/default.nix` and +covers JavaScript, Nix, YAML, GitHub Actions workflows, and sorted-list enforcement. + +--- + +## Configured Formatters + +### Summary Table + +| Formatter | Language/Files | Key Settings | +|-------------|-------------------------------|-------------------------------------------| +| `actionlint` | GitHub Actions YAML | Default (syntax + best practices) | +| `biome` | JavaScript / TypeScript | Single quotes, optional semicolons | +| `keep-sorted`| Any (marked sections) | Default | +| `nixfmt` | Nix expressions | nixfmt-rfc-style | +| `yamlfmt` | YAML files | Retain line breaks | +| `zizmor` | GitHub Actions YAML | Security scanning | + +--- + +### actionlint + +**Purpose**: Validates GitHub Actions workflow files for syntax errors, type mismatches, +and best practices. + +**Scope**: `.github/workflows/*.yml` + +**Configuration**: Default — no custom settings. + +```nix +programs.actionlint.enable = true; +``` + +**What it catches**: +- Invalid workflow syntax +- Missing or incorrect `runs-on` values +- Type mismatches in expressions +- Unknown action references + +--- + +### biome + +**Purpose**: Formats JavaScript and TypeScript source files with consistent style. + +**Scope**: All `.js` and `.ts` files except `*.min.js` + +**Configuration**: + +```nix +programs.biome = { + enable = true; + validate.enable = false; + settings.formatter = { + useEditorconfig = true; + }; + settings.javascript.formatter = { + quoteStyle = "single"; + semicolons = "asNeeded"; + }; + settings.json.formatter.enabled = false; +}; +settings.formatter.biome.excludes = [ + "*.min.js" +]; +``` + +**Style rules**: + +| Setting | Value | Effect | +|---------------------|----------------|-------------------------------------------| +| `useEditorconfig` | `true` | Respects `.editorconfig` (indent, etc.) | +| `quoteStyle` | `"single"` | Uses `'string'` instead of `"string"` | +| `semicolons` | `"asNeeded"` | Only inserts `;` where ASI requires it | +| `validate.enable` | `false` | No lint-level validation, only formatting | +| `json.formatter` | `disabled` | JSON files are not formatted by biome | + +**Exclusions**: `*.min.js` — Minified JavaScript files are never reformatted. + +--- + +### keep-sorted + +**Purpose**: Enforces alphabetical ordering in marked sections of any file type. + +**Scope**: Files containing `keep-sorted` markers. + +```nix +programs.keep-sorted.enable = true; +``` + +**Usage**: Add markers around sections that should stay sorted: + +``` +# keep-sorted start +apple +banana +cherry +# keep-sorted end +``` + +--- + +### nixfmt + +**Purpose**: Formats Nix expressions according to the RFC-style convention. + +**Scope**: All `.nix` files. + +```nix +programs.nixfmt = { + enable = true; + package = pkgs.nixfmt; +}; +``` + +The `pkgs.nixfmt` package from the pinned Nixpkgs provides the formatter. This +is `nixfmt-rfc-style`, the official Nix formatting standard. + +--- + +### yamlfmt + +**Purpose**: Formats YAML files with consistent indentation and structure. + +**Scope**: All `.yml` and `.yaml` files. + +```nix +programs.yamlfmt = { + enable = true; + settings.formatter = { + retain_line_breaks = true; + }; +}; +``` + +**Key setting**: `retain_line_breaks = true` — Preserves intentional blank lines between +YAML sections, preventing the formatter from collapsing the file into a dense block. + +--- + +### zizmor + +**Purpose**: Security scanner for GitHub Actions workflows. Detects injection +vulnerabilities, insecure defaults, and untrusted input handling. + +**Scope**: `.github/workflows/*.yml` + +```nix +programs.zizmor.enable = true; +``` + +**What it detects**: +- Script injection via `${{ github.event.* }}` in `run:` steps +- Insecure use of `pull_request_target` +- Unquoted expressions that could be exploited +- Dangerous permission configurations + +--- + +## treefmt Global Settings + +```nix +projectRootFile = ".git/config"; +settings.verbose = 1; +settings.on-unmatched = "debug"; +``` + +| Setting | Value | Purpose | +|--------------------|---------------|----------------------------------------------| +| `projectRootFile` | `.git/config` | Identifies repository root for treefmt | +| `settings.verbose` | `1` | Logs which files each formatter processes | +| `settings.on-unmatched` | `"debug"` | Files with no matching formatter are logged at debug level | + +--- + +## Running Formatters + +### In CI + +The formatting check runs as a Nix derivation: + +```bash +nix-build ci/ -A fmt.check +``` + +This: +1. Copies the full source tree (excluding `.git`) into the Nix store +2. Runs all configured formatters +3. Fails with a diff if any file would be reformatted + +### Locally (Nix Shell) + +```bash +cd ci/ +nix-shell # enter CI dev shell +treefmt # format all files +treefmt --check # check without modifying (dry run) +``` + +### Locally (Nix Build) + +```bash +# Just check (no modification): +nix-build ci/ -A fmt.check + +# Get the formatter binary: +nix-build ci/ -A fmt.pkg +./result/bin/treefmt +``` + +--- + +## Source Tree Construction + +The treefmt check operates on a clean copy of the source tree: + +```nix +fs = pkgs.lib.fileset; +src = fs.toSource { + root = ../.; + fileset = fs.difference ../. (fs.maybeMissing ../.git); +}; +``` + +This: +- Takes the entire repository directory (`../.` from `ci/`) +- Excludes the `.git` directory (which is large and irrelevant for formatting) +- `fs.maybeMissing` handles the case where `.git` doesn't exist (e.g., in tarballs) + +The resulting source is passed to`fmt.check`: + +```nix +check = treefmtEval.config.build.check src; +``` + +--- + +## Formatter Outputs + +The formatting system exposes three Nix attributes: + +```nix +{ + shell = treefmtEval.config.build.devShell; # Interactive shell + pkg = treefmtEval.config.build.wrapper; # treefmt binary + check = treefmtEval.config.build.check src; # CI check derivation +} +``` + +| Attribute | Use Case | +|------------|--------------------------------------------------------| +| `fmt.shell` | `nix develop .#fmt.shell` — interactive formatting | +| `fmt.pkg` | The treefmt wrapper with all formatters bundled | +| `fmt.check` | `nix build .#fmt.check` — CI formatting check | + +--- + +## Troubleshooting + +### "File would be reformatted" + +If CI fails with formatting issues: + +```bash +# Enter the CI shell to get the exact same formatter versions: +cd ci/ +nix-shell + +# Format all files: +treefmt + +# Stage and commit the changes: +git add -u +git commit -m "style(repo): apply treefmt formatting" +``` + +### Editor Integration + +For real-time formatting in VS Code: + +1. Use the biome extension for JavaScript/TypeScript +2. Configure single quotes and optional semicolons to match CI settings +3. Use nixpkgs-fmt or nixfmt for Nix files + +### Formatter Conflicts + +Each file type has exactly one formatter assigned by treefmt. If a file matches +multiple formatters, treefmt reports a conflict. The current configuration avoids +this by: +- Disabling biome's JSON formatter +- Having non-overlapping file type coverage |
