Skip to content

Commit 9335397

Browse files
committed
Merge branch 'release/v0.2.0'
2 parents a4865dc + ba9b081 commit 9335397

16 files changed

Lines changed: 588 additions & 159 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ jobs:
3838
- name: Build
3939
run: go build ./cmd/structalign
4040

41+
- name: Test
42+
run: go test ./...
43+
4144
- name: Smoke test
4245
run: |
4346
go run ./cmd/structalign -inspect ./_example

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.2.0] - 2026-05-24
9+
10+
### Added
11+
12+
- Support Go package patterns via go/packages (closes #5)
13+
14+
### Changed
15+
16+
- Merge tag 'v0.1.0' into devel
17+
- Merge pull request #6 from peczenyj/feature/5-recursive-package-pattern
18+
819
## [0.1.0] - 2026-05-24
920

1021
### Added
@@ -20,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2031

2132
- Bind flags to a config struct; fix -width and -verbose help
2233
- Modernize to Go 1.25 idioms
34+
- Merge branch 'release/v0.1.0'
2335

2436
### Fixed
2537

@@ -37,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3749
- Focus the README screenshot on the Record type
3850
- Document file/dir args instead of unsupported ./... (#5)
3951

52+
[0.2.0]: https://github.com///compare/v0.1.0..v0.2.0
4053
[0.1.0]: https://github.com///tree/v0.1.0
4154

4255
<!-- generated by git-cliff -->

CLAUDE.md

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,52 @@ The module path is `github.com/peczenyj/structalign`, so the install target is
2020
```
2121
go build -o structalign ./cmd/structalign # produces ./structalign
2222
go 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
2426
go run ./cmd/structalign ./_example # exercise diff mode
2527
go 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

3547
The 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
3749
upstream `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
5371
importing `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

Makefile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ fmt-check: ## Fail if any file is not gofmt'd
2727
echo "Not gofmt'd:"; echo "$$unformatted"; exit 1; \
2828
fi
2929

30+
.PHONY: test
31+
test: ## Run the unit and golden tests
32+
go test ./...
33+
34+
.PHONY: test-update
35+
test-update: ## Regenerate the .golden fixtures under testdata
36+
go test ./... -update
37+
3038
.PHONY: smoke
3139
smoke: ## Exercise both modes against the bundled sample
3240
go run $(PKG) -inspect $(SAMPLE)
@@ -35,7 +43,7 @@ smoke: ## Exercise both modes against the bundled sample
3543
fi
3644

3745
.PHONY: check
38-
check: fmt-check vet build smoke ## Run everything CI runs
46+
check: fmt-check vet build test smoke ## Run everything CI runs
3947

4048
.PHONY: screenshot
4149
screenshot: build ## Regenerate docs/diff.png from colored output (needs python3 + pillow)

README.md

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ Or grab a prebuilt binary for your OS/arch from the
3131
[Releases](https://github.com/peczenyj/structalign/releases) page. Check the
3232
installed version with `structalign -version`.
3333

34-
Then point it at a Go file or package directory (pass several to scan more than
35-
one at once):
34+
Then point it at a file, a package, or any Go package pattern:
3635

3736
```sh
38-
structalign ./path/to/pkg
37+
structalign ./... # every package in the module (skips _* and testdata dirs)
3938
```
4039

40+
It accepts whatever the `go` tool does — `./...`, import paths, directories, and
41+
single `.go` files — and you can pass several at once.
42+
4143
Pointed at the bundled sample (`./_example`), it reports the reordering and exits
4244
non-zero so it can gate CI:
4345

@@ -74,7 +76,10 @@ inspect a struct's layout. `structalign` fills both gaps.
7476
## Usage
7577

7678
```
77-
structalign [flags] <file.go | package-dir> [...]
79+
structalign [flags] [packages]
80+
81+
packages Go package patterns: ./..., import paths, directories, or
82+
single .go files (defaults the go tool understands)
7883
7984
-diff string diff style: unified | side | none (default "unified")
8085
-width int column width per side for -diff=side (default: auto from terminal)
@@ -154,8 +159,9 @@ type Mixed struct { // size: 24, align: 8, padding: 14
154159
```
155160

156161
The layout comes from the same `go/types` sizing the diff modes use
157-
(`types.Sizes.Offsetsof` / `Sizeof` / `Alignof`), so it respects the `gc`/`amd64`
158-
target. This is similar to `honnef.co/go/tools/cmd/structlayout`, but stays inside
162+
(`types.Sizes.Offsetsof` / `Sizeof` / `Alignof`), driven by the toolchain's
163+
target sizes (your host `GOOS`/`GOARCH` by default). This is similar to
164+
`honnef.co/go/tools/cmd/structlayout`, but stays inside
159165
this one tool and honors the same `-type` filter.
160166

161167
### Filtering by type name
@@ -166,7 +172,7 @@ structs and struct literals are never matched by a non-empty filter. It applies
166172
every mode:
167173

168174
```sh
169-
structalign -type='*Request' ./pkg # only structs ending in Request
175+
structalign -type='*Request' ./... # only structs ending in Request
170176
structalign -type='Record,Config' ./pkg # exact names
171177
structalign -inspect -type='*ID*' ./pkg # inspect just ID-related structs
172178
```
@@ -229,17 +235,20 @@ it as a package, so it stays out of `go build ./...` and friends.
229235
can occasionally induce false sharing between goroutines.
230236
- Reordering can hurt logical grouping/readability; treat the output as advice,
231237
most valuable for hot, frequently-allocated structs.
232-
- Sizes are computed for a target architecture (`amd64` here). 32-bit targets can
233-
differ; change `types.SizesFor("gc", "...")` in `cmd/structalign/main.go` if needed.
238+
- Sizes are computed for the toolchain's target (your host `GOOS`/`GOARCH` by
239+
default). To analyze another target, set them in the environment, e.g.
240+
`GOARCH=386 structalign ./...`.
234241

235242
## Design notes
236243

237244
### Pipeline
238245

239-
1. Type-check the target package with the stdlib (`go/types`), supplying
240-
`types.SizesFor("gc", "amd64")` so the analyzer's size math is correct.
246+
1. Load the target packages with `golang.org/x/tools/go/packages` (mode
247+
including syntax, types, type info, and `TypesSizes`). This resolves `./...`,
248+
import paths, directories, and single files the way the `go` tool does, and
249+
supplies the analyzer's size math from the real build target.
241250
2. Satisfy the analyzer's only dependency — the `inspect` pass — by building an
242-
`inspector.New(files)` and placing it in `Pass.ResultOf`.
251+
`inspector.New(pkg.Syntax)` and placing it in `Pass.ResultOf`.
243252
3. Provide a custom `Pass.Report` that captures each diagnostic's `NewText` (the
244253
proposed struct) and reads the original source slice between `Pos` and `End`.
245254
4. Diff the two with `github.com/aymanbagabas/go-udiff` (a maintained standalone

0 commit comments

Comments
 (0)