Skip to content

Commit e987101

Browse files
committed
Fix multi-struct rewrite loss by deferring DST rendering to run()
Store dirty *dst.File pointers instead of pre-rendered []byte buffers so that all struct rewrites within the same file accumulate on the shared DST before final rendering. The previous approach called decorator.Fprint inside betteralign() and stored the result in fixOps[fn], silently overwriting any prior rewrite for the same file; only the last modified struct per file was preserved. Also hoist os.Getwd() out of the inspect.Preorder closure so it is called once per analysis pass instead of once per AST node, and propagate its error properly rather than printing to stderr and continuing silently. Move the dNode lookup and hasIgnoreComment check to the top of betteralign() so ignored structs are skipped before any expensive computation. Pre-compute architecture suffix strings in removeOtherArches to avoid repeated string construction inside the inner loop.
1 parent 0968379 commit e987101

File tree

2 files changed

+37
-30
lines changed

2 files changed

+37
-30
lines changed

betteralign.go

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,20 @@ func run(pass *analysis.Pass) (any, error) {
181181

182182
var dFile *dst.File
183183

184-
applyFixesFset := make(map[string][]byte)
184+
dirtyFiles := make(map[string]*dst.File)
185185
testFset := make(map[string]bool)
186186
generatedFset := make(map[string]bool)
187187
structMetaMap := make(map[token.Pos]structMeta)
188188

189+
var wd string
190+
if len(fExcludeDirs) > 0 || len(fExcludeFiles) > 0 {
191+
var err error
192+
wd, err = os.Getwd()
193+
if err != nil {
194+
return nil, fmt.Errorf("%v: %w", ErrPreFilterFiles, err)
195+
}
196+
}
197+
189198
inspect.Preorder(nodeFilter, func(node ast.Node) {
190199
fn := pass.Fset.File(node.Pos()).Name()
191200

@@ -198,11 +207,6 @@ func run(pass *analysis.Pass) (any, error) {
198207
}
199208

200209
if len(fExcludeDirs) > 0 || len(fExcludeFiles) > 0 {
201-
wd, err := os.Getwd()
202-
if err != nil {
203-
fmt.Fprintf(os.Stderr, "%v %s: %v", ErrPreFilterFiles, fn, err)
204-
return
205-
}
206210
relfn, err := filepath.Rel(wd, fn)
207211
if err != nil {
208212
fmt.Fprintf(os.Stderr, "%v %s: %v", ErrPreFilterFiles, fn, err)
@@ -288,17 +292,20 @@ func run(pass *analysis.Pass) (any, error) {
288292
}
289293

290294
if tv, ok := pass.TypesInfo.Types[s]; ok {
291-
betteralign(pass, s, tv.Type.(*types.Struct), dec, dFile, applyFixesFset, fn)
295+
betteralign(pass, s, tv.Type.(*types.Struct), dec, dFile, dirtyFiles, fn)
292296
}
293297
})
294298

295-
if !apply {
296-
return nil, nil
297-
}
298-
299-
for fn, buf := range applyFixesFset {
300-
if err := applyToFile(fn, buf); err != nil {
301-
fmt.Fprintf(os.Stderr, "error applying fixes to %v: %v\n", fn, err)
299+
if apply {
300+
for fn, df := range dirtyFiles {
301+
var buf bytes.Buffer
302+
if err := decorator.Fprint(&buf, df); err != nil {
303+
fmt.Fprintf(os.Stderr, "failed to print %s: %v\n", fn, err)
304+
continue
305+
}
306+
if err := applyToFile(fn, buf.Bytes()); err != nil {
307+
fmt.Fprintf(os.Stderr, "error applying fixes to %v: %v\n", fn, err)
308+
}
302309
}
303310
}
304311

@@ -308,8 +315,15 @@ func run(pass *analysis.Pass) (any, error) {
308315
var unsafePointerTyp = types.Unsafe.Scope().Lookup("Pointer").(*types.TypeName).Type()
309316

310317
func betteralign(pass *analysis.Pass, aNode *ast.StructType, typ *types.Struct, dec *decorator.Decorator,
311-
dFile *dst.File, fixOps map[string][]byte, fn string,
318+
dFile *dst.File, dirtyFiles map[string]*dst.File, fn string,
312319
) {
320+
dNode := dec.Dst.Nodes[aNode].(*dst.StructType)
321+
322+
// Skip if explicitly ignored with magic comment substring.
323+
if hasIgnoreComment(dNode.Fields) {
324+
return
325+
}
326+
313327
wordSize := pass.TypesSizes.Sizeof(unsafePointerTyp)
314328
maxAlign := pass.TypesSizes.Alignof(unsafePointerTyp)
315329

@@ -327,13 +341,6 @@ func betteralign(pass *analysis.Pass, aNode *ast.StructType, typ *types.Struct,
327341
return
328342
}
329343

330-
dNode := dec.Dst.Nodes[aNode].(*dst.StructType)
331-
332-
// Skip if explicitly ignored with magic comment substring.
333-
if hasIgnoreComment(dNode.Fields) {
334-
return
335-
}
336-
337344
// Flatten the ast node since it could have multiple field names per list item while
338345
// *types.Struct only have one item per field.
339346
// TODO: Preserve multi-named fields instead of flattening.
@@ -362,19 +369,14 @@ func betteralign(pass *analysis.Pass, aNode *ast.StructType, typ *types.Struct,
362369

363370
dNode.Fields.List = reordered
364371

365-
var buf bytes.Buffer
366-
if err := decorator.Fprint(&buf, dFile); err != nil {
367-
return
368-
}
369-
370372
pass.Report(analysis.Diagnostic{
371373
Pos: aNode.Pos(),
372374
End: aNode.Pos() + token.Pos(len("struct")),
373375
Message: message,
374376
SuggestedFixes: nil,
375377
})
376378

377-
fixOps[fn] = buf.Bytes()
379+
dirtyFiles[fn] = dFile
378380
}
379381

380382
func optimalOrder(str *types.Struct, sizes *gcSizes) (*types.Struct, []int) {

betteralign_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,17 @@ func removeOtherArches(paths []string) []string {
2929

3030
delete(arches, runtime.GOARCH)
3131

32+
suffixes := make([]string, 0, len(arches))
33+
for arch := range arches {
34+
suffixes = append(suffixes, "_"+arch+".go")
35+
}
36+
3237
var blacklist bool
3338
for _, path := range paths {
3439
blacklist = false
3540

36-
for arch := range arches {
37-
if strings.Contains(path, strings.Join([]string{"_", arch, ".go"}, "")) {
41+
for _, suffix := range suffixes {
42+
if strings.Contains(path, suffix) {
3843
blacklist = true
3944
break
4045
}

0 commit comments

Comments
 (0)