github.com/vanstinator/golangci-lint@v0.0.0-20240223191551-cc572f00d9d1/pkg/golinters/unused.go (about)

     1  package golinters
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"golang.org/x/tools/go/analysis"
     8  	"honnef.co/go/tools/analysis/facts/directives"
     9  	"honnef.co/go/tools/analysis/facts/generated"
    10  	"honnef.co/go/tools/analysis/lint"
    11  	"honnef.co/go/tools/unused"
    12  
    13  	"github.com/vanstinator/golangci-lint/pkg/config"
    14  	"github.com/vanstinator/golangci-lint/pkg/golinters/goanalysis"
    15  	"github.com/vanstinator/golangci-lint/pkg/lint/linter"
    16  	"github.com/vanstinator/golangci-lint/pkg/result"
    17  )
    18  
    19  const unusedName = "unused"
    20  
    21  func NewUnused(settings *config.UnusedSettings, scSettings *config.StaticCheckSettings) *goanalysis.Linter {
    22  	var mu sync.Mutex
    23  	var resIssues []goanalysis.Issue
    24  
    25  	analyzer := &analysis.Analyzer{
    26  		Name:     unusedName,
    27  		Doc:      unused.Analyzer.Analyzer.Doc,
    28  		Requires: unused.Analyzer.Analyzer.Requires,
    29  		Run: func(pass *analysis.Pass) (any, error) {
    30  			issues := runUnused(pass, settings)
    31  			if len(issues) == 0 {
    32  				return nil, nil
    33  			}
    34  
    35  			mu.Lock()
    36  			resIssues = append(resIssues, issues...)
    37  			mu.Unlock()
    38  
    39  			return nil, nil
    40  		},
    41  	}
    42  
    43  	setAnalyzerGoVersion(analyzer, getGoVersion(scSettings))
    44  
    45  	return goanalysis.NewLinter(
    46  		unusedName,
    47  		"Checks Go code for unused constants, variables, functions and types",
    48  		[]*analysis.Analyzer{analyzer},
    49  		nil,
    50  	).WithIssuesReporter(func(lintCtx *linter.Context) []goanalysis.Issue {
    51  		return resIssues
    52  	}).WithLoadMode(goanalysis.LoadModeTypesInfo)
    53  }
    54  
    55  func runUnused(pass *analysis.Pass, cfg *config.UnusedSettings) []goanalysis.Issue {
    56  	res := getUnusedResults(pass, cfg)
    57  
    58  	used := make(map[string]bool)
    59  	for _, obj := range res.Used {
    60  		used[fmt.Sprintf("%s %d %s", obj.Position.Filename, obj.Position.Line, obj.Name)] = true
    61  	}
    62  
    63  	var issues []goanalysis.Issue
    64  
    65  	// Inspired by https://github.com/dominikh/go-tools/blob/d694aadcb1f50c2d8ac0a1dd06217ebb9f654764/lintcmd/lint.go#L177-L197
    66  	for _, object := range res.Unused {
    67  		if object.Kind == "type param" {
    68  			continue
    69  		}
    70  
    71  		key := fmt.Sprintf("%s %d %s", object.Position.Filename, object.Position.Line, object.Name)
    72  		if used[key] {
    73  			continue
    74  		}
    75  
    76  		issue := goanalysis.NewIssue(&result.Issue{
    77  			FromLinter: unusedName,
    78  			Text:       fmt.Sprintf("%s %s is unused", object.Kind, object.Name),
    79  			Pos:        object.Position,
    80  		}, pass)
    81  
    82  		issues = append(issues, issue)
    83  	}
    84  
    85  	return issues
    86  }
    87  
    88  func getUnusedResults(pass *analysis.Pass, settings *config.UnusedSettings) unused.Result {
    89  	opts := unused.Options{
    90  		FieldWritesAreUses:     settings.FieldWritesAreUses,
    91  		PostStatementsAreReads: settings.PostStatementsAreReads,
    92  		ExportedIsUsed:         settings.ExportedIsUsed,
    93  		ExportedFieldsAreUsed:  settings.ExportedFieldsAreUsed,
    94  		ParametersAreUsed:      settings.ParametersAreUsed,
    95  		LocalVariablesAreUsed:  settings.LocalVariablesAreUsed,
    96  		GeneratedIsUsed:        settings.GeneratedIsUsed,
    97  	}
    98  
    99  	// ref: https://github.com/dominikh/go-tools/blob/4ec1f474ca6c0feb8e10a8fcca4ab95f5b5b9881/internal/cmd/unused/unused.go#L68
   100  	nodes := unused.Graph(pass.Fset,
   101  		pass.Files,
   102  		pass.Pkg,
   103  		pass.TypesInfo,
   104  		pass.ResultOf[directives.Analyzer].([]lint.Directive),
   105  		pass.ResultOf[generated.Analyzer].(map[string]generated.Generator),
   106  		opts,
   107  	)
   108  
   109  	sg := unused.SerializedGraph{}
   110  	sg.Merge(nodes)
   111  	return sg.Results()
   112  }