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

     1  package golinters
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"sync"
     8  
     9  	"golang.org/x/tools/go/analysis"
    10  
    11  	"github.com/elek/golangci-lint/pkg/golinters/goanalysis"
    12  	"github.com/elek/golangci-lint/pkg/lint/linter"
    13  	"github.com/elek/golangci-lint/pkg/result"
    14  )
    15  
    16  type nakedretVisitor struct {
    17  	maxLength int
    18  	f         *token.FileSet
    19  	issues    []result.Issue
    20  }
    21  
    22  func (v *nakedretVisitor) processFuncDecl(funcDecl *ast.FuncDecl) {
    23  	file := v.f.File(funcDecl.Pos())
    24  	functionLineLength := file.Position(funcDecl.End()).Line - file.Position(funcDecl.Pos()).Line
    25  
    26  	// Scan the body for usage of the named returns
    27  	for _, stmt := range funcDecl.Body.List {
    28  		s, ok := stmt.(*ast.ReturnStmt)
    29  		if !ok {
    30  			continue
    31  		}
    32  
    33  		if len(s.Results) != 0 {
    34  			continue
    35  		}
    36  
    37  		file := v.f.File(s.Pos())
    38  		if file == nil || functionLineLength <= v.maxLength {
    39  			continue
    40  		}
    41  		if funcDecl.Name == nil {
    42  			continue
    43  		}
    44  
    45  		v.issues = append(v.issues, result.Issue{
    46  			FromLinter: nakedretName,
    47  			Text: fmt.Sprintf("naked return in func `%s` with %d lines of code",
    48  				funcDecl.Name.Name, functionLineLength),
    49  			Pos: v.f.Position(s.Pos()),
    50  		})
    51  	}
    52  }
    53  
    54  func (v *nakedretVisitor) Visit(node ast.Node) ast.Visitor {
    55  	funcDecl, ok := node.(*ast.FuncDecl)
    56  	if !ok {
    57  		return v
    58  	}
    59  
    60  	var namedReturns []*ast.Ident
    61  
    62  	// We've found a function
    63  	if funcDecl.Type != nil && funcDecl.Type.Results != nil {
    64  		for _, field := range funcDecl.Type.Results.List {
    65  			for _, ident := range field.Names {
    66  				if ident != nil {
    67  					namedReturns = append(namedReturns, ident)
    68  				}
    69  			}
    70  		}
    71  	}
    72  
    73  	if len(namedReturns) == 0 || funcDecl.Body == nil {
    74  		return v
    75  	}
    76  
    77  	v.processFuncDecl(funcDecl)
    78  	return v
    79  }
    80  
    81  const nakedretName = "nakedret"
    82  
    83  func NewNakedret() *goanalysis.Linter {
    84  	var mu sync.Mutex
    85  	var resIssues []goanalysis.Issue
    86  
    87  	analyzer := &analysis.Analyzer{
    88  		Name: nakedretName,
    89  		Doc:  goanalysis.TheOnlyanalyzerDoc,
    90  	}
    91  	return goanalysis.NewLinter(
    92  		nakedretName,
    93  		"Finds naked returns in functions greater than a specified function length",
    94  		[]*analysis.Analyzer{analyzer},
    95  		nil,
    96  	).WithContextSetter(func(lintCtx *linter.Context) {
    97  		analyzer.Run = func(pass *analysis.Pass) (interface{}, error) {
    98  			var res []goanalysis.Issue
    99  			for _, file := range pass.Files {
   100  				v := nakedretVisitor{
   101  					maxLength: lintCtx.Settings().Nakedret.MaxFuncLines,
   102  					f:         pass.Fset,
   103  				}
   104  				ast.Walk(&v, file)
   105  				for i := range v.issues {
   106  					res = append(res, goanalysis.NewIssue(&v.issues[i], pass))
   107  				}
   108  			}
   109  
   110  			if len(res) == 0 {
   111  				return nil, nil
   112  			}
   113  
   114  			mu.Lock()
   115  			resIssues = append(resIssues, res...)
   116  			mu.Unlock()
   117  
   118  			return nil, nil
   119  		}
   120  	}).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
   121  		return resIssues
   122  	}).WithLoadMode(goanalysis.LoadModeSyntax)
   123  }