This commit is contained in:
Igor Sirotin 2024-10-28 10:27:41 +00:00
parent 6ee62061bc
commit b0d3f87844
No known key found for this signature in database
GPG Key ID: 425E227CAAB81F95
4 changed files with 163 additions and 0 deletions

View File

@ -0,0 +1,89 @@
package analyzer
import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"go/ast"
"github.com/pkg/errors"
)
func New() *analysis.Analyzer {
return &analysis.Analyzer{
Name: "earlyreturn",
Doc: "reports places for early return pattern usage",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
}
func run(pass *analysis.Pass) (interface{}, error) {
inspected, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
if !ok {
return nil, errors.New("analyzer is not type *inspector.Inspector")
}
// Create a nodes filter for `if` statements
nodeFilter := []ast.Node{
(*ast.IfStmt)(nil),
}
// Inspect go statements
inspected.Preorder(nodeFilter, func(n ast.Node) {
processNode(pass, n)
})
inspected.WithStack(nodeFilter, func(n ast.Node, push bool, stack []ast.Node) bool {
return processNodeWithStack(pass, n, push, stack)
})
return nil, nil
}
func processNodeWithStack(pass *analysis.Pass, n ast.Node, push bool, stack []ast.Node) bool {
ifStmt, ok := n.(*ast.IfStmt)
if !ok {
panic("unexpected node type")
}
// Find the parent node of the `if` statement
if len(stack) < 2 {
return false
}
parent := stack[len(stack)-2] // Last node in stack is `n`. Second to last is the parent node.
// Check if the `if` statement has an `else` block
if ifStmt.Else != nil {
return
}
// Check if the `if` statement has a `return` statement
for _, stmt := range ifStmt.Body.List {
retStmt, ok := stmt.(*ast.ReturnStmt)
if ok {
pass.Reportf(retStmt.Pos(), "early return pattern detected")
return
}
}
}
func processNode(pass *analysis.Pass, n ast.Node) {
ifStmt, ok := n.(*ast.IfStmt)
if !ok {
panic("unexpected node type")
}
// Check if the `if` statement has an `else` block
if ifStmt.Else != nil {
return
}
// Check if the `if` statement has a `return` statement
for _, stmt := range ifStmt.Body.List {
retStmt, ok := stmt.(*ast.ReturnStmt)
if ok {
pass.Reportf(retStmt.Pos(), "early return pattern detected")
return
}
}
}

View File

@ -0,0 +1,12 @@
package analyzer
import (
"testing"
"golang.org/x/tools/go/analysis/analysistest"
)
func TestConditions(t *testing.T) {
t.Parallel()
a := New()
analysistest.Run(t, analysistest.TestData(), a, "functions")
}

View File

@ -0,0 +1,61 @@
package testdata
func test2(a bool, input string) string {
if a { // want "condition body is longer than the code after it"
input += "a"
input += "b"
input += "c"
return input
}
return "test"
}
func test1(a bool, input string) string {
out := input
if a {
// nothing to improve, `else` branch is same size as `if` branch
out += "a"
} else {
out += "b"
}
out += "c" // can't early return because: 1. has `else` branch AND 2. there's code after the `if` statement
return "test"
}
func test3(a bool, b bool, input string) string {
out := ""
if a {
out = "1"
} else {
if b {
out = "2"
} else {
out = "3"
}
}
return out
}
func test4(a bool, b bool) string {
out := ""
if a {
if b { // want "condition body is longer that `else` body"
out = "2"
} else {
out = "3"
}
} else {
out = "1"
}
return out
}
func test5(a bool, b bool, items []string) {
for range items {
if a { // want "condition body is longer than the code after it"
a := "1"
a += "2"
a += "3"
}
}
}

1
cmd/earlyreturn/main.go Normal file
View File

@ -0,0 +1 @@
package earlyreturn