Skip to content

Commit 13637e2

Browse files
Cuong Manh Lecuonglm
authored andcommitted
Add "-apply" flags for applying suggested fix
By using github.com/dave/dst to decorate original ast.File Fixes #37
1 parent 5d95bc1 commit 13637e2

7 files changed

Lines changed: 245 additions & 2 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ struct {
124124
}
125125
```
126126

127+
**Note**
128+
129+
For applying suggested fix, use `-apply` flag, instead of `-fix`.
130+
127131
## Development
128132

129133
Go 1.15+

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@ module github.com/orijtech/structslop
22

33
go 1.15
44

5-
require golang.org/x/tools v0.0.0-20200917221617-d56e4e40bc9d
5+
require (
6+
github.com/dave/dst v0.26.2
7+
github.com/stretchr/testify v1.6.1 // indirect
8+
golang.org/x/tools v0.0.0-20200917221617-d56e4e40bc9d
9+
)

go.sum

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,59 @@
1+
github.com/dave/dst v0.26.2 h1:lnxLAKI3tx7MgLNVDirFCsDTlTG9nKTk7GcptKcWSwY=
2+
github.com/dave/dst v0.26.2/go.mod h1:UMDJuIRPfyUCC78eFuB+SV/WI8oDeyFDvM/JR6NI3IU=
3+
github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ=
4+
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
5+
github.com/dave/kerr v0.0.0-20170318121727-bc25dd6abe8e/go.mod h1:qZqlPyPvfsDJt+3wHJ1EvSXDuVjFTK0j2p/ca+gtsb8=
6+
github.com/dave/rebecca v0.9.1/go.mod h1:N6XYdMD/OKw3lkF3ywh8Z6wPGuwNFDNtWYEMFWEmXBA=
7+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
8+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9+
github.com/google/pprof v0.0.0-20181127221834-b4f47329b966/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
10+
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
11+
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
12+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
13+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
14+
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
15+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
16+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
17+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
18+
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
19+
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
20+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
21+
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
22+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
23+
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
124
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
25+
golang.org/x/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8=
226
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
327
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
428
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
29+
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
530
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
631
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
732
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
833
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
34+
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
935
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
1036
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
37+
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
1138
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
39+
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1240
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
1341
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1442
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1543
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1644
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
45+
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
1746
golang.org/x/tools v0.0.0-20200917221617-d56e4e40bc9d h1:y39d97JVttj+rkTXITl1nf9Vsk+VoRuNzIDLFldUSB4=
1847
golang.org/x/tools v0.0.0-20200917221617-d56e4e40bc9d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
1948
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
2049
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
50+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
2151
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
2252
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
53+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
54+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
55+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
56+
gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek=
57+
gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
58+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
59+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

structslop.go

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ import (
2323
"go/parser"
2424
"go/token"
2525
"go/types"
26+
"io/ioutil"
27+
"os"
2628
"sort"
2729
"strings"
2830

31+
"github.com/dave/dst"
32+
"github.com/dave/dst/decorator"
2933
"golang.org/x/tools/go/analysis"
3034
"golang.org/x/tools/go/analysis/passes/inspect"
3135
"golang.org/x/tools/go/ast/inspector"
@@ -34,11 +38,13 @@ import (
3438
var (
3539
includeTestFiles bool
3640
verbose bool
41+
apply bool
3742
)
3843

3944
func init() {
4045
Analyzer.Flags.BoolVar(&includeTestFiles, "include-test-files", includeTestFiles, "also check test files")
4146
Analyzer.Flags.BoolVar(&verbose, "verbose", verbose, "print all information, even when struct is not sloppy")
47+
Analyzer.Flags.BoolVar(&apply, "apply", apply, "apply suggested fixes (using -fix won't work)")
4248
}
4349

4450
const Doc = `check for structs that can be rearrange fields to provide for maximum space/allocation efficiency`
@@ -60,15 +66,26 @@ func run(pass *analysis.Pass) (interface{}, error) {
6066
maxAlign: pass.TypesSizes.Alignof(types.Typ[types.UnsafePointer]),
6167
}
6268

69+
dec := decorator.NewDecorator(pass.Fset)
6370
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
64-
6571
nodeFilter := []ast.Node{
72+
(*ast.File)(nil),
6673
(*ast.StructType)(nil),
6774
}
75+
76+
fileDiags := make(map[string][]byte)
77+
var af *ast.File
78+
var df *dst.File
79+
6880
inspect.Preorder(nodeFilter, func(n ast.Node) {
6981
if strings.HasSuffix(pass.Fset.File(n.Pos()).Name(), "_test.go") && !includeTestFiles {
7082
return
7183
}
84+
if f, ok := n.(*ast.File); ok {
85+
af = f
86+
df, _ = dec.DecorateFile(af)
87+
return
88+
}
7289
atyp := n.(*ast.StructType)
7390
styp, ok := pass.TypesInfo.Types[atyp].Type.(*types.Struct)
7491
// Type information may be incomplete.
@@ -119,13 +136,61 @@ func run(pass *analysis.Pass) (interface{}, error) {
119136
}
120137
}
121138

139+
dtyp := dec.Dst.Nodes[atyp].(*dst.StructType)
140+
fields := make([]*dst.Field, 0, len(r.optIdx))
141+
dummy := &dst.Field{}
142+
for _, f := range dtyp.Fields.List {
143+
fields = append(fields, f)
144+
if len(f.Names) == 0 {
145+
continue
146+
}
147+
for range f.Names[1:] {
148+
fields = append(fields, dummy)
149+
}
150+
}
151+
optFields := make([]*dst.Field, 0, len(r.optIdx))
152+
for _, i := range r.optIdx {
153+
f := fields[i]
154+
if f == dummy {
155+
continue
156+
}
157+
optFields = append(optFields, f)
158+
}
159+
dtyp.Fields.List = optFields
160+
161+
var suggested bytes.Buffer
162+
if err := decorator.Fprint(&suggested, df); err != nil {
163+
return
164+
}
122165
pass.Report(analysis.Diagnostic{
123166
Pos: n.Pos(),
124167
End: n.End(),
125168
Message: msg,
126169
SuggestedFixes: nil,
127170
})
171+
f := pass.Fset.File(n.Pos())
172+
fileDiags[f.Name()] = suggested.Bytes()
128173
})
174+
175+
if !apply {
176+
return nil, nil
177+
}
178+
for fn, content := range fileDiags {
179+
fi, err := os.Open(fn)
180+
if err != nil {
181+
_, _ = fmt.Fprintf(os.Stderr, "failed to open file: %v", err)
182+
}
183+
st, err := fi.Stat()
184+
if err != nil {
185+
_, _ = fmt.Fprintf(os.Stderr, "failed to get file stat: %v", err)
186+
}
187+
if err := fi.Close(); err != nil {
188+
_, _ = fmt.Fprintf(os.Stderr, "failed to close file: %v", err)
189+
}
190+
if err := ioutil.WriteFile(fn, content, st.Mode()); err != nil {
191+
_, _ = fmt.Fprintf(os.Stderr, "failed to write suggested fix to file: %v", err)
192+
}
193+
}
129194
return nil, nil
130195
}
131196

structslop_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
package structslop_test
1616

1717
import (
18+
"bytes"
19+
"io/ioutil"
20+
"os"
21+
"path/filepath"
22+
"strings"
1823
"testing"
1924

2025
"golang.org/x/tools/go/analysis/analysistest"
@@ -27,14 +32,45 @@ func Test(t *testing.T) {
2732
analysistest.Run(t, testdata, structslop.Analyzer, "struct")
2833
}
2934

35+
func TestApply(t *testing.T) {
36+
dir := strings.Join([]string{".", "testdata", "src"}, string(os.PathSeparator))
37+
tmpdir, err := ioutil.TempDir(dir, "structslop-test-apply-")
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
defer os.RemoveAll(tmpdir)
42+
fn := filepath.Join(tmpdir, "p.go")
43+
src, _ := ioutil.ReadFile(filepath.Join(".", "testdata", "src", "struct", "p.go"))
44+
if err := ioutil.WriteFile(fn, src, 0644); err != nil {
45+
t.Fatal(err)
46+
}
47+
testdata := analysistest.TestData()
48+
_ = structslop.Analyzer.Flags.Set("apply", "true")
49+
defer func() {
50+
_ = structslop.Analyzer.Flags.Set("apply", "false")
51+
}()
52+
analysistest.Run(t, testdata, structslop.Analyzer, filepath.Base(tmpdir))
53+
got, _ := ioutil.ReadFile(fn)
54+
expected, _ := ioutil.ReadFile(filepath.Join(".", "testdata", "src", "struct", "p.go.golden"))
55+
if !bytes.Equal(expected, got) {
56+
t.Errorf("unexpected suggested fix, want:\n%s\ngot:\n%s\n", string(expected), string(got))
57+
}
58+
}
59+
3060
func TestIncludeTestFiles(t *testing.T) {
3161
testdata := analysistest.TestData()
3262
_ = structslop.Analyzer.Flags.Set("include-test-files", "true")
63+
defer func() {
64+
_ = structslop.Analyzer.Flags.Set("include-test-files", "false")
65+
}()
3366
analysistest.Run(t, testdata, structslop.Analyzer, "include-test-files")
3467
}
3568

3669
func TestVerboseMode(t *testing.T) {
3770
testdata := analysistest.TestData()
3871
_ = structslop.Analyzer.Flags.Set("verbose", "true")
72+
defer func() {
73+
_ = structslop.Analyzer.Flags.Set("verbose", "false")
74+
}()
3975
analysistest.Run(t, testdata, structslop.Analyzer, "verbose")
4076
}

testdata/src/struct/p.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,11 @@ type s9 struct { // want `struct has size 40 \(size class 48\), could be 24 \(si
7979
a3 [3]bool
8080
_ [0]func()
8181
}
82+
83+
// Preserve comments.
84+
type s10 struct { // want `struct has size 40 \(size class 48\), could be 24 \(size class 32\), you'll save 33.33% if you rearrange it to:\nstruct {\n\t_ \[0\]func\(\)\n\ti1 int\n\ti2 int\n\ta3 \[3\]bool\n\tb bool\n}`
85+
b bool // b is bool
86+
i1, i2 int // i1, i2 are int
87+
a3 [3]bool // a3 is array of bool
88+
_ [0]func()
89+
}

