summaryrefslogtreecommitdiff
path: root/docs/handbook/ci/formatting.md
blob: 9d2ddb35a4f3d6c47e5c9cadc9be4a99feea6404 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
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