github.com/vanstinator/golangci-lint@v0.0.0-20240223191551-cc572f00d9d1/pkg/golinters/dogsled.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/vanstinator/golangci-lint/pkg/config"
    12  	"github.com/vanstinator/golangci-lint/pkg/golinters/goanalysis"
    13  	"github.com/vanstinator/golangci-lint/pkg/lint/linter"
    14  	"github.com/vanstinator/golangci-lint/pkg/result"
    15  )
    16  
    17  const dogsledName = "dogsled"
    18  
    19  //nolint:dupl
    20  func NewDogsled(settings *config.DogsledSettings) *goanalysis.Linter {
    21  	var mu sync.Mutex
    22  	var resIssues []goanalysis.Issue
    23  
    24  	analyzer := &analysis.Analyzer{
    25  		Name: dogsledName,
    26  		Doc:  goanalysis.TheOnlyanalyzerDoc,
    27  		Run: func(pass *analysis.Pass) (any, error) {
    28  			issues := runDogsled(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  		dogsledName,
    44  		"Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())",
    45  		[]*analysis.Analyzer{analyzer},
    46  		nil,
    47  	).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
    48  		return resIssues
    49  	}).WithLoadMode(goanalysis.LoadModeSyntax)
    50  }
    51  
    52  func runDogsled(pass *analysis.Pass, settings *config.DogsledSettings) []goanalysis.Issue {
    53  	var reports []goanalysis.Issue
    54  	for _, f := range pass.Files {
    55  		v := &returnsVisitor{
    56  			maxBlanks: settings.MaxBlankIdentifiers,
    57  			f:         pass.Fset,
    58  		}
    59  
    60  		ast.Walk(v, f)
    61  
    62  		for i := range v.issues {
    63  			reports = append(reports, goanalysis.NewIssue(&v.issues[i], pass))
    64  		}
    65  	}
    66  
    67  	return reports
    68  }
    69  
    70  type returnsVisitor struct {
    71  	f         *token.FileSet
    72  	maxBlanks int
    73  	issues    []result.Issue
    74  }
    75  
    76  func (v *returnsVisitor) Visit(node ast.Node) ast.Visitor {
    77  	funcDecl, ok := node.(*ast.FuncDecl)
    78  	if !ok {
    79  		return v
    80  	}
    81  	if funcDecl.Body == nil {
    82  		return v
    83  	}
    84  
    85  	for _, expr := range funcDecl.Body.List {
    86  		assgnStmt, ok := expr.(*ast.AssignStmt)
    87  		if !ok {
    88  			continue
    89  		}
    90  
    91  		numBlank := 0
    92  		for _, left := range assgnStmt.Lhs {
    93  			ident, ok := left.(*ast.Ident)
    94  			if !ok {
    95  				continue
    96  			}
    97  			if ident.Name == "_" {
    98  				numBlank++
    99  			}
   100  		}
   101  
   102  		if numBlank > v.maxBlanks {
   103  			v.issues = append(v.issues, result.Issue{
   104  				FromLinter: dogsledName,
   105  				Text:       fmt.Sprintf("declaration has %v blank identifiers", numBlank),
   106  				Pos:        v.f.Position(assgnStmt.Pos()),
   107  			})
   108  		}
   109  	}
   110  	return v
   111  }