Skip to content

Commit b378ad7

Browse files
committed
fix: no ordering for anonymous struct literals
Signed-off-by: Ilya Lesikov <ilya@lesikov.com>
1 parent ec00ec5 commit b378ad7

File tree

2 files changed

+90
-39
lines changed

2 files changed

+90
-39
lines changed

pkg/formatter/structs.go

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,80 @@ func reorderStructLiterals(f *dst.File, structDefs map[string][]string) {
9191
return true
9292
}
9393

94-
typeName := extractTypeName(cl.Type)
95-
if typeName == "" {
96-
return true
94+
// Process this literal and all nested children for reordering
95+
reorderCompositeLitRecursive(cl, nil, structDefs)
96+
97+
// Don't let dst.Inspect descend into children - we handle them
98+
return false
99+
})
100+
}
101+
102+
func reorderCompositeLitRecursive(cl *dst.CompositeLit, inheritedFieldOrder []string, structDefs map[string][]string) {
103+
// Determine field order for THIS literal
104+
fieldOrder := resolveSortedFieldOrder(cl.Type, inheritedFieldOrder, structDefs)
105+
106+
// Reorder if we know the field order
107+
if len(fieldOrder) > 0 {
108+
reorderCompositeLitFields(cl, fieldOrder)
109+
}
110+
111+
// Determine field order to pass to children (from element type)
112+
childFieldOrder := getElementSortedFieldOrder(cl.Type, structDefs)
113+
114+
// Process all child elements
115+
for _, elt := range cl.Elts {
116+
reorderElementRecursive(elt, childFieldOrder, structDefs)
117+
}
118+
}
119+
120+
func reorderElementRecursive(elt dst.Expr, inheritedFieldOrder []string, structDefs map[string][]string) {
121+
switch e := elt.(type) {
122+
case *dst.CompositeLit:
123+
reorderCompositeLitRecursive(e, inheritedFieldOrder, structDefs)
124+
case *dst.KeyValueExpr:
125+
// Value might be a composite literal (map values, struct fields)
126+
if child, ok := e.Value.(*dst.CompositeLit); ok {
127+
reorderCompositeLitRecursive(child, inheritedFieldOrder, structDefs)
97128
}
129+
}
130+
}
98131

99-
fieldOrder, exists := structDefs[typeName]
100-
if !exists {
101-
return true
132+
func resolveSortedFieldOrder(t dst.Expr, inherited []string, structDefs map[string][]string) []string {
133+
if t == nil {
134+
return inherited
135+
}
136+
137+
// Anonymous struct type - get field names from the (now sorted) struct
138+
if st, ok := t.(*dst.StructType); ok {
139+
return getFieldNamesFromStructType(st)
140+
}
141+
142+
// Named type
143+
if typeName := extractTypeName(t); typeName != "" {
144+
if order, exists := structDefs[typeName]; exists {
145+
return order
102146
}
147+
}
103148

104-
reorderCompositeLitFields(cl, fieldOrder)
149+
return nil
150+
}
105151

106-
return true
107-
})
152+
func getElementSortedFieldOrder(t dst.Expr, structDefs map[string][]string) []string {
153+
if t == nil {
154+
return nil
155+
}
156+
157+
// Slice/array: []T or [N]T
158+
if at, ok := t.(*dst.ArrayType); ok {
159+
return resolveSortedFieldOrder(at.Elt, nil, structDefs)
160+
}
161+
162+
// Map: map[K]V - return value type's field order
163+
if mt, ok := t.(*dst.MapType); ok {
164+
return resolveSortedFieldOrder(mt.Value, nil, structDefs)
165+
}
166+
167+
return nil
108168
}
109169

