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
68 changes: 50 additions & 18 deletions dsl/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,9 @@ func formatCallOnePerLine(call *ast.CallExpr, indent string,

argIndent := indent + "\t"

for i, arg := range call.Args {
for i := range call.Args {
b.WriteString(argIndent)
argSrc := renderNode(arg, ctx.Fset)
argSrc := renderCallArg(call, i, ctx)
// Handle multi-line arguments by re-indenting
lines := strings.Split(argSrc, "\n")
for j, line := range lines {
Expand Down Expand Up @@ -360,8 +360,8 @@ func formatCallLeftPack(call *ast.CallExpr, indent string,
lineWidth := indentWidth
firstOnLine := true

for i, arg := range call.Args {
argSrc := renderNode(arg, ctx.Fset)
for i := range call.Args {
argSrc := renderCallArg(call, i, ctx)
argWidth := visualLen(argSrc, ctx.TabStop)

if firstOnLine {
Expand Down Expand Up @@ -399,6 +399,21 @@ func formatCallLeftPack(call *ast.CallExpr, indent string,
return b.String()
}

// renderCallArg renders one call argument, including the variadic ellipsis
// that the Go AST stores on CallExpr rather than on the final Args expression.
func renderCallArg(call *ast.CallExpr, index int, ctx *Context) string {
if call == nil || ctx == nil || index < 0 || index >= len(call.Args) {
return ""
}

argSrc := renderNode(call.Args[index], ctx.Fset)
if index == len(call.Args)-1 && call.Ellipsis.IsValid() {
argSrc += "..."
}

return argSrc
}

// formatCallAdaptive chooses between one-per-line and left-pack.
func formatCallAdaptive(call *ast.CallExpr, indent string,
ctx *Context) string {
Expand Down Expand Up @@ -1803,7 +1818,7 @@ func (a *BreakCallArgsLayoutAction) Execute(caps Captures, ctx *Context) (
startCol := prefixWidthAt(ctx.Source, start, ctx.TabStop)

funDoc := callFunDoc(call.Fun, ctx)
argDocs, ok := buildCallArgsDocs(call.Args, a.Grouping, ctx)
argDocs, ok := buildCallArgsDocs(call, a.Grouping, ctx)
if !ok {
return nil, false
}
Expand Down Expand Up @@ -1934,8 +1949,8 @@ func formatStructuredLogCallPacked(call *ast.CallExpr, pairStart int,
}

argTexts := make([]string, 0, len(call.Args))
for _, arg := range call.Args {
argText := renderNode(arg, ctx.Fset)
for i := range call.Args {
argText := renderCallArg(call, i, ctx)
if argText == "" || strings.Contains(argText, "\n") {
return "", false
}
Expand Down Expand Up @@ -2056,30 +2071,45 @@ func buildCallArgsGroupDocs(argDocs []layout.Doc, isMake bool) []layout.Doc {
return argsGroupDocs
}

func buildCallArgsDocs(args []ast.Expr, grouping string,
func buildCallArgsDocs(call *ast.CallExpr, grouping string,
ctx *Context) ([]layout.Doc, bool) {

if call == nil {
return nil, false
}
args := call.Args
if len(args) == 0 {
return nil, false
}

var docs []layout.Doc
var ok bool
switch grouping {
case "pairs":
return buildCallArgPairs(args, ctx)
docs, ok = buildCallArgPairs(args, ctx)

default:
// Default: one argument per line (forced break).
docs := make([]layout.Doc, 0, len(args))
docs = make([]layout.Doc, 0, len(args))
for _, arg := range args {
argDoc, ok := callArgDoc(arg, ctx)
if !ok {
return nil, false
}
docs = append(docs, argDoc)
}

return docs, true
ok = true
}
if !ok || len(docs) == 0 {
return nil, false
}
if call.Ellipsis.IsValid() {
docs[len(docs)-1] = layout.C(
docs[len(docs)-1], layout.T("..."),
)
}

return docs, true
}

func buildCallArgPairs(args []ast.Expr, ctx *Context) ([]layout.Doc, bool) {
Expand Down Expand Up @@ -3652,8 +3682,8 @@ func formatCallPackedSimple(call *ast.CallExpr, indent string,
lineWidth := contIndentWidth
b.WriteString(contIndent)

for i, arg := range call.Args {
argSrc := renderNode(arg, ctx.Fset)
for i := range call.Args {
argSrc := renderCallArg(call, i, ctx)
argWidth := visualLen(argSrc, ctx.TabStop)

if i > 0 {
Expand Down Expand Up @@ -3706,8 +3736,8 @@ func formatCallLeftFlowSimple(call *ast.CallExpr, indent string,
// Start on same line as opening paren
lineWidth := visualLen(indent, ctx.TabStop) + len(funcSrc) + 1

for i, arg := range call.Args {
argSrc := renderNode(arg, ctx.Fset)
for i := range call.Args {
argSrc := renderCallArg(call, i, ctx)

if nextWidth, handled := formatLeftFlowStringArg(
&b, argSrc, i == 0, lineWidth, contIndent,
Expand Down Expand Up @@ -6172,8 +6202,10 @@ func formatMethodChain(calls []*ast.CallExpr, indent string,

// Build the arguments part
var argParts []string
for _, arg := range call.Args {
argParts = append(argParts, renderNode(arg, ctx.Fset))
for argIndex := range call.Args {
argParts = append(
argParts, renderCallArg(call, argIndex, ctx),
)
}
argsInline := strings.Join(argParts, ", ")

Expand Down
32 changes: 32 additions & 0 deletions formatter/pipeline_layout_call_args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,35 @@ func f() {
_, err := parser.ParseFile(fset, "out.go", out, parser.AllErrors)
require.NoError(t, err)
}

func TestPipelineDSLMultiLineLayoutArgsPreservesVariadicArgument(t *testing.T) {
t.Parallel()

const in = `package p

func f(prefix string, values []any) {
veryLongFunctionNameForTestingPurposes(prefix, values...)
}
`

p := NewPipeline(PipelineConfig{
ColumnLimit: 30,
TabStop: 8,
UseDSLMultiLineCalls: true,
DSLMultiLineStyle: "layout-args",
UseDSLLogCalls: false,
UseDSLExpr: false,
UseDSLComments: false,
UseDSLFuncSigs: false,
UseDSLBlankLines: false,
})

out1 := p.Format([]byte(in))
out2 := p.Format(out1)
out := string(out1)

require.Contains(t, out, "\t\tvalues...,\n")
require.NotContains(t, out, "\t\tvalues,\n")
require.Equal(t, string(out1), string(out2), "not idempotent")
requireASTEquivalent(t, []byte(in), out1)
}
38 changes: 38 additions & 0 deletions formatter/pipeline_structured_logcalls_next_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,44 @@ func f(log Logger, ctx any, batchID string, nextHeight int, blocksRemaining int)
)
}

func TestPipelineNext_StructuredLogCalls_PreservesVariadicArgument(
t *testing.T) {

t.Parallel()

const in = `package p

type Logger interface { WarnS(any, string, error, ...any) }

func f(log Logger, ctx any, wrapped error, logFields []any) {
log.WarnS(
ctx, "outbox dispatch failed after all retry attempts", wrapped,
logFields...,
)
}
`

p := NewPipeline(PipelineConfig{
ColumnLimit: 80,
TabStop: 8,
UseDSLLogCalls: true,
UseDSLMultiLineCalls: false,
UseDSLExpr: false,
UseDSLComments: false,
UseDSLFuncSigs: false,
UseDSLBlankLines: false,
})

out1 := p.Format([]byte(in))
out2 := p.Format(out1)
out := string(out1)

require.Contains(t, out, "\t\tlogFields...,\n")
require.NotContains(t, out, "\t\tlogFields,\n")
require.Equal(t, string(out1), string(out2), "not idempotent")
requireASTEquivalent(t, []byte(in), out1)
}

func TestPipelineNext_StructuredLogCalls_PacksSlogAttrs(t *testing.T) {
t.Parallel()

Expand Down
Loading