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