@@ -20,34 +20,52 @@ The module path is `github.com/peczenyj/structalign`, so the install target is
2020```
2121go build -o structalign ./cmd/structalign # produces ./structalign
2222go vet ./...
23- go run ./cmd/structalign [flags] <file.go | dir> [...]
23+ go test ./... # unit + golden tests
24+ go test ./... -update # regenerate testdata/*.golden
25+ go run ./cmd/structalign [flags] [packages] # packages: ./..., import paths, dirs, files
2426go run ./cmd/structalign ./_example # exercise diff mode
2527go run ./cmd/structalign -inspect ./_example # exercise inspect mode
2628```
2729
28- There are ** no tests** in this repo. Verify changes by running the binary against
29- ` ./_example ` and eyeballing output. Exit code is meaningful: diff modes exit ** 1**
30- when any reordering is found (CI-friendly), ** 0** when none; ` -inspect ` always
31- exits 0.
30+ Tests live in ` cmd/structalign/main_test.go ` (same ` package main ` , so they reach
31+ unexported funcs). Two layers: table-driven unit tests for the pure helpers
32+ (` parsePatterns ` , ` matchAny ` , ` normalizeArgs ` , ` stripStructTags ` , ` relPath ` ,
33+ ` lcsDiff ` , ` renderUnified ` ), and golden-file integration tests that run the real
34+ ` loadPackages ` → ` diffPackage ` /` inspectStructs ` pipeline against ` ./_example ` and
35+ compare to ` testdata/*.golden ` . The golden test ` t.Chdir ` s to the repo root so
36+ ` ./_example ` resolves and paths render relative; regenerate fixtures with
37+ ` -update ` . Golden cases assume a ** 64-bit target** (` unsafe.Sizeof(uintptr) == 8 ` )
38+ and ` t.Skip ` otherwise, since ` types.Sizes ` depends on pointer width. Rendering is
39+ testable because ` diffPackage ` /` inspectStructs ` /` render* ` take an ` io.Writer `
40+ (` main ` passes ` os.Stdout ` ).
41+
42+ Exit code is meaningful: diff modes exit ** 1** when any reordering is found
43+ (CI-friendly), ** 0** when none; ` -inspect ` always exits 0.
3244
3345## Core architecture
3446
3547The key design decision (see the package doc and README "How it works"): this tool
3648** does not reimplement** the field-alignment algorithm. It runs the unmodified
3749upstream ` golang.org/x/tools/go/analysis/passes/fieldalignment.Analyzer ` and
38- intercepts the ` SuggestedFix ` it already produces. The pipeline in ` process() ` :
39-
40- 1 . ` loadFiles ` parses a ` .go ` file or every non-test ` .go ` in a directory as one package.
41- 2 . Type-check with ` go/types ` , supplying ` types.SizesFor("gc", "amd64") ` so the
42- analyzer's size math is correct. The ` types.Config.Error ` callback swallows
43- errors so partially-resolving packages (missing deps) are still analyzed.
44- 3 . Run the analyzer manually via an ` analysis.Pass ` , satisfying its only
45- dependency (the ` inspect ` pass) with ` inspector.New(files) ` in ` Pass.ResultOf ` .
46- 4 . A custom ` Pass.Report ` captures each diagnostic's single ` TextEdit ` : ` NewText `
50+ intercepts the ` SuggestedFix ` it already produces. The pipeline:
51+
52+ 1 . ` main ` resolves the CLI args (` normalizeArgs ` rewrites bare ` .go ` files to
53+ ` file= ` queries) and ` loadPackages ` loads them via
54+ ` golang.org/x/tools/go/packages ` (` ./... ` , import paths, dirs, files). This
55+ gives syntax, types, type info, and ` TypesSizes ` for the real build target.
56+ 2 . Per package, ` diffPackage ` runs the analyzer via an ` analysis.Pass ` , wiring
57+ ` pkg.Syntax ` /` pkg.Types ` /` pkg.TypesInfo ` /` pkg.TypesSizes ` and satisfying its
58+ only dependency (the ` inspect ` pass) with ` inspector.New(pkg.Syntax) ` .
59+ 3 . A custom ` Pass.Report ` captures each diagnostic's single ` TextEdit ` : ` NewText `
4760 is the proposed reordered struct, and ` readSource ` reads the original source
4861 slice between the edit's ` Pos ` /` End ` .
49- 5 . ` lcsDiff ` diffs the two via ` github.com/aymanbagabas/go-udiff ` (` udiff.Lines ` ),
50- and ` render ` emits unified / side-by-side / proposed-only output.
62+ 4 . ` lcsDiff ` diffs the two via ` github.com/aymanbagabas/go-udiff ` (` udiff.Lines ` ),
63+ and ` render ` emits unified / side-by-side / proposed-only output. Filenames
64+ are shown relative to the working dir (` relPath ` ), since go/packages reports
65+ absolute paths.
66+
67+ Package load/type errors are reported to stderr but non-fatal — a
68+ partially-resolved package can still produce findings.
5169
5270** Why go-udiff and not x/tools' own diff:** Go's internal-package rule forbids
5371importing ` golang.org/x/tools/internal/diff ` from a module not rooted under
@@ -62,9 +80,9 @@ to swap it back for the internal package — it won't compile from this module.
6280
6381## Things to keep consistent when editing
6482
65- - ** Architecture target is hardcoded ** to ` ("gc", "amd64") ` in ` cmd/structalign/main.go ` 's ` process ` and
66- reused for inspect. The README documents changing it for 32-bit targets — keep
67- both call sites in sync if you parameterize it .
83+ - ** Type sizes come from ` go/packages ` ** ( ` pkg.TypesSizes ` ), i.e. the toolchain's
84+ real target (host ` GOOS ` / ` GOARCH ` by default; override via env). It is no longer
85+ hardcoded to amd64 .
6886- ** Struct name labeling** depends on ` structNameIndex ` mapping ` StructType.Pos() `
6987 to the declared type name, because the analyzer reports diagnostics at exactly
7088 that position. Anonymous structs have no name and are filtered out by any
0 commit comments