diff --git a/README.md b/README.md index 546706a..fbcdcb3 100644 --- a/README.md +++ b/README.md @@ -451,6 +451,26 @@ func Foo(bar, baz string) {} +#### Clothe naked returns + +**Naked returns are rewritten to include explicit return values** + +
Example + +```go +func Foo() (err error) { + return +} +``` + +```go +func Foo() (err error) { + return err +} +``` + +
+ ### Installation `gofumpt` is a replacement for `gofmt`, so you can simply `go install` it as diff --git a/format/format.go b/format/format.go index 7316eb7..08fc33f 100644 --- a/format/format.go +++ b/format/format.go @@ -190,6 +190,11 @@ type fumpter struct { blockLevel int minSplitFactor float64 + + // parentFuncs is a stack of parent function declarations or + // literals, used to determine return type information when clothing + // naked returns. + parentFuncs []ast.Node } func (f *fumpter) commentsBetween(p1, p2 token.Pos) []*ast.CommentGroup { @@ -336,6 +341,16 @@ var rxCommentDirective = regexp.MustCompile(`^([a-z-]+:[a-z]+|line\b|export\b|ex func (f *fumpter) applyPre(c *astutil.Cursor) { f.splitLongLine(c) + if c.Node() != nil && len(f.parentFuncs) > 0 { + // "pop" the last parent if it's no longer valid. + for i := len(f.parentFuncs) - 1; i >= 0; i-- { + if f.parentFuncs[i].End() < c.Node().Pos() { + f.parentFuncs = f.parentFuncs[:i] + break + } + } + } + switch node := c.Node().(type) { case *ast.File: // Join contiguous lone var/const/import lines. @@ -702,6 +717,54 @@ func (f *fumpter) applyPre(c *astutil.Cursor) { case *ast.AssignStmt: // Only remove lines between the assignment token and the first right-hand side expression f.removeLines(f.Line(node.TokPos), f.Line(node.Rhs[0].Pos())) + + case *ast.FuncDecl, *ast.FuncLit: + // Track the current function declaration or literal, to access + // return type information for clothing of naked returns. + f.parentFuncs = append(f.parentFuncs, node) + // Clothe naked returns + case *ast.ReturnStmt: + if node.Results != nil { + break + } + + // We have either a naked return, or a function with no return values + var results *ast.FieldList + // Find the nearest ancestor that is either a func declaration or func literal + parentLoop: + for i := len(f.parentFuncs) - 1; i >= 0; i-- { + switch p := f.parentFuncs[i].(type) { + case *ast.FuncDecl: + results = p.Type.Results + break parentLoop + case *ast.FuncLit: + results = p.Type.Results + break parentLoop + } + } + if results.NumFields() == 0 { + break + } + + // The function has return values; let's clothe the return + node.Results = make([]ast.Expr, 0, results.NumFields()) + nameLoop: + for _, result := range results.List { + for _, ident := range result.Names { + name := ident.Name + if name == "_" { // we can't handle blank names just yet, abort the transform + node.Results = nil + break nameLoop + } + node.Results = append(node.Results, &ast.Ident{ + NamePos: node.Pos(), // Use the Pos of the return statement, to not interfere with comment placement + Name: name, + }) + } + } + if len(node.Results) > 0 { + c.Replace(node) + } } } diff --git a/testdata/script/clothe-returns.txtar b/testdata/script/clothe-returns.txtar new file mode 100644 index 0000000..299189d --- /dev/null +++ b/testdata/script/clothe-returns.txtar @@ -0,0 +1,74 @@ +exec gofumpt -w foo.go +cmp foo.go foo.go.golden + +exec gofumpt -d foo.go.golden +! stdout . + +-- foo.go -- +package p + +func foo() (err error) { + if true { + return + } + if false { + return func() (err2 error) { + return + } + } + return +} + +func bar() (_ int, err error) { + return +} + +func baz() (a, b, c int) { + return +} + +func qux() (file string, b int, err error) { + if err == nil { + return + } + + // A comment + return +} + +// quux does quuxy things +func quux() {} +-- foo.go.golden -- +package p + +func foo() (err error) { + if true { + return err + } + if false { + return func() (err2 error) { + return err2 + } + } + return err +} + +func bar() (_ int, err error) { + return +} + +func baz() (a, b, c int) { + return a, b, c +} + +func qux() (file string, b int, err error) { + if err == nil { + return file, b, err + } + + // A comment + return file, b, err +} + +// quux does quuxy things +func quux() {}