github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/golinters/errs/main.go (about)

     1  // Copyright (C) 2019 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package errs
     5  
     6  import (
     7  	"github.com/elek/golangci-lint/pkg/golinters/goanalysis"
     8  	"go/ast"
     9  	"go/token"
    10  
    11  	"golang.org/x/tools/go/analysis"
    12  	"golang.org/x/tools/go/analysis/passes/inspect"
    13  	"golang.org/x/tools/go/ast/inspector"
    14  	"golang.org/x/tools/go/types/typeutil"
    15  )
    16  
    17  func NewErrsCheck() *goanalysis.Linter {
    18  	return goanalysis.NewLinter(
    19  		"errs",
    20  		"STORJ: moknit related checks",
    21  		[]*analysis.Analyzer{
    22  			Analyzer,
    23  		},
    24  		nil,
    25  	).WithLoadMode(goanalysis.LoadModeWholeProgram)
    26  }
    27  
    28  // Analyzer verifies whether errs package is properly used.
    29  var Analyzer = &analysis.Analyzer{
    30  	Name: "errs",
    31  	Doc:  "check for proper usage of errs package",
    32  	Run:  run,
    33  	Requires: []*analysis.Analyzer{
    34  		inspect.Analyzer,
    35  	},
    36  	FactTypes: []analysis.Fact{},
    37  }
    38  
    39  func run(pass *analysis.Pass) (interface{}, error) {
    40  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    41  	nodeFilter := []ast.Node{
    42  		(*ast.CallExpr)(nil),
    43  	}
    44  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    45  		call := n.(*ast.CallExpr)
    46  		fn := typeutil.StaticCallee(pass.TypesInfo, call)
    47  		if fn == nil {
    48  			return // not a static call
    49  		}
    50  
    51  		switch fn.FullName() {
    52  		case "github.com/zeebo/errs.Combine":
    53  			if len(call.Args) == 0 {
    54  				pass.Reportf(call.Lparen, "errs.Combine() can be simplified to nil")
    55  			}
    56  			if len(call.Args) == 1 && call.Ellipsis == token.NoPos {
    57  				pass.Reportf(call.Lparen, "errs.Combine(x) can be simplified to x")
    58  			}
    59  		case "(*github.com/zeebo/errs.Class).New":
    60  			if len(call.Args) == 0 {
    61  				return
    62  			}
    63  			// Disallow things like Error.New(err.Error())
    64  
    65  			switch arg := call.Args[0].(type) {
    66  			case *ast.BasicLit: // allow string constants
    67  			case *ast.Ident: // allow string variables
    68  			default:
    69  				// allow "alpha" + "beta" + "gamma"
    70  				if IsConcatString(arg) {
    71  					return
    72  				}
    73  
    74  				pass.Reportf(call.Lparen, "(*errs.Class).New with non-obvious format string")
    75  			}
    76  		}
    77  	})
    78  
    79  	return nil, nil
    80  }
    81  
    82  // IsConcatString returns whether arg is a basic string expression.
    83  func IsConcatString(arg ast.Expr) bool {
    84  	switch arg := arg.(type) {
    85  	case *ast.BasicLit:
    86  		return arg.Kind == token.STRING
    87  	case *ast.BinaryExpr:
    88  		return arg.Op == token.ADD && IsConcatString(arg.X) && IsConcatString(arg.Y)
    89  	default:
    90  		return false
    91  	}
    92  }