104 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			104 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package checkers
 | |
| 
 | |
| import (
 | |
| 	"go/ast"
 | |
| 	"go/types"
 | |
| 
 | |
| 	"github.com/go-lintpack/lintpack"
 | |
| 	"github.com/go-lintpack/lintpack/astwalk"
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	var info lintpack.CheckerInfo
 | |
| 	info.Name = "unnamedResult"
 | |
| 	info.Tags = []string{"style", "opinionated", "experimental"}
 | |
| 	info.Params = lintpack.CheckerParams{
 | |
| 		"checkExported": {
 | |
| 			Value: false,
 | |
| 			Usage: "whether to check exported functions",
 | |
| 		},
 | |
| 	}
 | |
| 	info.Summary = "Detects unnamed results that may benefit from names"
 | |
| 	info.Before = `func f() (float64, float64)`
 | |
| 	info.After = `func f() (x, y float64)`
 | |
| 
 | |
| 	collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
 | |
| 		c := &unnamedResultChecker{ctx: ctx}
 | |
| 		c.checkExported = info.Params.Bool("checkExported")
 | |
| 		return astwalk.WalkerForFuncDecl(c)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| type unnamedResultChecker struct {
 | |
| 	astwalk.WalkHandler
 | |
| 	ctx *lintpack.CheckerContext
 | |
| 
 | |
| 	checkExported bool
 | |
| }
 | |
| 
 | |
| func (c *unnamedResultChecker) VisitFuncDecl(decl *ast.FuncDecl) {
 | |
| 	if c.checkExported && !ast.IsExported(decl.Name.Name) {
 | |
| 		return
 | |
| 	}
 | |
| 	results := decl.Type.Results
 | |
| 	switch {
 | |
| 	case results == nil:
 | |
| 		return // Function has no results
 | |
| 	case len(results.List) > 0 && results.List[0].Names != nil:
 | |
| 		return // Skip named results
 | |
| 	}
 | |
| 
 | |
| 	typeName := func(x ast.Expr) string { return c.typeName(c.ctx.TypesInfo.TypeOf(x)) }
 | |
| 	isError := func(x ast.Expr) bool { return qualifiedName(x) == "error" }
 | |
| 	isBool := func(x ast.Expr) bool { return qualifiedName(x) == "bool" }
 | |
| 
 | |
| 	// Main difference with case of len=2 is that we permit any
 | |
| 	// typ1 as long as second type is either error or bool.
 | |
| 	if results.NumFields() == 2 {
 | |
| 		typ1, typ2 := results.List[0].Type, results.List[1].Type
 | |
| 		name1, name2 := typeName(typ1), typeName(typ2)
 | |
| 		cond := (name1 != name2 && name2 != "") ||
 | |
| 			(!isError(typ1) && isError(typ2)) ||
 | |
| 			(!isBool(typ1) && isBool(typ2))
 | |
| 		if !cond {
 | |
| 			c.warn(decl)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	seen := make(map[string]bool, len(results.List))
 | |
| 	for i := range results.List {
 | |
| 		typ := results.List[i].Type
 | |
| 		name := typeName(typ)
 | |
| 		isLast := i == len(results.List)-1
 | |
| 
 | |
| 		cond := !seen[name] ||
 | |
| 			(isLast && (isError(typ) || isBool(typ)))
 | |
| 		if !cond {
 | |
| 			c.warn(decl)
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		seen[name] = true
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *unnamedResultChecker) typeName(typ types.Type) string {
 | |
| 	switch typ := typ.(type) {
 | |
| 	case *types.Array:
 | |
| 		return c.typeName(typ.Elem())
 | |
| 	case *types.Pointer:
 | |
| 		return c.typeName(typ.Elem())
 | |
| 	case *types.Slice:
 | |
| 		return c.typeName(typ.Elem())
 | |
| 	case *types.Named:
 | |
| 		return typ.Obj().Name()
 | |
| 	default:
 | |
| 		return ""
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (c *unnamedResultChecker) warn(n ast.Node) {
 | |
| 	c.ctx.Warn(n, "consider giving a name to these results")
 | |
| }
 | 
