Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clothe returns #287

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,26 @@ func Foo(bar, baz string) {}

</details>

#### Clothe naked returns

**Naked returns are rewritten to include explicit return values**

<details><summary><i>Example</i></summary>

```go
func Foo() (err error) {
return
}
```

```go
func Foo() (err error) {
return err
}
```

</details>

### Installation

`gofumpt` is a replacement for `gofmt`, so you can simply `go install` it as
Expand Down
63 changes: 63 additions & 0 deletions format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
}
}

Expand Down
74 changes: 74 additions & 0 deletions testdata/script/clothe-returns.txtar
Original file line number Diff line number Diff line change
@@ -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() {}