Skip to content

Commit 8893c20

Browse files
committed
fix: properly expand value to key-value fields in slices or maps of structs
Signed-off-by: Ilya Lesikov <ilya@lesikov.com>
1 parent 23bc322 commit 8893c20

File tree

3 files changed

+90
-25
lines changed

3 files changed

+90
-25
lines changed

pkg/formatter/structs.go

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -278,31 +278,78 @@ func convertPositionalToKeyed(f *dst.File, structDefs map[string][]string) {
278278
return true
279279
}
280280

281-
if !isPositionalLiteral(cl) {
282-
return true
283-
}
281+
// Process this literal and all nested children
282+
processCompositeLit(cl, nil, structDefs)
284283

285-
// Handle anonymous struct type
286-
if st, ok := cl.Type.(*dst.StructType); ok {
287-
fieldNames := getFieldNamesFromStructType(st)
288-
convertToKeyedLiteral(cl, fieldNames)
289-
return true
290-
}
284+
// Don't let dst.Inspect descend into children - we handle them
285+
return false
286+
})
287+
}
291288

292-
// Handle named struct type
293-
typeName := extractTypeName(cl.Type)
294-
if typeName == "" {
295-
return true
289+
func processCompositeLit(cl *dst.CompositeLit, inheritedFieldNames []string, structDefs map[string][]string) {
290+
// Determine field names for THIS literal
291+
fieldNames := resolveFieldNames(cl.Type, inheritedFieldNames, structDefs)
292+
293+
// Convert if positional and we know the field names
294+
if len(fieldNames) > 0 && isPositionalLiteral(cl) {
295+
convertToKeyedLiteral(cl, fieldNames)
296+
}
297+
298+
// Determine field names to pass to children (from element type)
299+
childFieldNames := getElementFieldNames(cl.Type, structDefs)
300+
301+
// Process all child elements
302+
for _, elt := range cl.Elts {
303+
processElement(elt, childFieldNames, structDefs)
304+
}
305+
}
306+
307+
func processElement(elt dst.Expr, inheritedFieldNames []string, structDefs map[string][]string) {
308+
switch e := elt.(type) {
309+
case *dst.CompositeLit:
310+
processCompositeLit(e, inheritedFieldNames, structDefs)
311+
case *dst.KeyValueExpr:
312+
// Value might be a composite literal (map values, struct fields)
313+
if child, ok := e.Value.(*dst.CompositeLit); ok {
314+
processCompositeLit(child, inheritedFieldNames, structDefs)
296315
}
316+
}
317+
}
297318

298-
fieldNames, exists := structDefs[typeName]
299-
if !exists {
300-
// Type not in this file - leave untouched
301-
return true
319+
func resolveFieldNames(t dst.Expr, inherited []string, structDefs map[string][]string) []string {
320+
if t == nil {
321+
return inherited
322+
}
323+
324+
// Anonymous struct type
325+
if st, ok := t.(*dst.StructType); ok {
326+
return getFieldNamesFromStructType(st)
327+
}
328+
329+
// Named type
330+
if typeName := extractTypeName(t); typeName != "" {
331+
if names, exists := structDefs[typeName]; exists {
332+
return names
302333
}
334+
}
303335

304-
convertToKeyedLiteral(cl, fieldNames)
336+
return nil
337+
}
305338

306-
return true
307-
})
339+
func getElementFieldNames(t dst.Expr, structDefs map[string][]string) []string {
340+
if t == nil {
341+
return nil
342+
}
343+
344+
// Slice/array: []T or [N]T
345+
if at, ok := t.(*dst.ArrayType); ok {
346+
return resolveFieldNames(at.Elt, nil, structDefs)
347+
}
348+
349+
// Map: map[K]V - return value type's field names
350+
if mt, ok := t.(*dst.MapType); ok {
351+
return resolveFieldNames(mt.Value, nil, structDefs)
352+
}
353+
354+
return nil
308355
}

pkg/formatter/testdata/expected.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/http"
77
"os"
8+
"path/filepath"
89
"strings"
910
)
1011

@@ -47,11 +48,18 @@ var (
4748
DefaultStatus StatusCode = "default"
4849
ErrorStatus StatusCode = "error"
4950

50-
globalA = 5
51-
globalB = 3
52-
globalMiddle = 7
53-
globalZ = 10
54-
singleConst = 1
51+
globalA = 5
52+
globalB = 3
53+
globalMiddle = 7
54+
globalZ = 10
55+
singleConst = 1
56+
sliceOfStructs = []struct {
57+
content string
58+
path string
59+
}{
60+
{path: filepath.Join("a", "b"), content: "content1"},
61+
{path: filepath.Join("c", "d"), content: "content2"},
62+
}
5563
)
5664

5765
// Test: type declared in wrong place

pkg/formatter/testdata/input.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/http"
77
"os"
8+
"path/filepath"
89
"strings"
910
)
1011

@@ -341,6 +342,15 @@ func createEmpty() *PositionalTest {
341342
return &PositionalTest{}
342343
}
343344

345+
// Test: slice of anonymous structs with positional literals
346+
var sliceOfStructs = []struct {
347+
path string
348+
content string
349+
}{
350+
{filepath.Join("a", "b"), "content1"},
351+
{filepath.Join("c", "d"), "content2"},
352+
}
353+
344354
// Test: multi-line func signature should collapse to single line
345355
func multiLineFunc(
346356
a int,

0 commit comments

Comments
 (0)