lint.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958
  1. package stylecheck
  2. import (
  3. "fmt"
  4. "go/ast"
  5. "go/constant"
  6. "go/token"
  7. "go/types"
  8. "sort"
  9. "strconv"
  10. "strings"
  11. "unicode"
  12. "unicode/utf8"
  13. "honnef.co/go/tools/analysis/code"
  14. "honnef.co/go/tools/analysis/edit"
  15. "honnef.co/go/tools/analysis/lint"
  16. "honnef.co/go/tools/analysis/report"
  17. "honnef.co/go/tools/config"
  18. "honnef.co/go/tools/go/ast/astutil"
  19. "honnef.co/go/tools/go/ir"
  20. "honnef.co/go/tools/go/ir/irutil"
  21. "honnef.co/go/tools/go/types/typeutil"
  22. "honnef.co/go/tools/internal/passes/buildir"
  23. "honnef.co/go/tools/pattern"
  24. "golang.org/x/exp/typeparams"
  25. "golang.org/x/tools/go/analysis"
  26. "golang.org/x/tools/go/analysis/passes/inspect"
  27. "golang.org/x/tools/go/ast/inspector"
  28. )
  29. func docText(doc *ast.CommentGroup) (string, bool) {
  30. if doc == nil {
  31. return "", false
  32. }
  33. // We trim spaces primarily because of /**/ style comments, which often have leading space.
  34. text := strings.TrimSpace(doc.Text())
  35. return text, text != ""
  36. }
  37. func CheckPackageComment(pass *analysis.Pass) (interface{}, error) {
  38. // - At least one file in a non-main package should have a package comment
  39. //
  40. // - The comment should be of the form
  41. // "Package x ...". This has a slight potential for false
  42. // positives, as multiple files can have package comments, in
  43. // which case they get appended. But that doesn't happen a lot in
  44. // the real world.
  45. if pass.Pkg.Name() == "main" {
  46. return nil, nil
  47. }
  48. hasDocs := false
  49. for _, f := range pass.Files {
  50. if code.IsInTest(pass, f) {
  51. continue
  52. }
  53. text, ok := docText(f.Doc)
  54. if ok {
  55. hasDocs = true
  56. prefix := "Package " + f.Name.Name + " "
  57. if !strings.HasPrefix(text, prefix) {
  58. report.Report(pass, f.Doc, fmt.Sprintf(`package comment should be of the form "%s..."`, prefix))
  59. }
  60. }
  61. }
  62. if !hasDocs {
  63. for _, f := range pass.Files {
  64. if code.IsInTest(pass, f) {
  65. continue
  66. }
  67. report.Report(pass, f, "at least one file in a package should have a package comment", report.ShortRange())
  68. }
  69. }
  70. return nil, nil
  71. }
  72. func CheckDotImports(pass *analysis.Pass) (interface{}, error) {
  73. for _, f := range pass.Files {
  74. imports:
  75. for _, imp := range f.Imports {
  76. path := imp.Path.Value
  77. path = path[1 : len(path)-1]
  78. for _, w := range config.For(pass).DotImportWhitelist {
  79. if w == path {
  80. continue imports
  81. }
  82. }
  83. if imp.Name != nil && imp.Name.Name == "." && !code.IsInTest(pass, f) {
  84. report.Report(pass, imp, "should not use dot imports", report.FilterGenerated())
  85. }
  86. }
  87. }
  88. return nil, nil
  89. }
  90. func CheckDuplicatedImports(pass *analysis.Pass) (interface{}, error) {
  91. for _, f := range pass.Files {
  92. // Collect all imports by their import path
  93. imports := make(map[string][]*ast.ImportSpec, len(f.Imports))
  94. for _, imp := range f.Imports {
  95. imports[imp.Path.Value] = append(imports[imp.Path.Value], imp)
  96. }
  97. for path, value := range imports {
  98. if path[1:len(path)-1] == "unsafe" {
  99. // Don't flag unsafe. Cgo generated code imports
  100. // unsafe using the blank identifier, and most
  101. // user-written cgo code also imports unsafe
  102. // explicitly.
  103. continue
  104. }
  105. // If there's more than one import per path, we flag that
  106. if len(value) > 1 {
  107. s := fmt.Sprintf("package %s is being imported more than once", path)
  108. opts := []report.Option{report.FilterGenerated()}
  109. for _, imp := range value[1:] {
  110. opts = append(opts, report.Related(imp, fmt.Sprintf("other import of %s", path)))
  111. }
  112. report.Report(pass, value[0], s, opts...)
  113. }
  114. }
  115. }
  116. return nil, nil
  117. }
  118. func CheckBlankImports(pass *analysis.Pass) (interface{}, error) {
  119. fset := pass.Fset
  120. for _, f := range pass.Files {
  121. if code.IsMainLike(pass) || code.IsInTest(pass, f) {
  122. continue
  123. }
  124. // Collect imports of the form `import _ "foo"`, i.e. with no
  125. // parentheses, as their comment will be associated with the
  126. // (paren-free) GenDecl, not the import spec itself.
  127. //
  128. // We don't directly process the GenDecl so that we can
  129. // correctly handle the following:
  130. //
  131. // import _ "foo"
  132. // import _ "bar"
  133. //
  134. // where only the first import should get flagged.
  135. skip := map[ast.Spec]bool{}
  136. ast.Inspect(f, func(node ast.Node) bool {
  137. switch node := node.(type) {
  138. case *ast.File:
  139. return true
  140. case *ast.GenDecl:
  141. if node.Tok != token.IMPORT {
  142. return false
  143. }
  144. if node.Lparen == token.NoPos && node.Doc != nil {
  145. skip[node.Specs[0]] = true
  146. }
  147. return false
  148. }
  149. return false
  150. })
  151. for i, imp := range f.Imports {
  152. pos := fset.Position(imp.Pos())
  153. if !astutil.IsBlank(imp.Name) {
  154. continue
  155. }
  156. // Only flag the first blank import in a group of imports,
  157. // or don't flag any of them, if the first one is
  158. // commented
  159. if i > 0 {
  160. prev := f.Imports[i-1]
  161. prevPos := fset.Position(prev.Pos())
  162. if pos.Line-1 == prevPos.Line && astutil.IsBlank(prev.Name) {
  163. continue
  164. }
  165. }
  166. if imp.Doc == nil && imp.Comment == nil && !skip[imp] {
  167. report.Report(pass, imp, "a blank import should be only in a main or test package, or have a comment justifying it")
  168. }
  169. }
  170. }
  171. return nil, nil
  172. }
  173. var checkIncDecQ = pattern.MustParse(`(AssignStmt x tok@(Or "+=" "-=") (BasicLit "INT" "1"))`)
  174. func CheckIncDec(pass *analysis.Pass) (interface{}, error) {
  175. // TODO(dh): this can be noisy for function bodies that look like this:
  176. // x += 3
  177. // ...
  178. // x += 2
  179. // ...
  180. // x += 1
  181. fn := func(node ast.Node) {
  182. m, ok := code.Match(pass, checkIncDecQ, node)
  183. if !ok {
  184. return
  185. }
  186. suffix := ""
  187. switch m.State["tok"].(token.Token) {
  188. case token.ADD_ASSIGN:
  189. suffix = "++"
  190. case token.SUB_ASSIGN:
  191. suffix = "--"
  192. }
  193. report.Report(pass, node, fmt.Sprintf("should replace %s with %s%s", report.Render(pass, node), report.Render(pass, m.State["x"].(ast.Node)), suffix))
  194. }
  195. code.Preorder(pass, fn, (*ast.AssignStmt)(nil))
  196. return nil, nil
  197. }
  198. func CheckErrorReturn(pass *analysis.Pass) (interface{}, error) {
  199. fnLoop:
  200. for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
  201. sig := fn.Type().(*types.Signature)
  202. rets := sig.Results()
  203. if rets == nil || rets.Len() < 2 {
  204. continue
  205. }
  206. if rets.At(rets.Len()-1).Type() == types.Universe.Lookup("error").Type() {
  207. // Last return type is error. If the function also returns
  208. // errors in other positions, that's fine.
  209. continue
  210. }
  211. if rets.Len() >= 2 && rets.At(rets.Len()-1).Type() == types.Universe.Lookup("bool").Type() && rets.At(rets.Len()-2).Type() == types.Universe.Lookup("error").Type() {
  212. // Accept (..., error, bool) and assume it's a comma-ok function. It's not clear whether the bool should come last or not for these kinds of functions.
  213. continue
  214. }
  215. for i := rets.Len() - 2; i >= 0; i-- {
  216. if rets.At(i).Type() == types.Universe.Lookup("error").Type() {
  217. report.Report(pass, rets.At(i), "error should be returned as the last argument", report.ShortRange())
  218. continue fnLoop
  219. }
  220. }
  221. }
  222. return nil, nil
  223. }
  224. // CheckUnexportedReturn checks that exported functions on exported
  225. // types do not return unexported types.
  226. func CheckUnexportedReturn(pass *analysis.Pass) (interface{}, error) {
  227. for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
  228. if fn.Synthetic != 0 || fn.Parent() != nil {
  229. continue
  230. }
  231. if !ast.IsExported(fn.Name()) || code.IsMain(pass) || code.IsInTest(pass, fn) {
  232. continue
  233. }
  234. sig := fn.Type().(*types.Signature)
  235. if sig.Recv() != nil && !ast.IsExported(typeutil.Dereference(sig.Recv().Type()).(*types.Named).Obj().Name()) {
  236. continue
  237. }
  238. res := sig.Results()
  239. for i := 0; i < res.Len(); i++ {
  240. if named, ok := typeutil.DereferenceR(res.At(i).Type()).(*types.Named); ok &&
  241. !ast.IsExported(named.Obj().Name()) &&
  242. named != types.Universe.Lookup("error").Type() {
  243. report.Report(pass, fn, "should not return unexported type")
  244. }
  245. }
  246. }
  247. return nil, nil
  248. }
  249. func CheckReceiverNames(pass *analysis.Pass) (interface{}, error) {
  250. irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
  251. for _, m := range irpkg.Members {
  252. if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
  253. ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
  254. for _, sel := range ms {
  255. fn := sel.Obj().(*types.Func)
  256. recv := fn.Type().(*types.Signature).Recv()
  257. if typeutil.Dereference(recv.Type()) != T.Type() {
  258. // skip embedded methods
  259. continue
  260. }
  261. if recv.Name() == "self" || recv.Name() == "this" {
  262. report.Report(pass, recv, `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`, report.FilterGenerated())
  263. }
  264. if recv.Name() == "_" {
  265. report.Report(pass, recv, "receiver name should not be an underscore, omit the name if it is unused", report.FilterGenerated())
  266. }
  267. }
  268. }
  269. }
  270. return nil, nil
  271. }
  272. func CheckReceiverNamesIdentical(pass *analysis.Pass) (interface{}, error) {
  273. irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
  274. for _, m := range irpkg.Members {
  275. names := map[string]int{}
  276. var firstFn *types.Func
  277. if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
  278. ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
  279. for _, sel := range ms {
  280. fn := sel.Obj().(*types.Func)
  281. recv := fn.Type().(*types.Signature).Recv()
  282. if code.IsGenerated(pass, recv.Pos()) {
  283. // Don't concern ourselves with methods in generated code
  284. continue
  285. }
  286. if typeutil.Dereference(recv.Type()) != T.Type() {
  287. // skip embedded methods
  288. continue
  289. }
  290. if firstFn == nil {
  291. firstFn = fn
  292. }
  293. if recv.Name() != "" && recv.Name() != "_" {
  294. names[recv.Name()]++
  295. }
  296. }
  297. }
  298. if len(names) > 1 {
  299. var seen []string
  300. for name, count := range names {
  301. seen = append(seen, fmt.Sprintf("%dx %q", count, name))
  302. }
  303. sort.Strings(seen)
  304. report.Report(pass, firstFn, fmt.Sprintf("methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", ")))
  305. }
  306. }
  307. return nil, nil
  308. }
  309. func CheckContextFirstArg(pass *analysis.Pass) (interface{}, error) {
  310. // TODO(dh): this check doesn't apply to test helpers. Example from the stdlib:
  311. // func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) {
  312. fnLoop:
  313. for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
  314. if fn.Synthetic != 0 || fn.Parent() != nil {
  315. continue
  316. }
  317. params := fn.Signature.Params()
  318. if params.Len() < 2 {
  319. continue
  320. }
  321. if types.TypeString(params.At(0).Type(), nil) == "context.Context" {
  322. continue
  323. }
  324. for i := 1; i < params.Len(); i++ {
  325. param := params.At(i)
  326. if types.TypeString(param.Type(), nil) == "context.Context" {
  327. report.Report(pass, param, "context.Context should be the first argument of a function", report.ShortRange())
  328. continue fnLoop
  329. }
  330. }
  331. }
  332. return nil, nil
  333. }
  334. func CheckErrorStrings(pass *analysis.Pass) (interface{}, error) {
  335. objNames := map[*ir.Package]map[string]bool{}
  336. irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
  337. objNames[irpkg] = map[string]bool{}
  338. for _, m := range irpkg.Members {
  339. if typ, ok := m.(*ir.Type); ok {
  340. objNames[irpkg][typ.Name()] = true
  341. }
  342. }
  343. for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
  344. objNames[fn.Package()][fn.Name()] = true
  345. }
  346. for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
  347. if code.IsInTest(pass, fn) {
  348. // We don't care about malformed error messages in tests;
  349. // they're usually for direct human consumption, not part
  350. // of an API
  351. continue
  352. }
  353. for _, block := range fn.Blocks {
  354. instrLoop:
  355. for _, ins := range block.Instrs {
  356. call, ok := ins.(*ir.Call)
  357. if !ok {
  358. continue
  359. }
  360. if !irutil.IsCallToAny(call.Common(), "errors.New", "fmt.Errorf") {
  361. continue
  362. }
  363. k, ok := call.Common().Args[0].(*ir.Const)
  364. if !ok {
  365. continue
  366. }
  367. s := constant.StringVal(k.Value)
  368. if len(s) == 0 {
  369. continue
  370. }
  371. switch s[len(s)-1] {
  372. case '.', ':', '!', '\n':
  373. report.Report(pass, call, "error strings should not end with punctuation or newlines")
  374. }
  375. idx := strings.IndexByte(s, ' ')
  376. if idx == -1 {
  377. // single word error message, probably not a real
  378. // error but something used in tests or during
  379. // debugging
  380. continue
  381. }
  382. word := s[:idx]
  383. first, n := utf8.DecodeRuneInString(word)
  384. if !unicode.IsUpper(first) {
  385. continue
  386. }
  387. for _, c := range word[n:] {
  388. if unicode.IsUpper(c) {
  389. // Word is probably an initialism or
  390. // multi-word function name
  391. continue instrLoop
  392. }
  393. }
  394. if strings.ContainsRune(word, '(') {
  395. // Might be a function call
  396. continue instrLoop
  397. }
  398. word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) })
  399. if objNames[fn.Package()][word] {
  400. // Word is probably the name of a function or type in this package
  401. continue
  402. }
  403. // First word in error starts with a capital
  404. // letter, and the word doesn't contain any other
  405. // capitals, making it unlikely to be an
  406. // initialism or multi-word function name.
  407. //
  408. // It could still be a proper noun, though.
  409. report.Report(pass, call, "error strings should not be capitalized")
  410. }
  411. }
  412. }
  413. return nil, nil
  414. }
  415. func CheckTimeNames(pass *analysis.Pass) (interface{}, error) {
  416. suffixes := []string{
  417. "Sec", "Secs", "Seconds",
  418. "Msec", "Msecs",
  419. "Milli", "Millis", "Milliseconds",
  420. "Usec", "Usecs", "Microseconds",
  421. "MS", "Ms",
  422. }
  423. fn := func(names []*ast.Ident) {
  424. for _, name := range names {
  425. if _, ok := pass.TypesInfo.Defs[name]; !ok {
  426. continue
  427. }
  428. T := pass.TypesInfo.TypeOf(name)
  429. if !typeutil.IsType(T, "time.Duration") && !typeutil.IsType(T, "*time.Duration") {
  430. continue
  431. }
  432. for _, suffix := range suffixes {
  433. if strings.HasSuffix(name.Name, suffix) {
  434. report.Report(pass, name, fmt.Sprintf("var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix))
  435. break
  436. }
  437. }
  438. }
  439. }
  440. fn2 := func(node ast.Node) {
  441. switch node := node.(type) {
  442. case *ast.ValueSpec:
  443. fn(node.Names)
  444. case *ast.FieldList:
  445. for _, field := range node.List {
  446. fn(field.Names)
  447. }
  448. case *ast.AssignStmt:
  449. if node.Tok != token.DEFINE {
  450. break
  451. }
  452. var names []*ast.Ident
  453. for _, lhs := range node.Lhs {
  454. if lhs, ok := lhs.(*ast.Ident); ok {
  455. names = append(names, lhs)
  456. }
  457. }
  458. fn(names)
  459. }
  460. }
  461. code.Preorder(pass, fn2, (*ast.ValueSpec)(nil), (*ast.FieldList)(nil), (*ast.AssignStmt)(nil))
  462. return nil, nil
  463. }
  464. func CheckErrorVarNames(pass *analysis.Pass) (interface{}, error) {
  465. for _, f := range pass.Files {
  466. for _, decl := range f.Decls {
  467. gen, ok := decl.(*ast.GenDecl)
  468. if !ok || gen.Tok != token.VAR {
  469. continue
  470. }
  471. for _, spec := range gen.Specs {
  472. spec := spec.(*ast.ValueSpec)
  473. if len(spec.Names) != len(spec.Values) {
  474. continue
  475. }
  476. for i, name := range spec.Names {
  477. val := spec.Values[i]
  478. if !code.IsCallToAny(pass, val, "errors.New", "fmt.Errorf") {
  479. continue
  480. }
  481. if pass.Pkg.Path() == "net/http" && strings.HasPrefix(name.Name, "http2err") {
  482. // special case for internal variable names of
  483. // bundled HTTP 2 code in net/http
  484. continue
  485. }
  486. prefix := "err"
  487. if name.IsExported() {
  488. prefix = "Err"
  489. }
  490. if !strings.HasPrefix(name.Name, prefix) {
  491. report.Report(pass, name, fmt.Sprintf("error var %s should have name of the form %sFoo", name.Name, prefix))
  492. }
  493. }
  494. }
  495. }
  496. }
  497. return nil, nil
  498. }
  499. var httpStatusCodes = map[int64]string{
  500. 100: "StatusContinue",
  501. 101: "StatusSwitchingProtocols",
  502. 102: "StatusProcessing",
  503. 200: "StatusOK",
  504. 201: "StatusCreated",
  505. 202: "StatusAccepted",
  506. 203: "StatusNonAuthoritativeInfo",
  507. 204: "StatusNoContent",
  508. 205: "StatusResetContent",
  509. 206: "StatusPartialContent",
  510. 207: "StatusMultiStatus",
  511. 208: "StatusAlreadyReported",
  512. 226: "StatusIMUsed",
  513. 300: "StatusMultipleChoices",
  514. 301: "StatusMovedPermanently",
  515. 302: "StatusFound",
  516. 303: "StatusSeeOther",
  517. 304: "StatusNotModified",
  518. 305: "StatusUseProxy",
  519. 307: "StatusTemporaryRedirect",
  520. 308: "StatusPermanentRedirect",
  521. 400: "StatusBadRequest",
  522. 401: "StatusUnauthorized",
  523. 402: "StatusPaymentRequired",
  524. 403: "StatusForbidden",
  525. 404: "StatusNotFound",
  526. 405: "StatusMethodNotAllowed",
  527. 406: "StatusNotAcceptable",
  528. 407: "StatusProxyAuthRequired",
  529. 408: "StatusRequestTimeout",
  530. 409: "StatusConflict",
  531. 410: "StatusGone",
  532. 411: "StatusLengthRequired",
  533. 412: "StatusPreconditionFailed",
  534. 413: "StatusRequestEntityTooLarge",
  535. 414: "StatusRequestURITooLong",
  536. 415: "StatusUnsupportedMediaType",
  537. 416: "StatusRequestedRangeNotSatisfiable",
  538. 417: "StatusExpectationFailed",
  539. 418: "StatusTeapot",
  540. 422: "StatusUnprocessableEntity",
  541. 423: "StatusLocked",
  542. 424: "StatusFailedDependency",
  543. 426: "StatusUpgradeRequired",
  544. 428: "StatusPreconditionRequired",
  545. 429: "StatusTooManyRequests",
  546. 431: "StatusRequestHeaderFieldsTooLarge",
  547. 451: "StatusUnavailableForLegalReasons",
  548. 500: "StatusInternalServerError",
  549. 501: "StatusNotImplemented",
  550. 502: "StatusBadGateway",
  551. 503: "StatusServiceUnavailable",
  552. 504: "StatusGatewayTimeout",
  553. 505: "StatusHTTPVersionNotSupported",
  554. 506: "StatusVariantAlsoNegotiates",
  555. 507: "StatusInsufficientStorage",
  556. 508: "StatusLoopDetected",
  557. 510: "StatusNotExtended",
  558. 511: "StatusNetworkAuthenticationRequired",
  559. }
  560. func CheckHTTPStatusCodes(pass *analysis.Pass) (interface{}, error) {
  561. whitelist := map[string]bool{}
  562. for _, code := range config.For(pass).HTTPStatusCodeWhitelist {
  563. whitelist[code] = true
  564. }
  565. fn := func(node ast.Node) {
  566. call := node.(*ast.CallExpr)
  567. var arg int
  568. switch code.CallName(pass, call) {
  569. case "net/http.Error":
  570. arg = 2
  571. case "net/http.Redirect":
  572. arg = 3
  573. case "net/http.StatusText":
  574. arg = 0
  575. case "net/http.RedirectHandler":
  576. arg = 1
  577. default:
  578. return
  579. }
  580. if arg >= len(call.Args) {
  581. return
  582. }
  583. tv, ok := code.IntegerLiteral(pass, call.Args[arg])
  584. if !ok {
  585. return
  586. }
  587. n, ok := constant.Int64Val(tv.Value)
  588. if !ok {
  589. return
  590. }
  591. if whitelist[strconv.FormatInt(n, 10)] {
  592. return
  593. }
  594. s, ok := httpStatusCodes[n]
  595. if !ok {
  596. return
  597. }
  598. lit := call.Args[arg]
  599. report.Report(pass, lit, fmt.Sprintf("should use constant http.%s instead of numeric literal %d", s, n),
  600. report.FilterGenerated(),
  601. report.Fixes(edit.Fix(fmt.Sprintf("use http.%s instead of %d", s, n), edit.ReplaceWithString(lit, "http."+s))))
  602. }
  603. code.Preorder(pass, fn, (*ast.CallExpr)(nil))
  604. return nil, nil
  605. }
  606. func CheckDefaultCaseOrder(pass *analysis.Pass) (interface{}, error) {
  607. fn := func(node ast.Node) {
  608. stmt := node.(*ast.SwitchStmt)
  609. list := stmt.Body.List
  610. for i, c := range list {
  611. if c.(*ast.CaseClause).List == nil && i != 0 && i != len(list)-1 {
  612. report.Report(pass, c, "default case should be first or last in switch statement", report.FilterGenerated())
  613. break
  614. }
  615. }
  616. }
  617. code.Preorder(pass, fn, (*ast.SwitchStmt)(nil))
  618. return nil, nil
  619. }
  620. var (
  621. checkYodaConditionsQ = pattern.MustParse(`(BinaryExpr left@(TrulyConstantExpression _) tok@(Or "==" "!=") right@(Not (TrulyConstantExpression _)))`)
  622. checkYodaConditionsR = pattern.MustParse(`(BinaryExpr right tok left)`)
  623. )
  624. func CheckYodaConditions(pass *analysis.Pass) (interface{}, error) {
  625. fn := func(node ast.Node) {
  626. if _, edits, ok := code.MatchAndEdit(pass, checkYodaConditionsQ, checkYodaConditionsR, node); ok {
  627. report.Report(pass, node, "don't use Yoda conditions",
  628. report.FilterGenerated(),
  629. report.Fixes(edit.Fix("un-Yoda-fy", edits...)))
  630. }
  631. }
  632. code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
  633. return nil, nil
  634. }
  635. func CheckInvisibleCharacters(pass *analysis.Pass) (interface{}, error) {
  636. fn := func(node ast.Node) {
  637. lit := node.(*ast.BasicLit)
  638. if lit.Kind != token.STRING {
  639. return
  640. }
  641. type invalid struct {
  642. r rune
  643. off int
  644. }
  645. var invalids []invalid
  646. hasFormat := false
  647. hasControl := false
  648. prev := rune(-1)
  649. const zwj = '\u200d'
  650. for off, r := range lit.Value {
  651. if unicode.Is(unicode.Cf, r) {
  652. // Don't flag joined emojis. These are multiple emojis joined with ZWJ, which some platform render as single composite emojis.
  653. // For the purpose of this check, we consider all symbols, including all symbol modifiers, emoji.
  654. if r != zwj || (r == zwj && !unicode.Is(unicode.S, prev)) {
  655. invalids = append(invalids, invalid{r, off})
  656. hasFormat = true
  657. }
  658. } else if unicode.Is(unicode.Cc, r) && r != '\n' && r != '\t' && r != '\r' {
  659. invalids = append(invalids, invalid{r, off})
  660. hasControl = true
  661. }
  662. prev = r
  663. }
  664. switch len(invalids) {
  665. case 0:
  666. return
  667. case 1:
  668. var kind string
  669. if hasFormat {
  670. kind = "format"
  671. } else if hasControl {
  672. kind = "control"
  673. } else {
  674. panic("unreachable")
  675. }
  676. r := invalids[0]
  677. msg := fmt.Sprintf("string literal contains the Unicode %s character %U, consider using the %q escape sequence instead", kind, r.r, r.r)
  678. replacement := strconv.QuoteRune(r.r)
  679. replacement = replacement[1 : len(replacement)-1]
  680. edit := analysis.SuggestedFix{
  681. Message: fmt.Sprintf("replace %s character %U with %q", kind, r.r, r.r),
  682. TextEdits: []analysis.TextEdit{{
  683. Pos: lit.Pos() + token.Pos(r.off),
  684. End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
  685. NewText: []byte(replacement),
  686. }},
  687. }
  688. delete := analysis.SuggestedFix{
  689. Message: fmt.Sprintf("delete %s character %U", kind, r.r),
  690. TextEdits: []analysis.TextEdit{{
  691. Pos: lit.Pos() + token.Pos(r.off),
  692. End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
  693. }},
  694. }
  695. report.Report(pass, lit, msg, report.Fixes(edit, delete))
  696. default:
  697. var kind string
  698. if hasFormat && hasControl {
  699. kind = "format and control"
  700. } else if hasFormat {
  701. kind = "format"
  702. } else if hasControl {
  703. kind = "control"
  704. } else {
  705. panic("unreachable")
  706. }
  707. msg := fmt.Sprintf("string literal contains Unicode %s characters, consider using escape sequences instead", kind)
  708. var edits []analysis.TextEdit
  709. var deletions []analysis.TextEdit
  710. for _, r := range invalids {
  711. replacement := strconv.QuoteRune(r.r)
  712. replacement = replacement[1 : len(replacement)-1]
  713. edits = append(edits, analysis.TextEdit{
  714. Pos: lit.Pos() + token.Pos(r.off),
  715. End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
  716. NewText: []byte(replacement),
  717. })
  718. deletions = append(deletions, analysis.TextEdit{
  719. Pos: lit.Pos() + token.Pos(r.off),
  720. End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
  721. })
  722. }
  723. edit := analysis.SuggestedFix{
  724. Message: fmt.Sprintf("replace all %s characters with escape sequences", kind),
  725. TextEdits: edits,
  726. }
  727. delete := analysis.SuggestedFix{
  728. Message: fmt.Sprintf("delete all %s characters", kind),
  729. TextEdits: deletions,
  730. }
  731. report.Report(pass, lit, msg, report.Fixes(edit, delete))
  732. }
  733. }
  734. code.Preorder(pass, fn, (*ast.BasicLit)(nil))
  735. return nil, nil
  736. }
  737. func CheckExportedFunctionDocs(pass *analysis.Pass) (interface{}, error) {
  738. fn := func(node ast.Node) {
  739. if code.IsInTest(pass, node) {
  740. return
  741. }
  742. decl := node.(*ast.FuncDecl)
  743. text, ok := docText(decl.Doc)
  744. if !ok {
  745. return
  746. }
  747. if !ast.IsExported(decl.Name.Name) {
  748. return
  749. }
  750. kind := "function"
  751. if decl.Recv != nil {
  752. kind = "method"
  753. var ident *ast.Ident
  754. T := decl.Recv.List[0].Type
  755. if T_, ok := T.(*ast.StarExpr); ok {
  756. T = T_.X
  757. }
  758. switch T := T.(type) {
  759. case *ast.IndexExpr:
  760. ident = T.X.(*ast.Ident)
  761. case *typeparams.IndexListExpr:
  762. ident = T.X.(*ast.Ident)
  763. case *ast.Ident:
  764. ident = T
  765. default:
  766. lint.ExhaustiveTypeSwitch(T)
  767. }
  768. if !ast.IsExported(ident.Name) {
  769. return
  770. }
  771. }
  772. prefix := decl.Name.Name + " "
  773. if !strings.HasPrefix(text, prefix) {
  774. report.Report(pass, decl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, decl.Name.Name, prefix), report.FilterGenerated())
  775. }
  776. }
  777. code.Preorder(pass, fn, (*ast.FuncDecl)(nil))
  778. return nil, nil
  779. }
  780. func CheckExportedTypeDocs(pass *analysis.Pass) (interface{}, error) {
  781. var genDecl *ast.GenDecl
  782. fn := func(node ast.Node, push bool) bool {
  783. if !push {
  784. genDecl = nil
  785. return false
  786. }
  787. if code.IsInTest(pass, node) {
  788. return false
  789. }
  790. switch node := node.(type) {
  791. case *ast.GenDecl:
  792. if node.Tok == token.IMPORT {
  793. return false
  794. }
  795. genDecl = node
  796. return true
  797. case *ast.TypeSpec:
  798. if !ast.IsExported(node.Name.Name) {
  799. return false
  800. }
  801. doc := node.Doc
  802. text, ok := docText(doc)
  803. if !ok {
  804. if len(genDecl.Specs) != 1 {
  805. // more than one spec in the GenDecl, don't validate the
  806. // docstring
  807. return false
  808. }
  809. if genDecl.Lparen.IsValid() {
  810. // 'type ( T )' is weird, don't guess the user's intention
  811. return false
  812. }
  813. doc = genDecl.Doc
  814. text, ok = docText(doc)
  815. if !ok {
  816. return false
  817. }
  818. }
  819. // Check comment before we strip articles in case the type's name is an article.
  820. if strings.HasPrefix(text, node.Name.Name+" ") {
  821. return false
  822. }
  823. s := text
  824. articles := [...]string{"A", "An", "The"}
  825. for _, a := range articles {
  826. if strings.HasPrefix(s, a+" ") {
  827. s = s[len(a)+1:]
  828. break
  829. }
  830. }
  831. if !strings.HasPrefix(s, node.Name.Name+" ") {
  832. report.Report(pass, doc, fmt.Sprintf(`comment on exported type %s should be of the form "%s ..." (with optional leading article)`, node.Name.Name, node.Name.Name), report.FilterGenerated())
  833. }
  834. return false
  835. case *ast.FuncLit, *ast.FuncDecl:
  836. return false
  837. default:
  838. lint.ExhaustiveTypeSwitch(node)
  839. return false
  840. }
  841. }
  842. pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.TypeSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
  843. return nil, nil
  844. }
  845. func CheckExportedVarDocs(pass *analysis.Pass) (interface{}, error) {
  846. var genDecl *ast.GenDecl
  847. fn := func(node ast.Node, push bool) bool {
  848. if !push {
  849. genDecl = nil
  850. return false
  851. }
  852. if code.IsInTest(pass, node) {
  853. return false
  854. }
  855. switch node := node.(type) {
  856. case *ast.GenDecl:
  857. if node.Tok == token.IMPORT {
  858. return false
  859. }
  860. genDecl = node
  861. return true
  862. case *ast.ValueSpec:
  863. if genDecl.Lparen.IsValid() || len(node.Names) > 1 {
  864. // Don't try to guess the user's intention
  865. return false
  866. }
  867. name := node.Names[0].Name
  868. if !ast.IsExported(name) {
  869. return false
  870. }
  871. text, ok := docText(genDecl.Doc)
  872. if !ok {
  873. return false
  874. }
  875. prefix := name + " "
  876. if !strings.HasPrefix(text, prefix) {
  877. kind := "var"
  878. if genDecl.Tok == token.CONST {
  879. kind = "const"
  880. }
  881. report.Report(pass, genDecl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix), report.FilterGenerated())
  882. }
  883. return false
  884. case *ast.FuncLit, *ast.FuncDecl:
  885. return false
  886. default:
  887. lint.ExhaustiveTypeSwitch(node)
  888. return false
  889. }
  890. }
  891. pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.ValueSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
  892. return nil, nil
  893. }