You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENTS.md
+230-6Lines changed: 230 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,12 +1,49 @@
1
1
# Fantomas
2
2
3
-
F# source code formatter. Parses F# to an untyped AST (via vendored FCS), transforms it to an intermediate representation called Oak (`SyntaxOak.fs`), then prints it back via writer events (`CodePrinter.fs` + `Context.fs`).
3
+
This file provides canonical guidance to coding agents working in this repository.
4
4
5
-
## Build & Test
5
+
## Issue Tracking
6
+
7
+
Issues for this repository are tracked at: https://github.com/AgricoZA/Ops/issues
8
+
9
+
## Build Commands
6
10
7
11
```bash
8
-
dotnet build fantomas.slnx
9
-
dotnet test src/Fantomas.Core.Tests/
12
+
# Initialize repository (downloads FCS files from F# compiler)
13
+
dotnet fsi build.fsx -p Init
14
+
15
+
# Full build pipeline (format check, build, test, pack)
16
+
dotnet fsi build.fsx
17
+
18
+
# Build only
19
+
dotnet build -c Release
20
+
21
+
# Run all tests
22
+
dotnet test
23
+
24
+
# Run specific test project
25
+
dotnet test src/Fantomas.Core.Tests
26
+
27
+
# Run a specific test by name
28
+
dotnet test src/Fantomas.Core.Tests --filter "FullyQualifiedName~TestClassName.TestName"
29
+
30
+
# Format all code
31
+
dotnet fsi build.fsx -p FormatAll
32
+
33
+
# Format only changed files
34
+
dotnet fsi build.fsx -p FormatChanged
35
+
36
+
# Check formatting without changing files
37
+
dotnet fantomas src docs build.fsx --check
38
+
39
+
# Run benchmarks
40
+
dotnet fsi build.fsx -p Benchmark
41
+
42
+
# Run analyzers
43
+
dotnet fsi build.fsx -p Analyze
44
+
45
+
# Build and serve documentation locally
46
+
dotnet fsi build.fsx -p Docs
10
47
```
11
48
12
49
## Diagnostic Scripts
@@ -20,9 +57,95 @@ All scripts accept a file path or stdin, with optional `--signature` and `--edit
20
57
21
58
Scripts require a debug build first (`dotnet build src/Fantomas/Fantomas.fsproj`).
22
59
23
-
## Changelog
60
+
## Architecture Overview
61
+
62
+
Fantomas is an opinionated F# source code formatter. It parses F# source into an AST, transforms it to a custom tree model (Oak), and reconstructs formatted source code.
63
+
64
+
### Project Structure
65
+
66
+
```
67
+
Fantomas.FCS → Fantomas.Core → Fantomas (CLI)
68
+
↓
69
+
Fantomas.Client (editor integration)
70
+
```
71
+
72
+
-**Fantomas.FCS**: Custom fork of F# compiler exposing only the parser. Downloads files from `dotnet/fsharp` repo via `Init` pipeline.
73
+
-**Fantomas.Core**: Core formatting library (netstandard2.0). Public API in `CodeFormatter.fsi`.
74
+
-**Fantomas**: CLI tool handling `.editorconfig` and `.fantomasignore` files.
75
+
-**Fantomas.Client**: LSP-based library for editor integration (not direct dependency on Core).
-**Partial Active Patterns**: Heavy use in `ASTTransformer.fs` for pattern matching on AST nodes
96
+
-**Custom Operators**: `!-` and `+>` in `CodePrinter.fs`
97
+
-**Signature Files (.fsi)**: Define module boundaries and public API
98
+
-**Type Extensions**: Extend AST types with range information
99
+
-**Event Sourcing**: Instructions written to event list in `Context.fs` before final output
100
+
101
+
### Trivia Handling
102
+
103
+
Trivia (comments, blank lines, conditional directives) are not part of the AST. They're:
104
+
1. Detected from `ParsedImplFileInputTrivia`/`ParsedSigFileInputTrivia` and `ISourceText`
105
+
2. Inserted into Oak nodes as `ContentBefore`/`ContentAfter`
106
+
107
+
Common trivia bug fix: Use `sepNlnConsideringTriviaContentBeforeForMainNode` instead of `sepNln` in `CodePrinter.fs`.
108
+
109
+
## Testing
110
+
111
+
Tests use NUnit with FsUnit assertions. Core tests are in `src/Fantomas.Core.Tests/`.
112
+
113
+
### Test fixture style
114
+
115
+
Formatter tests should use neutral Fantomas-style sample code, not Agrico Ops domain names, business concepts, or naming conventions. When reducing a bug found in Ops, replace domain-specific names such as `Invoice`, `Docket`, `StockViewer`, `itemGuidO`, or `SuccessfulDomainEventU` with generic names while preserving only the syntactic shape needed to reproduce the formatter behaviour.
116
+
117
+
```bash
118
+
# Run single test file's tests
119
+
dotnet test src/Fantomas.Core.Tests --filter "FullyQualifiedName~CommentTests"
120
+
121
+
# Run specific test
122
+
dotnet test src/Fantomas.Core.Tests --filter "TestName~my_test_name"
This is a fork of [fsprojects/fantomas](https://github.com/fsprojects/fantomas) with custom features (e.g., `LeadingTupleSeparator` for union case fields).
171
+
172
+
### Versioning Scheme
173
+
174
+
Versions follow the pattern `{upstream-version}-agrico-{NNN}`, e.g. `8.0.0-alpha-003-agrico-001`. This embeds the upstream version we're based on.
175
+
176
+
**Reset `NNN` to `001` each time we rebase onto a new upstream version.** Within a given upstream, `NNN` counts our own iterations.
177
+
178
+
### Package ID
179
+
180
+
This fork publishes under the upstream package id `fantomas`. The CLI command name is also `fantomas` (via `<ToolCommandName>fantomas</ToolCommandName>`).
181
+
182
+
A previous attempt (`agrico-003`) renamed the package id to `fantomas.agrico` to dodge SemVer collision with upstream Fantomas on nuget.org. That broke any tooling that looks Fantomas up by package id — most notably JetBrains Rider's *Settings → Languages & Frameworks → F# → Fantomas → Location → Local dotnet tool* detection, which silently fell back to its bundled Fantomas when the manifest entry was keyed on `fantomas.agrico`. Result: on-save formatting in Rider drifted from `dotnet fantomas` CLI output.
183
+
184
+
`agrico-004` reverted to package id `fantomas`. The collision with upstream Fantomas is now handled on the **consumer side** via NuGet `packageSourceMapping`: the `fantomas` package id is locked to the AgricoZA GitHub Packages feed (`<package pattern="fantomas" />` mapped to `github-agrico`), so `dotnet tool restore`/`update` never sees upstream's `fantomas` builds on nuget.org. See AgricoZA/Ops#2320 for the consumer-side change.
185
+
186
+
### CHANGELOG Constraints
187
+
188
+
Versions are extracted from `CHANGELOG.md` by `Ionide.KeepAChangelog.Tasks`. Subsection headings must be standard Keep a Changelog types (`Added`, `Changed`, `Fixed`, `Removed`, etc.) — custom headings like `### Upstream` will cause build failures.
189
+
190
+
### Publishing to GitHub Packages
191
+
192
+
Packages are published to the **AgricoZA GitHub Packages NuGet feed** (`https://nuget.pkg.github.com/AgricoZA/index.json`), which is configured as a source in the Ops repo's `NuGet.config`. Published versions are visible at https://github.com/orgs/AgricoZA/packages/nuget/package/fantomas.
193
+
194
+
**Steps to publish a new version:**
195
+
196
+
1.**Update version in CHANGELOG.md**:
197
+
```markdown
198
+
## [8.0.0-alpha-012-agrico-NNN] - YYYY-MM-DD
199
+
200
+
### Added
201
+
- Description of new feature
202
+
```
203
+
204
+
2.**Build and test:**
205
+
```bash
206
+
dotnet fsi build.fsx
207
+
```
208
+
Packages are output to `artifacts/package/release/`
The Ops repo's `NuGet.config` files include a `<packageSourceMapping>` block that locks the `fantomas` package id to the AgricoZA feed, so this resolves to the fork even though both feeds carry packages named `fantomas`.
224
+
225
+
5.**Verify and commit** the updated `.config/dotnet-tools.json` in the Ops repo.
226
+
227
+
### Syncing Upstream Changes
228
+
229
+
The `custom` branch is rebased onto `upstream/main` to maintain a clean linear history. Our custom commits sit on top of upstream.
230
+
231
+
```bash
232
+
git fetch upstream
233
+
git rebase --onto upstream/main <old-upstream-head> custom
234
+
```
235
+
236
+
### Minimising Upstream Merge Conflicts
237
+
238
+
When adding a fork-specific feature, arrange the code so that rebasing onto `upstream/main` produces at most a few trivial conflicts. The rules below apply to every Agrico feature (`LeadingTupleSeparator`, `RecordFieldAlignment`, anything we add next):
239
+
240
+
**Tests**
241
+
- New tests go in their own file under `src/Fantomas.Core.Tests/Agrico/` with an `Agrico`-prefixed filename. Upstream never edits these files.
242
+
- Register them at the bottom of the `Fantomas.Core.Tests.fsproj``<ItemGroup>` under the comment `<!-- Agrico fork: keep custom tests isolated so upstream merges never conflict. -->`. Conflicts on this line are trivial to resolve.
243
+
- Never extend an upstream-owned test file (`TupleTests.fs`, `LetBindingTests.fs`, etc.) with Agrico test cases — move them out.
244
+
245
+
**Config fields in `FormatConfig.fs`**
246
+
- Append new fields at the end of the `FormatConfig` record and at the end of `FormatConfig.Default`. Both are touchpoints upstream also grows over time; end-of-record placement keeps the textual diff isolated and the merge trivial.
247
+
248
+
**Behaviour changes in `CodePrinter.fs`**
249
+
- Large helpers (new `gen*` functions, policy predicates, grouping logic) live at the **tail of the file**, after `genField`, clustered together under a section-header comment block that names the feature. Upstream rarely modifies the tail. `module internal rec Fantomas.Core.CodePrinter` means these helpers can be called from anywhere earlier in the file without let-rec acrobatics.
250
+
- The **inline edit** at the upstream call site must collapse to a **single function call** — not a multi-line conditional. Example for `RecordFieldAlignment` in the `TypeDefn.Record` branch:
Replaces one line of upstream code with one line of our code. If upstream refactors the surrounding function, the merge conflict is a single-line substitution.
255
+
- Add a leading comment at the inline edit (`// Agrico: see <helper-name>.`) so a merger knows exactly what to preserve.
256
+
- The helper should internally fall back to the upstream default when the feature is off, so inserting the call is behaviour-neutral without the config flag.
257
+
258
+
**Conflict triage when rebasing**
259
+
- The predictable hot spots are: `FormatConfig.fs` (end of record + end of Default), `CodePrinter.fs` (the 1–3 inline call sites per feature), and `Fantomas.Core.Tests.fsproj` (the bottom `<Compile>` block). All other Agrico code lives in files upstream doesn't touch.
260
+
- If upstream refactored around a call site, preserve **the call** (`+> indentSepNlnUnindent (genMaybeAligned...)`), not the surrounding boilerplate — let upstream's boilerplate win, keep our one-line hook.
261
+
262
+
### Custom Features in This Fork
263
+
264
+
-**LeadingTupleSeparator**: Also applies to discriminated union case fields (upstream only supports expressions, types, and patterns)
265
+
-**RecordFieldAlignment**: gofmt-style alignment for Stroustrup records, with blank-line-delimited groups. Applies to two cases, both gated by the same flag:
266
+
-**Type declarations**: aligns `:` across record fields. Long function-type field values wrap at each top-level `->` under the first argument's column; tuple-separator placement reuses the `LeadingTupleSeparator` flag.
267
+
-**Construction expressions**: aligns `=` across record assignments. Stroustrup-style value expressions (nested records, lists) keep their normal layout; other long values (e.g. function applications) stay on the `=` line and wrap from there, mirroring the type-declaration wrap-from-colon behaviour.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+13Lines changed: 13 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,18 @@
1
1
# Changelog
2
2
3
+
## [8.0.0-alpha-012-agrico-008] - 2026-05-01
4
+
5
+
### Changed
6
+
7
+
- Rebuilt the #2416 formatter fix from committed source so package metadata points at the implementation commit.
8
+
9
+
## [8.0.0-alpha-012-agrico-007] - 2026-05-01
10
+
11
+
### Fixed
12
+
13
+
- Agrico match-arrow alignment now falls back to a valid multiline arm when aligning the arrow would make an inline arm body wrap invalidly.
14
+
- Agrico union-case alignment now avoids the inline-first-field layout for anonymous record payloads, preserving valid anonymous record syntax. See AgricoZA/Ops#2416.
0 commit comments