Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -438,3 +439,7 @@ conventions, and the release process.
## License

[MIT](LICENSE) © Tiago Peczenyj

## Foo

bar
8 changes: 8 additions & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down
62 changes: 62 additions & 0 deletions internal/app/sort_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
Loading