diff --git a/README.md b/README.md index 5141b02..25c604f 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ structalign [flags] [packages] -verbose in -inspect mode, show padding on its own `_` line -tags preserve struct field tags in output (default: strip them) -summary in diff mode, print a one-line summary after the diffs + -sort in diff mode, present structs largest-first (by bytes saved) -type string only consider named structs matching these comma-separated glob patterns (e.g. "*Request,Config"); empty means all @@ -438,3 +439,7 @@ conventions, and the release process. ## License [MIT](LICENSE) © Tiago Peczenyj + +## Foo + +bar diff --git a/internal/app/app.go b/internal/app/app.go index af16598..4e69798 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -71,6 +71,7 @@ type options struct { generated bool skipCachePadded bool summary bool + sort bool } // savings is the absolute bytes a finding saves, or 0 when sizes are unknown or @@ -106,6 +107,7 @@ func (a *App) Run(args []string) int { fs.BoolVar(&opt.generated, "generated", false, "also analyze generated files (// Code generated ... DO NOT EDIT.)") fs.BoolVar(&opt.skipCachePadded, "skip-cache-padded", false, "skip structs containing a golang.org/x/sys/cpu.CacheLinePad field") fs.BoolVar(&opt.summary, "summary", false, "in diff mode, print a one-line summary after the diffs") + fs.BoolVar(&opt.sort, "sort", false, "present results largest-first (diff: by bytes saved)") fs.Usage = func() { fmt.Fprintf(a.Stderr, "structalign: print field-aligned struct reorderings (no file changes)\n\n") fmt.Fprintf(a.Stderr, "usage: structalign [flags] [packages]\n\n") @@ -195,6 +197,12 @@ func (a *App) Run(args []string) int { } } + if opt.sort && !opt.inspect { + sort.SliceStable(allFindings, func(i, j int) bool { + return savings(allFindings[i]) > savings(allFindings[j]) + }) + } + var total int if opt.inspect { total = printer.RenderLayouts(allLayouts, opt.verbose, opt.tags) diff --git a/internal/app/sort_test.go b/internal/app/sort_test.go new file mode 100644 index 0000000..d696b06 --- /dev/null +++ b/internal/app/sort_test.go @@ -0,0 +1,62 @@ +package app_test + +import ( + "bytes" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/peczenyj/structalign/internal/align" + "github.com/peczenyj/structalign/internal/app" + "github.com/peczenyj/structalign/internal/layout" + "github.com/peczenyj/structalign/internal/mocks" + "github.com/peczenyj/structalign/internal/testutil" + "github.com/peczenyj/structalign/pkg/common" +) + +// Small saves 8 bytes (24->16); Big saves 16 (40->24) — more interleaved fields. +const sortSrc = `package sample + +type Small struct { + A bool + B int64 + C bool +} + +type Big struct { + A bool + B int64 + C bool + D int64 + E bool +} +` + +func sortApp(t *testing.T, out, errb *bytes.Buffer) *app.App { + t.Helper() + tgt := testutil.Target(t, sortSrc) + ml := mocks.NewLoader(t) + ml.EXPECT().Load(mock.Anything).Return([]common.Target{tgt}, nil) + return &app.App{Loader: ml, Aligner: align.New(), Inspector: layout.New(), Stdout: out, Stderr: errb} +} + +func TestRunSortDiffOrdersBySavings(t *testing.T) { + var out, errb bytes.Buffer + a := sortApp(t, &out, &errb) + code := a.Run([]string{"-sort", "pkg"}) + assert.Equal(t, 1, code) + s := out.String() + assert.Less(t, strings.Index(s, "Big"), strings.Index(s, "Small"), + "with -sort, the bigger-saving struct must render first") +} + +func TestRunDiffDefaultOrderUnchanged(t *testing.T) { + var out, errb bytes.Buffer + a := sortApp(t, &out, &errb) + _ = a.Run([]string{"pkg"}) + s := out.String() + assert.Less(t, strings.Index(s, "Small"), strings.Index(s, "Big"), + "without -sort, source order (Small before Big) is preserved") +}