testdata/src/struct/p.go.golden

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2020 Orijtech, Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package p
16+
17+
import "net/http/httptest"
18+
19+
type s struct{}
20+
21+
type s1 struct {
22+
i int
23+
}
24+
25+
type s2 struct {
26+
i int
27+
j int
28+
}
29+
30+
type s3 struct { // want `struct has size 24 \(size class 32\), could be 16 \(size class 16\), you'll save 50.00% if you rearrange it to:\nstruct {\n\ty uint64\n\tx uint32\n\tz uint32\n}`
31+
y uint64
32+
x uint32
33+
z uint32
34+
}
35+
36+
type s4 struct { // want `struct has size 40 \(size class 48\), could be 24 \(size class 32\), you'll save 33.33% if you rearrange it to:\nstruct {\n\t_ \[0\]func\(\)\n\ti1 int\n\ti2 int\n\ta3 \[3\]bool\n\tb bool\n}`
37+
_ [0]func()
38+
i1 int
39+
i2 int
40+
a3 [3]bool
41+
b bool
42+
}
43+
44+
// should be good, the struct has size 32, can be rearrange to have size 24, but runtime allocator
45+
// allocate the same size class 32.
46+
type s5 struct {
47+
x uint32
48+
y uint64
49+
z *s
50+
t uint32
51+
}
52+
53+
type s6 struct { // should be good, see #16
54+
bytep *uint8
55+
mask uint8
56+
index uintptr
57+
}
58+
59+
type s7 struct { // want `struct has size 40 \(size class 48\), could be 32 \(size class 32\), you'll save 33.33% if you rearrange it to:\nstruct {\n\ty uint64\n\tt \*httptest.Server\n\tw uint64\n\tx uint32\n\tz uint32\n}`
60+
y uint64
61+
t *httptest.Server
62+
w uint64
63+
x uint32
64+
z uint32
65+
}
66+
67+
type s8 struct { // want `struct has size 40 \(size class 48\), could be 32 \(size class 32\), you'll save 33.33% if you rearrange it to:\nstruct {\n\ty uint64\n\tt \*s\n\tw uint64\n\tx uint32\n\tz uint32\n}`
68+
y uint64
69+
t *s
70+
w uint64
71+
x uint32
72+
z uint32
73+
}
74+
75+
// Struct which combines multiple fields of the same type, see issue #41.
76+
type s9 struct { // want `struct has size 40 \(size class 48\), could be 24 \(size class 32\), you'll save 33.33% if you rearrange it to:\nstruct {\n\t_ \[0\]func\(\)\n\ti1 int\n\ti2 int\n\ta3 \[3\]bool\n\tb bool\n}`
77+
_ [0]func()
78+
i1, i2 int
79+
a3 [3]bool
80+
b bool
81+
}
82+
83+
// Preserve comments.
84+
type s10 struct { // want `struct has size 40 \(size class 48\), could be 24 \(size class 32\), you'll save 33.33% if you rearrange it to:\nstruct {\n\t_ \[0\]func\(\)\n\ti1 int\n\ti2 int\n\ta3 \[3\]bool\n\tb bool\n}`
85+
_ [0]func()
86+
i1, i2 int // i1, i2 are int
87+
a3 [3]bool // a3 is array of bool
88+
b bool // b is bool
89+
}

0 commit comments

Comments
 (0)