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 }