github.com/elek/golangci-lint@v1.42.2-0.20211208090441-c05b7fcb3a9a/pkg/golinters/errcheck.go (about) 1 package golinters 2 3 import ( 4 "bufio" 5 "fmt" 6 "os" 7 "os/user" 8 "path/filepath" 9 "regexp" 10 "strings" 11 "sync" 12 13 "github.com/kisielk/errcheck/errcheck" 14 "github.com/pkg/errors" 15 "golang.org/x/tools/go/analysis" 16 "golang.org/x/tools/go/packages" 17 18 "github.com/elek/golangci-lint/pkg/config" 19 "github.com/elek/golangci-lint/pkg/fsutils" 20 "github.com/elek/golangci-lint/pkg/golinters/goanalysis" 21 "github.com/elek/golangci-lint/pkg/lint/linter" 22 "github.com/elek/golangci-lint/pkg/result" 23 ) 24 25 func NewErrcheck() *goanalysis.Linter { 26 const linterName = "errcheck" 27 28 var mu sync.Mutex 29 var res []goanalysis.Issue 30 31 analyzer := &analysis.Analyzer{ 32 Name: linterName, 33 Doc: goanalysis.TheOnlyanalyzerDoc, 34 } 35 36 return goanalysis.NewLinter( 37 linterName, 38 "Errcheck is a program for checking for unchecked errors "+ 39 "in go programs. These unchecked errors can be critical bugs in some cases", 40 []*analysis.Analyzer{analyzer}, 41 nil, 42 ).WithContextSetter(func(lintCtx *linter.Context) { 43 // copied from errcheck 44 checker, err := getChecker(&lintCtx.Settings().Errcheck) 45 if err != nil { 46 lintCtx.Log.Errorf("failed to get checker: %v", err) 47 return 48 } 49 50 checker.Tags = lintCtx.Cfg.Run.BuildTags 51 52 analyzer.Run = func(pass *analysis.Pass) (interface{}, error) { 53 pkg := &packages.Package{ 54 Fset: pass.Fset, 55 Syntax: pass.Files, 56 Types: pass.Pkg, 57 TypesInfo: pass.TypesInfo, 58 } 59 60 errcheckIssues := checker.CheckPackage(pkg).Unique() 61 if len(errcheckIssues.UncheckedErrors) == 0 { 62 return nil, nil 63 } 64 65 issues := make([]goanalysis.Issue, len(errcheckIssues.UncheckedErrors)) 66 for i, err := range errcheckIssues.UncheckedErrors { 67 var text string 68 if err.FuncName != "" { 69 text = fmt.Sprintf( 70 "Error return value of %s is not checked", 71 formatCode(err.SelectorName, lintCtx.Cfg), 72 ) 73 } else { 74 text = "Error return value is not checked" 75 } 76 77 issues[i] = goanalysis.NewIssue( 78 &result.Issue{ 79 FromLinter: linterName, 80 Text: text, 81 Pos: err.Pos, 82 }, 83 pass, 84 ) 85 } 86 87 mu.Lock() 88 res = append(res, issues...) 89 mu.Unlock() 90 91 return nil, nil 92 } 93 }).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue { 94 return res 95 }).WithLoadMode(goanalysis.LoadModeTypesInfo) 96 } 97 98 // parseIgnoreConfig was taken from errcheck in order to keep the API identical. 99 // https://github.com/kisielk/errcheck/blob/1787c4bee836470bf45018cfbc783650db3c6501/main.go#L25-L60 100 func parseIgnoreConfig(s string) (map[string]*regexp.Regexp, error) { 101 if s == "" { 102 return nil, nil 103 } 104 105 cfg := map[string]*regexp.Regexp{} 106 107 for _, pair := range strings.Split(s, ",") { 108 colonIndex := strings.Index(pair, ":") 109 var pkg, re string 110 if colonIndex == -1 { 111 pkg = "" 112 re = pair 113 } else { 114 pkg = pair[:colonIndex] 115 re = pair[colonIndex+1:] 116 } 117 regex, err := regexp.Compile(re) 118 if err != nil { 119 return nil, err 120 } 121 cfg[pkg] = regex 122 } 123 124 return cfg, nil 125 } 126 127 func getChecker(errCfg *config.ErrcheckSettings) (*errcheck.Checker, error) { 128 ignoreConfig, err := parseIgnoreConfig(errCfg.Ignore) 129 if err != nil { 130 return nil, errors.Wrap(err, "failed to parse 'ignore' directive") 131 } 132 133 checker := errcheck.Checker{ 134 Exclusions: errcheck.Exclusions{ 135 BlankAssignments: !errCfg.CheckAssignToBlank, 136 TypeAssertions: !errCfg.CheckTypeAssertions, 137 SymbolRegexpsByPackage: map[string]*regexp.Regexp{}, 138 Symbols: append([]string{}, errcheck.DefaultExcludedSymbols...), 139 }, 140 } 141 142 for pkg, re := range ignoreConfig { 143 checker.Exclusions.SymbolRegexpsByPackage[pkg] = re 144 } 145 146 if errCfg.Exclude != "" { 147 exclude, err := readExcludeFile(errCfg.Exclude) 148 if err != nil { 149 return nil, err 150 } 151 152 checker.Exclusions.Symbols = append(checker.Exclusions.Symbols, exclude...) 153 } 154 155 checker.Exclusions.Symbols = append(checker.Exclusions.Symbols, errCfg.ExcludeFunctions...) 156 157 return &checker, nil 158 } 159 160 func getFirstPathArg() string { 161 args := os.Args 162 163 // skip all args ([golangci-lint, run/linters]) before files/dirs list 164 for len(args) != 0 { 165 if args[0] == "run" { 166 args = args[1:] 167 break 168 } 169 170 args = args[1:] 171 } 172 173 // find first file/dir arg 174 firstArg := "./..." 175 for _, arg := range args { 176 if !strings.HasPrefix(arg, "-") { 177 firstArg = arg 178 break 179 } 180 } 181 182 return firstArg 183 } 184 185 func setupConfigFileSearch(name string) []string { 186 if strings.HasPrefix(name, "~") { 187 if u, err := user.Current(); err == nil { 188 name = strings.Replace(name, "~", u.HomeDir, 1) 189 } 190 } 191 192 if filepath.IsAbs(name) { 193 return []string{name} 194 } 195 196 firstArg := getFirstPathArg() 197 198 absStartPath, err := filepath.Abs(firstArg) 199 if err != nil { 200 absStartPath = filepath.Clean(firstArg) 201 } 202 203 // start from it 204 var curDir string 205 if fsutils.IsDir(absStartPath) { 206 curDir = absStartPath 207 } else { 208 curDir = filepath.Dir(absStartPath) 209 } 210 211 // find all dirs from it up to the root 212 configSearchPaths := []string{filepath.Join(".", name)} 213 for { 214 configSearchPaths = append(configSearchPaths, filepath.Join(curDir, name)) 215 newCurDir := filepath.Dir(curDir) 216 if curDir == newCurDir || newCurDir == "" { 217 break 218 } 219 curDir = newCurDir 220 } 221 222 return configSearchPaths 223 } 224 225 func readExcludeFile(name string) ([]string, error) { 226 var err error 227 var fh *os.File 228 229 for _, path := range setupConfigFileSearch(name) { 230 if fh, err = os.Open(path); err == nil { 231 break 232 } 233 } 234 235 if fh == nil { 236 return nil, errors.Wrapf(err, "failed reading exclude file: %s", name) 237 } 238 239 scanner := bufio.NewScanner(fh) 240 241 var excludes []string 242 for scanner.Scan() { 243 excludes = append(excludes, scanner.Text()) 244 } 245 246 if err := scanner.Err(); err != nil { 247 return nil, errors.Wrapf(err, "failed scanning file: %s", name) 248 } 249 250 return excludes, nil 251 }