110170
func assembleFieldList(embedded, public, private []*dst.Field) []*dst.Field {
@@ -188,6 +248,14 @@ func reorderCompositeLitFields(cl *dst.CompositeLit, fieldOrder []string) {
188248
return
189249
}
190250

251+
// Capture the original first element's decoration
252+
var originalFirstBefore dst.SpaceType
253+
if len(cl.Elts) > 0 {
254+
if kv, ok := cl.Elts[0].(*dst.KeyValueExpr); ok {
255+
originalFirstBefore = kv.Decs.Before
256+
}
257+
}
258+
191259
var newElts []dst.Expr
192260
for _, fieldName := range fieldOrder {
193261
if kv, exists := keyedElts[fieldName]; exists {
@@ -202,10 +270,11 @@ func reorderCompositeLitFields(cl *dst.CompositeLit, fieldOrder []string) {
202270

203271
newElts = append(newElts, nonKeyed...)
204272

273+
// Preserve original decoration style
205274
for i, elt := range newElts {
206275
if kv, ok := elt.(*dst.KeyValueExpr); ok {
207276
if i == 0 {
208-
kv.Decs.Before = dst.NewLine
277+
kv.Decs.Before = originalFirstBefore
209278
} else {
210279
kv.Decs.Before = dst.None
211280
}

pkg/formatter/testdata/expected.go

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ var (
5757
content string
5858
path string
5959
}{
60-
{path: filepath.Join("a", "b"), content: "content1"},
61-
{path: filepath.Join("c", "d"), content: "content2"},
60+
{content: "content1", path: filepath.Join("a", "b")},
61+
{content: "content2", path: filepath.Join("c", "d")},
6262
}
6363
)
6464

@@ -123,9 +123,7 @@ func NewServer() *Server {
123123

124124
// Test: struct literal fields should be reordered
125125
func NewServerWithOptions(host string, port int) *Server {
126-
return &Server{
127-
Host: host, port: port,
128-
}
126+
return &Server{Host: host, port: port}
129127
}
130128

131129
func (s *Server) AnotherPublic() {
@@ -186,9 +184,7 @@ func NewConfig() Config {
186184

187185
// Test: struct literal reordering
188186
func NewConfigWithDefaults() *Config {
189-
return &Config{
190-
Timeout: 30, Verbose: true, debug: false, name: "default",
191-
}
187+
return &Config{Timeout: 30, Verbose: true, debug: false, name: "default"}
192188
}
193189

194190
type Empty struct{}
@@ -231,9 +227,7 @@ type myPrivateType struct {
231227
}
232228

233229
func newMyPrivateType() *myPrivateType {
234-
return &myPrivateType{
235-
value: 1,
236-
}
230+
return &myPrivateType{value: 1}
237231
}
238232

239233
// Test: positional literals should be converted to keyed
@@ -266,7 +260,7 @@ func createAnonymous() interface{} {
266260
return struct {
267261
A string
268262
B int
269-
}{B: 42, A: "hello"}
263+
}{A: "hello", B: 42}
270264
}
271265

272266
// Test: empty literal - no change
@@ -283,36 +277,24 @@ func createExternal() *os.File {
283277

284278
// Test: already keyed literal - no change
285279
func createKeyed() *PositionalTest {
286-
return &PositionalTest{
287-
Age: 35, City: "Boston", Name: "Alice",
288-
}
280+
return &PositionalTest{Age: 35, City: "Boston", Name: "Alice"}
289281
}
290282

291283
// Test: struct literal field reordering
292284
func createMixed() *Mixed {
293-
return &Mixed{
294-
Address: "addr", Name: "test", age: 25, count: 1,
295-
}
285+
return &Mixed{Address: "addr", Name: "test", age: 25, count: 1}
296286
}
297287

298288
func createPositional() *PositionalTest {
299-
return &PositionalTest{
300-
Age: 30, City: "NYC", Name: "John",
301-
}
289+
return &PositionalTest{Age: 30, City: "NYC", Name: "John"}
302290
}
303291

304292
func createPositionalPartial() *PositionalTest {
305-
return &PositionalTest{
306-
Age: 25, Name: "Jane",
307-
}
293+
return &PositionalTest{Age: 25, Name: "Jane"}
308294
}
309295

310296
func createWithEmbedded() *WithEmbedded {
311-
return &WithEmbedded{
312-
PositionalTest: PositionalTest{
313-
Age: 40, City: "LA", Name: "Bob",
314-
}, Extra: "extra",
315-
}
297+
return &WithEmbedded{PositionalTest: PositionalTest{Age: 40, City: "LA", Name: "Bob"}, Extra: "extra"}
316298
}
317299

318300
// Test: blank line before comments

0 commit comments

Comments
 (0)