lint.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. // Package lint provides the foundation for tools like staticcheck
  2. package lint // import "honnef.co/go/tools/lint"
  3. import (
  4. "bytes"
  5. "fmt"
  6. "go/scanner"
  7. "go/token"
  8. "go/types"
  9. "path/filepath"
  10. "sort"
  11. "strings"
  12. "sync"
  13. "sync/atomic"
  14. "unicode"
  15. "golang.org/x/tools/go/analysis"
  16. "golang.org/x/tools/go/packages"
  17. "honnef.co/go/tools/config"
  18. )
  19. type Documentation struct {
  20. Title string
  21. Text string
  22. Since string
  23. NonDefault bool
  24. Options []string
  25. }
  26. func (doc *Documentation) String() string {
  27. b := &strings.Builder{}
  28. fmt.Fprintf(b, "%s\n\n", doc.Title)
  29. if doc.Text != "" {
  30. fmt.Fprintf(b, "%s\n\n", doc.Text)
  31. }
  32. fmt.Fprint(b, "Available since\n ")
  33. if doc.Since == "" {
  34. fmt.Fprint(b, "unreleased")
  35. } else {
  36. fmt.Fprintf(b, "%s", doc.Since)
  37. }
  38. if doc.NonDefault {
  39. fmt.Fprint(b, ", non-default")
  40. }
  41. fmt.Fprint(b, "\n")
  42. if len(doc.Options) > 0 {
  43. fmt.Fprintf(b, "\nOptions\n")
  44. for _, opt := range doc.Options {
  45. fmt.Fprintf(b, " %s", opt)
  46. }
  47. fmt.Fprint(b, "\n")
  48. }
  49. return b.String()
  50. }
  51. type Ignore interface {
  52. Match(p Problem) bool
  53. }
  54. type LineIgnore struct {
  55. File string
  56. Line int
  57. Checks []string
  58. Matched bool
  59. Pos token.Pos
  60. }
  61. func (li *LineIgnore) Match(p Problem) bool {
  62. pos := p.Pos
  63. if pos.Filename != li.File || pos.Line != li.Line {
  64. return false
  65. }
  66. for _, c := range li.Checks {
  67. if m, _ := filepath.Match(c, p.Check); m {
  68. li.Matched = true
  69. return true
  70. }
  71. }
  72. return false
  73. }
  74. func (li *LineIgnore) String() string {
  75. matched := "not matched"
  76. if li.Matched {
  77. matched = "matched"
  78. }
  79. return fmt.Sprintf("%s:%d %s (%s)", li.File, li.Line, strings.Join(li.Checks, ", "), matched)
  80. }
  81. type FileIgnore struct {
  82. File string
  83. Checks []string
  84. }
  85. func (fi *FileIgnore) Match(p Problem) bool {
  86. if p.Pos.Filename != fi.File {
  87. return false
  88. }
  89. for _, c := range fi.Checks {
  90. if m, _ := filepath.Match(c, p.Check); m {
  91. return true
  92. }
  93. }
  94. return false
  95. }
  96. type Severity uint8
  97. const (
  98. Error Severity = iota
  99. Warning
  100. Ignored
  101. )
  102. // Problem represents a problem in some source code.
  103. type Problem struct {
  104. Pos token.Position
  105. End token.Position
  106. Message string
  107. Check string
  108. Severity Severity
  109. }
  110. func (p *Problem) String() string {
  111. return fmt.Sprintf("%s (%s)", p.Message, p.Check)
  112. }
  113. // A Linter lints Go source code.
  114. type Linter struct {
  115. Checkers []*analysis.Analyzer
  116. CumulativeCheckers []CumulativeChecker
  117. GoVersion int
  118. Config config.Config
  119. Stats Stats
  120. }
  121. type CumulativeChecker interface {
  122. Analyzer() *analysis.Analyzer
  123. Result() []types.Object
  124. ProblemObject(*token.FileSet, types.Object) Problem
  125. }
  126. func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error) {
  127. var allAnalyzers []*analysis.Analyzer
  128. allAnalyzers = append(allAnalyzers, l.Checkers...)
  129. for _, cum := range l.CumulativeCheckers {
  130. allAnalyzers = append(allAnalyzers, cum.Analyzer())
  131. }
  132. // The -checks command line flag overrules all configuration
  133. // files, which means that for `-checks="foo"`, no check other
  134. // than foo can ever be reported to the user. Make use of this
  135. // fact to cull the list of analyses we need to run.
  136. // replace "inherit" with "all", as we don't want to base the
  137. // list of all checks on the default configuration, which
  138. // disables certain checks.
  139. checks := make([]string, len(l.Config.Checks))
  140. copy(checks, l.Config.Checks)
  141. for i, c := range checks {
  142. if c == "inherit" {
  143. checks[i] = "all"
  144. }
  145. }
  146. allowed := FilterChecks(allAnalyzers, checks)
  147. var allowedAnalyzers []*analysis.Analyzer
  148. for _, c := range l.Checkers {
  149. if allowed[c.Name] {
  150. allowedAnalyzers = append(allowedAnalyzers, c)
  151. }
  152. }
  153. hasCumulative := false
  154. for _, cum := range l.CumulativeCheckers {
  155. a := cum.Analyzer()
  156. if allowed[a.Name] {
  157. hasCumulative = true
  158. allowedAnalyzers = append(allowedAnalyzers, a)
  159. }
  160. }
  161. r, err := NewRunner(&l.Stats)
  162. if err != nil {
  163. return nil, err
  164. }
  165. r.goVersion = l.GoVersion
  166. pkgs, err := r.Run(cfg, patterns, allowedAnalyzers, hasCumulative)
  167. if err != nil {
  168. return nil, err
  169. }
  170. tpkgToPkg := map[*types.Package]*Package{}
  171. for _, pkg := range pkgs {
  172. tpkgToPkg[pkg.Types] = pkg
  173. for _, e := range pkg.errs {
  174. switch e := e.(type) {
  175. case types.Error:
  176. p := Problem{
  177. Pos: e.Fset.PositionFor(e.Pos, false),
  178. Message: e.Msg,
  179. Severity: Error,
  180. Check: "compile",
  181. }
  182. pkg.problems = append(pkg.problems, p)
  183. case packages.Error:
  184. msg := e.Msg
  185. if len(msg) != 0 && msg[0] == '\n' {
  186. // TODO(dh): See https://github.com/golang/go/issues/32363
  187. msg = msg[1:]
  188. }
  189. var pos token.Position
  190. if e.Pos == "" {
  191. // Under certain conditions (malformed package
  192. // declarations, multiple packages in the same
  193. // directory), go list emits an error on stderr
  194. // instead of JSON. Those errors do not have
  195. // associated position information in
  196. // go/packages.Error, even though the output on
  197. // stderr may contain it.
  198. if p, n, err := parsePos(msg); err == nil {
  199. if abs, err := filepath.Abs(p.Filename); err == nil {
  200. p.Filename = abs
  201. }
  202. pos = p
  203. msg = msg[n+2:]
  204. }
  205. } else {
  206. var err error
  207. pos, _, err = parsePos(e.Pos)
  208. if err != nil {
  209. panic(fmt.Sprintf("internal error: %s", e))
  210. }
  211. }
  212. p := Problem{
  213. Pos: pos,
  214. Message: msg,
  215. Severity: Error,
  216. Check: "compile",
  217. }
  218. pkg.problems = append(pkg.problems, p)
  219. case scanner.ErrorList:
  220. for _, e := range e {
  221. p := Problem{
  222. Pos: e.Pos,
  223. Message: e.Msg,
  224. Severity: Error,
  225. Check: "compile",
  226. }
  227. pkg.problems = append(pkg.problems, p)
  228. }
  229. case error:
  230. p := Problem{
  231. Pos: token.Position{},
  232. Message: e.Error(),
  233. Severity: Error,
  234. Check: "compile",
  235. }
  236. pkg.problems = append(pkg.problems, p)
  237. }
  238. }
  239. }
  240. atomic.StoreUint32(&r.stats.State, StateCumulative)
  241. var problems []Problem
  242. for _, cum := range l.CumulativeCheckers {
  243. for _, res := range cum.Result() {
  244. pkg := tpkgToPkg[res.Pkg()]
  245. allowedChecks := FilterChecks(allowedAnalyzers, pkg.cfg.Merge(l.Config).Checks)
  246. if allowedChecks[cum.Analyzer().Name] {
  247. pos := DisplayPosition(pkg.Fset, res.Pos())
  248. // FIXME(dh): why are we ignoring generated files
  249. // here? Surely this is specific to 'unused', not all
  250. // cumulative checkers
  251. if _, ok := pkg.gen[pos.Filename]; ok {
  252. continue
  253. }
  254. p := cum.ProblemObject(pkg.Fset, res)
  255. problems = append(problems, p)
  256. }
  257. }
  258. }
  259. for _, pkg := range pkgs {
  260. for _, ig := range pkg.ignores {
  261. for i := range pkg.problems {
  262. p := &pkg.problems[i]
  263. if ig.Match(*p) {
  264. p.Severity = Ignored
  265. }
  266. }
  267. for i := range problems {
  268. p := &problems[i]
  269. if ig.Match(*p) {
  270. p.Severity = Ignored
  271. }
  272. }
  273. }
  274. if pkg.cfg == nil {
  275. // The package failed to load, otherwise we would have a
  276. // valid config. Pass through all errors.
  277. problems = append(problems, pkg.problems...)
  278. } else {
  279. for _, p := range pkg.problems {
  280. allowedChecks := FilterChecks(allowedAnalyzers, pkg.cfg.Merge(l.Config).Checks)
  281. allowedChecks["compile"] = true
  282. if allowedChecks[p.Check] {
  283. problems = append(problems, p)
  284. }
  285. }
  286. }
  287. for _, ig := range pkg.ignores {
  288. ig, ok := ig.(*LineIgnore)
  289. if !ok {
  290. continue
  291. }
  292. if ig.Matched {
  293. continue
  294. }
  295. couldveMatched := false
  296. allowedChecks := FilterChecks(allowedAnalyzers, pkg.cfg.Merge(l.Config).Checks)
  297. for _, c := range ig.Checks {
  298. if !allowedChecks[c] {
  299. continue
  300. }
  301. couldveMatched = true
  302. break
  303. }
  304. if !couldveMatched {
  305. // The ignored checks were disabled for the containing package.
  306. // Don't flag the ignore for not having matched.
  307. continue
  308. }
  309. p := Problem{
  310. Pos: DisplayPosition(pkg.Fset, ig.Pos),
  311. Message: "this linter directive didn't match anything; should it be removed?",
  312. Check: "",
  313. }
  314. problems = append(problems, p)
  315. }
  316. }
  317. if len(problems) == 0 {
  318. return nil, nil
  319. }
  320. sort.Slice(problems, func(i, j int) bool {
  321. pi := problems[i].Pos
  322. pj := problems[j].Pos
  323. if pi.Filename != pj.Filename {
  324. return pi.Filename < pj.Filename
  325. }
  326. if pi.Line != pj.Line {
  327. return pi.Line < pj.Line
  328. }
  329. if pi.Column != pj.Column {
  330. return pi.Column < pj.Column
  331. }
  332. return problems[i].Message < problems[j].Message
  333. })
  334. var out []Problem
  335. out = append(out, problems[0])
  336. for i, p := range problems[1:] {
  337. // We may encounter duplicate problems because one file
  338. // can be part of many packages.
  339. if problems[i] != p {
  340. out = append(out, p)
  341. }
  342. }
  343. return out, nil
  344. }
  345. func FilterChecks(allChecks []*analysis.Analyzer, checks []string) map[string]bool {
  346. // OPT(dh): this entire computation could be cached per package
  347. allowedChecks := map[string]bool{}
  348. for _, check := range checks {
  349. b := true
  350. if len(check) > 1 && check[0] == '-' {
  351. b = false
  352. check = check[1:]
  353. }
  354. if check == "*" || check == "all" {
  355. // Match all
  356. for _, c := range allChecks {
  357. allowedChecks[c.Name] = b
  358. }
  359. } else if strings.HasSuffix(check, "*") {
  360. // Glob
  361. prefix := check[:len(check)-1]
  362. isCat := strings.IndexFunc(prefix, func(r rune) bool { return unicode.IsNumber(r) }) == -1
  363. for _, c := range allChecks {
  364. idx := strings.IndexFunc(c.Name, func(r rune) bool { return unicode.IsNumber(r) })
  365. if isCat {
  366. // Glob is S*, which should match S1000 but not SA1000
  367. cat := c.Name[:idx]
  368. if prefix == cat {
  369. allowedChecks[c.Name] = b
  370. }
  371. } else {
  372. // Glob is S1*
  373. if strings.HasPrefix(c.Name, prefix) {
  374. allowedChecks[c.Name] = b
  375. }
  376. }
  377. }
  378. } else {
  379. // Literal check name
  380. allowedChecks[check] = b
  381. }
  382. }
  383. return allowedChecks
  384. }
  385. type Positioner interface {
  386. Pos() token.Pos
  387. }
  388. func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
  389. if p == token.NoPos {
  390. return token.Position{}
  391. }
  392. // Only use the adjusted position if it points to another Go file.
  393. // This means we'll point to the original file for cgo files, but
  394. // we won't point to a YACC grammar file.
  395. pos := fset.PositionFor(p, false)
  396. adjPos := fset.PositionFor(p, true)
  397. if filepath.Ext(adjPos.Filename) == ".go" {
  398. return adjPos
  399. }
  400. return pos
  401. }
  402. var bufferPool = &sync.Pool{
  403. New: func() interface{} {
  404. buf := bytes.NewBuffer(nil)
  405. buf.Grow(64)
  406. return buf
  407. },
  408. }
  409. func FuncName(f *types.Func) string {
  410. buf := bufferPool.Get().(*bytes.Buffer)
  411. buf.Reset()
  412. if f.Type() != nil {
  413. sig := f.Type().(*types.Signature)
  414. if recv := sig.Recv(); recv != nil {
  415. buf.WriteByte('(')
  416. if _, ok := recv.Type().(*types.Interface); ok {
  417. // gcimporter creates abstract methods of
  418. // named interfaces using the interface type
  419. // (not the named type) as the receiver.
  420. // Don't print it in full.
  421. buf.WriteString("interface")
  422. } else {
  423. types.WriteType(buf, recv.Type(), nil)
  424. }
  425. buf.WriteByte(')')
  426. buf.WriteByte('.')
  427. } else if f.Pkg() != nil {
  428. writePackage(buf, f.Pkg())
  429. }
  430. }
  431. buf.WriteString(f.Name())
  432. s := buf.String()
  433. bufferPool.Put(buf)
  434. return s
  435. }
  436. func writePackage(buf *bytes.Buffer, pkg *types.Package) {
  437. if pkg == nil {
  438. return
  439. }
  440. s := pkg.Path()
  441. if s != "" {
  442. buf.WriteString(s)
  443. buf.WriteByte('.')
  444. }
  445. }