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