lint.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954
  1. package quickfix
  2. import (
  3. "fmt"
  4. "go/ast"
  5. "go/constant"
  6. "go/token"
  7. "go/types"
  8. "strings"
  9. "honnef.co/go/tools/analysis/code"
  10. "honnef.co/go/tools/analysis/edit"
  11. "honnef.co/go/tools/analysis/report"
  12. "honnef.co/go/tools/go/ast/astutil"
  13. "honnef.co/go/tools/go/types/typeutil"
  14. "honnef.co/go/tools/pattern"
  15. "golang.org/x/tools/go/analysis"
  16. )
  17. func negateDeMorgan(expr ast.Expr, recursive bool) ast.Expr {
  18. switch expr := expr.(type) {
  19. case *ast.BinaryExpr:
  20. var out ast.BinaryExpr
  21. switch expr.Op {
  22. case token.EQL:
  23. out.X = expr.X
  24. out.Op = token.NEQ
  25. out.Y = expr.Y
  26. case token.LSS:
  27. out.X = expr.X
  28. out.Op = token.GEQ
  29. out.Y = expr.Y
  30. case token.GTR:
  31. out.X = expr.X
  32. out.Op = token.LEQ
  33. out.Y = expr.Y
  34. case token.NEQ:
  35. out.X = expr.X
  36. out.Op = token.EQL
  37. out.Y = expr.Y
  38. case token.LEQ:
  39. out.X = expr.X
  40. out.Op = token.GTR
  41. out.Y = expr.Y
  42. case token.GEQ:
  43. out.X = expr.X
  44. out.Op = token.LSS
  45. out.Y = expr.Y
  46. case token.LAND:
  47. out.X = negateDeMorgan(expr.X, recursive)
  48. out.Op = token.LOR
  49. out.Y = negateDeMorgan(expr.Y, recursive)
  50. case token.LOR:
  51. out.X = negateDeMorgan(expr.X, recursive)
  52. out.Op = token.LAND
  53. out.Y = negateDeMorgan(expr.Y, recursive)
  54. }
  55. return &out
  56. case *ast.ParenExpr:
  57. if recursive {
  58. return &ast.ParenExpr{
  59. X: negateDeMorgan(expr.X, recursive),
  60. }
  61. } else {
  62. return &ast.UnaryExpr{
  63. Op: token.NOT,
  64. X: expr,
  65. }
  66. }
  67. case *ast.UnaryExpr:
  68. if expr.Op == token.NOT {
  69. return expr.X
  70. } else {
  71. return &ast.UnaryExpr{
  72. Op: token.NOT,
  73. X: expr,
  74. }
  75. }
  76. default:
  77. return &ast.UnaryExpr{
  78. Op: token.NOT,
  79. X: expr,
  80. }
  81. }
  82. }
  83. func simplifyParentheses(node ast.Expr) ast.Expr {
  84. var changed bool
  85. // XXX accept list of ops to operate on
  86. // XXX copy AST node, don't modify in place
  87. post := func(c *astutil.Cursor) bool {
  88. out := c.Node()
  89. if paren, ok := c.Node().(*ast.ParenExpr); ok {
  90. out = paren.X
  91. }
  92. if binop, ok := out.(*ast.BinaryExpr); ok {
  93. if right, ok := binop.Y.(*ast.BinaryExpr); ok && binop.Op == right.Op {
  94. // XXX also check that Op is associative
  95. root := binop
  96. pivot := root.Y.(*ast.BinaryExpr)
  97. root.Y = pivot.X
  98. pivot.X = root
  99. root = pivot
  100. out = root
  101. }
  102. }
  103. if out != c.Node() {
  104. changed = true
  105. c.Replace(out)
  106. }
  107. return true
  108. }
  109. for changed = true; changed; {
  110. changed = false
  111. node = astutil.Apply(node, nil, post).(ast.Expr)
  112. }
  113. return node
  114. }
  115. var demorganQ = pattern.MustParse(`(UnaryExpr "!" expr@(BinaryExpr _ _ _))`)
  116. func CheckDeMorgan(pass *analysis.Pass) (interface{}, error) {
  117. // TODO(dh): support going in the other direction, e.g. turning `!a && !b && !c` into `!(a || b || c)`
  118. // hasFloats reports whether any subexpression is of type float.
  119. hasFloats := func(expr ast.Expr) bool {
  120. found := false
  121. ast.Inspect(expr, func(node ast.Node) bool {
  122. if expr, ok := node.(ast.Expr); ok {
  123. if basic, ok := pass.TypesInfo.TypeOf(expr).Underlying().(*types.Basic); ok {
  124. if (basic.Info() & types.IsFloat) != 0 {
  125. found = true
  126. return false
  127. }
  128. }
  129. }
  130. return true
  131. })
  132. return found
  133. }
  134. fn := func(node ast.Node, stack []ast.Node) {
  135. matcher, ok := code.Match(pass, demorganQ, node)
  136. if !ok {
  137. return
  138. }
  139. expr := matcher.State["expr"].(ast.Expr)
  140. // be extremely conservative when it comes to floats
  141. if hasFloats(expr) {
  142. return
  143. }
  144. n := negateDeMorgan(expr, false)
  145. nr := negateDeMorgan(expr, true)
  146. nc, ok := astutil.CopyExpr(n)
  147. if !ok {
  148. return
  149. }
  150. ns := simplifyParentheses(nc)
  151. nrc, ok := astutil.CopyExpr(nr)
  152. if !ok {
  153. return
  154. }
  155. nrs := simplifyParentheses(nrc)
  156. var bn, bnr, bns, bnrs string
  157. switch parent := stack[len(stack)-2]; parent.(type) {
  158. case *ast.BinaryExpr, *ast.IfStmt, *ast.ForStmt, *ast.SwitchStmt:
  159. // Always add parentheses for if, for and switch. If
  160. // they're unnecessary, go/printer will strip them when
  161. // the whole file gets formatted.
  162. bn = report.Render(pass, &ast.ParenExpr{X: n})
  163. bnr = report.Render(pass, &ast.ParenExpr{X: nr})
  164. bns = report.Render(pass, &ast.ParenExpr{X: ns})
  165. bnrs = report.Render(pass, &ast.ParenExpr{X: nrs})
  166. default:
  167. // TODO are there other types where we don't want to strip parentheses?
  168. bn = report.Render(pass, n)
  169. bnr = report.Render(pass, nr)
  170. bns = report.Render(pass, ns)
  171. bnrs = report.Render(pass, nrs)
  172. }
  173. // Note: we cannot compare the ASTs directly, because
  174. // simplifyParentheses might have rebalanced trees without
  175. // affecting the rendered form.
  176. var fixes []analysis.SuggestedFix
  177. fixes = append(fixes, edit.Fix("Apply De Morgan's law", edit.ReplaceWithString(node, bn)))
  178. if bn != bns {
  179. fixes = append(fixes, edit.Fix("Apply De Morgan's law & simplify", edit.ReplaceWithString(node, bns)))
  180. }
  181. if bn != bnr {
  182. fixes = append(fixes, edit.Fix("Apply De Morgan's law recursively", edit.ReplaceWithString(node, bnr)))
  183. if bnr != bnrs {
  184. fixes = append(fixes, edit.Fix("Apply De Morgan's law recursively & simplify", edit.ReplaceWithString(node, bnrs)))
  185. }
  186. }
  187. report.Report(pass, node, "could apply De Morgan's law", report.Fixes(fixes...))
  188. }
  189. code.PreorderStack(pass, fn, (*ast.UnaryExpr)(nil))
  190. return nil, nil
  191. }
  192. func findSwitchPairs(pass *analysis.Pass, expr ast.Expr, pairs *[]*ast.BinaryExpr) (OUT bool) {
  193. binexpr, ok := astutil.Unparen(expr).(*ast.BinaryExpr)
  194. if !ok {
  195. return false
  196. }
  197. switch binexpr.Op {
  198. case token.EQL:
  199. if code.MayHaveSideEffects(pass, binexpr.X, nil) || code.MayHaveSideEffects(pass, binexpr.Y, nil) {
  200. return false
  201. }
  202. // syntactic identity should suffice. we do not allow side
  203. // effects in the case clauses, so there should be no way for
  204. // values to change.
  205. if len(*pairs) > 0 && !astutil.Equal(binexpr.X, (*pairs)[0].X) {
  206. return false
  207. }
  208. *pairs = append(*pairs, binexpr)
  209. return true
  210. case token.LOR:
  211. return findSwitchPairs(pass, binexpr.X, pairs) && findSwitchPairs(pass, binexpr.Y, pairs)
  212. default:
  213. return false
  214. }
  215. }
  216. func CheckTaglessSwitch(pass *analysis.Pass) (interface{}, error) {
  217. fn := func(node ast.Node) {
  218. swtch := node.(*ast.SwitchStmt)
  219. if swtch.Tag != nil || len(swtch.Body.List) == 0 {
  220. return
  221. }
  222. pairs := make([][]*ast.BinaryExpr, len(swtch.Body.List))
  223. for i, stmt := range swtch.Body.List {
  224. stmt := stmt.(*ast.CaseClause)
  225. for _, cond := range stmt.List {
  226. if !findSwitchPairs(pass, cond, &pairs[i]) {
  227. return
  228. }
  229. }
  230. }
  231. var x ast.Expr
  232. for _, pair := range pairs {
  233. if len(pair) == 0 {
  234. continue
  235. }
  236. if x == nil {
  237. x = pair[0].X
  238. } else {
  239. if !astutil.Equal(x, pair[0].X) {
  240. return
  241. }
  242. }
  243. }
  244. if x == nil {
  245. // the switch only has a default case
  246. if len(pairs) > 1 {
  247. panic("found more than one case clause with no pairs")
  248. }
  249. return
  250. }
  251. edits := make([]analysis.TextEdit, 0, len(swtch.Body.List)+1)
  252. for i, stmt := range swtch.Body.List {
  253. stmt := stmt.(*ast.CaseClause)
  254. if stmt.List == nil {
  255. continue
  256. }
  257. var values []string
  258. for _, binexpr := range pairs[i] {
  259. y := binexpr.Y
  260. if p, ok := y.(*ast.ParenExpr); ok {
  261. y = p.X
  262. }
  263. values = append(values, report.Render(pass, y))
  264. }
  265. edits = append(edits, edit.ReplaceWithString(edit.Range{stmt.List[0].Pos(), stmt.Colon}, strings.Join(values, ", ")))
  266. }
  267. pos := swtch.Switch + token.Pos(len("switch"))
  268. edits = append(edits, edit.ReplaceWithString(edit.Range{pos, pos}, " "+report.Render(pass, x)))
  269. report.Report(pass, swtch, fmt.Sprintf("could use tagged switch on %s", report.Render(pass, x)),
  270. report.Fixes(edit.Fix("Replace with tagged switch", edits...)))
  271. }
  272. code.Preorder(pass, fn, (*ast.SwitchStmt)(nil))
  273. return nil, nil
  274. }
  275. func CheckIfElseToSwitch(pass *analysis.Pass) (interface{}, error) {
  276. fn := func(node ast.Node, stack []ast.Node) {
  277. if _, ok := stack[len(stack)-2].(*ast.IfStmt); ok {
  278. // this if statement is part of an if-else chain
  279. return
  280. }
  281. ifstmt := node.(*ast.IfStmt)
  282. m := map[ast.Expr][]*ast.BinaryExpr{}
  283. for item := ifstmt; item != nil; {
  284. if item.Init != nil {
  285. return
  286. }
  287. if item.Body == nil {
  288. return
  289. }
  290. skip := false
  291. ast.Inspect(item.Body, func(node ast.Node) bool {
  292. if branch, ok := node.(*ast.BranchStmt); ok && branch.Tok != token.GOTO {
  293. skip = true
  294. return false
  295. }
  296. return true
  297. })
  298. if skip {
  299. return
  300. }
  301. var pairs []*ast.BinaryExpr
  302. if !findSwitchPairs(pass, item.Cond, &pairs) {
  303. return
  304. }
  305. m[item.Cond] = pairs
  306. switch els := item.Else.(type) {
  307. case *ast.IfStmt:
  308. item = els
  309. case *ast.BlockStmt, nil:
  310. item = nil
  311. default:
  312. panic(fmt.Sprintf("unreachable: %T", els))
  313. }
  314. }
  315. var x ast.Expr
  316. for _, pair := range m {
  317. if len(pair) == 0 {
  318. continue
  319. }
  320. if x == nil {
  321. x = pair[0].X
  322. } else {
  323. if !astutil.Equal(x, pair[0].X) {
  324. return
  325. }
  326. }
  327. }
  328. if x == nil {
  329. // shouldn't happen
  330. return
  331. }
  332. // We require at least two 'if' to make this suggestion, to
  333. // avoid clutter in the editor.
  334. if len(m) < 2 {
  335. return
  336. }
  337. var edits []analysis.TextEdit
  338. for item := ifstmt; item != nil; {
  339. var end token.Pos
  340. if item.Else != nil {
  341. end = item.Else.Pos()
  342. } else {
  343. // delete up to but not including the closing brace.
  344. end = item.Body.Rbrace
  345. }
  346. var conds []string
  347. for _, cond := range m[item.Cond] {
  348. y := cond.Y
  349. if p, ok := y.(*ast.ParenExpr); ok {
  350. y = p.X
  351. }
  352. conds = append(conds, report.Render(pass, y))
  353. }
  354. sconds := strings.Join(conds, ", ")
  355. edits = append(edits,
  356. edit.ReplaceWithString(edit.Range{item.If, item.Body.Lbrace + 1}, "case "+sconds+":"),
  357. edit.Delete(edit.Range{item.Body.Rbrace, end}))
  358. switch els := item.Else.(type) {
  359. case *ast.IfStmt:
  360. item = els
  361. case *ast.BlockStmt:
  362. edits = append(edits, edit.ReplaceWithString(edit.Range{els.Lbrace, els.Lbrace + 1}, "default:"))
  363. item = nil
  364. case nil:
  365. item = nil
  366. default:
  367. panic(fmt.Sprintf("unreachable: %T", els))
  368. }
  369. }
  370. // FIXME this forces the first case to begin in column 0. try to fix the indentation
  371. edits = append(edits, edit.ReplaceWithString(edit.Range{ifstmt.If, ifstmt.If}, fmt.Sprintf("switch %s {\n", report.Render(pass, x))))
  372. report.Report(pass, ifstmt, fmt.Sprintf("could use tagged switch on %s", report.Render(pass, x)),
  373. report.Fixes(edit.Fix("Replace with tagged switch", edits...)),
  374. report.ShortRange())
  375. }
  376. code.PreorderStack(pass, fn, (*ast.IfStmt)(nil))
  377. return nil, nil
  378. }
  379. var stringsReplaceAllQ = pattern.MustParse(`(Or
  380. (CallExpr fn@(Function "strings.Replace") [_ _ _ lit@(IntegerLiteral "-1")])
  381. (CallExpr fn@(Function "strings.SplitN") [_ _ lit@(IntegerLiteral "-1")])
  382. (CallExpr fn@(Function "strings.SplitAfterN") [_ _ lit@(IntegerLiteral "-1")])
  383. (CallExpr fn@(Function "bytes.Replace") [_ _ _ lit@(IntegerLiteral "-1")])
  384. (CallExpr fn@(Function "bytes.SplitN") [_ _ lit@(IntegerLiteral "-1")])
  385. (CallExpr fn@(Function "bytes.SplitAfterN") [_ _ lit@(IntegerLiteral "-1")]))`)
  386. func CheckStringsReplaceAll(pass *analysis.Pass) (interface{}, error) {
  387. // XXX respect minimum Go version
  388. // FIXME(dh): create proper suggested fix for renamed import
  389. fn := func(node ast.Node) {
  390. matcher, ok := code.Match(pass, stringsReplaceAllQ, node)
  391. if !ok {
  392. return
  393. }
  394. var replacement string
  395. switch typeutil.FuncName(matcher.State["fn"].(*types.Func)) {
  396. case "strings.Replace":
  397. replacement = "strings.ReplaceAll"
  398. case "strings.SplitN":
  399. replacement = "strings.Split"
  400. case "strings.SplitAfterN":
  401. replacement = "strings.SplitAfter"
  402. case "bytes.Replace":
  403. replacement = "bytes.ReplaceAll"
  404. case "bytes.SplitN":
  405. replacement = "bytes.Split"
  406. case "bytes.SplitAfterN":
  407. replacement = "bytes.SplitAfter"
  408. default:
  409. panic("unreachable")
  410. }
  411. call := node.(*ast.CallExpr)
  412. report.Report(pass, call.Fun, fmt.Sprintf("could use %s instead", replacement),
  413. report.Fixes(edit.Fix(fmt.Sprintf("Use %s instead", replacement),
  414. edit.ReplaceWithString(call.Fun, replacement),
  415. edit.Delete(matcher.State["lit"].(ast.Node)))))
  416. }
  417. code.Preorder(pass, fn, (*ast.CallExpr)(nil))
  418. return nil, nil
  419. }
  420. var mathPowQ = pattern.MustParse(`(CallExpr (Function "math.Pow") [x (IntegerLiteral n)])`)
  421. func CheckMathPow(pass *analysis.Pass) (interface{}, error) {
  422. fn := func(node ast.Node) {
  423. matcher, ok := code.Match(pass, mathPowQ, node)
  424. if !ok {
  425. return
  426. }
  427. x := matcher.State["x"].(ast.Expr)
  428. if code.MayHaveSideEffects(pass, x, nil) {
  429. return
  430. }
  431. n, ok := constant.Int64Val(constant.ToInt(matcher.State["n"].(types.TypeAndValue).Value))
  432. if !ok {
  433. return
  434. }
  435. needConversion := false
  436. if T, ok := pass.TypesInfo.Types[x]; ok && T.Value != nil {
  437. info := types.Info{
  438. Types: map[ast.Expr]types.TypeAndValue{},
  439. }
  440. // determine if the constant expression would have type float64 if used on its own
  441. if err := types.CheckExpr(pass.Fset, pass.Pkg, x.Pos(), x, &info); err != nil {
  442. // This should not happen
  443. return
  444. }
  445. if T, ok := info.Types[x].Type.(*types.Basic); ok {
  446. if T.Kind() != types.UntypedFloat && T.Kind() != types.Float64 {
  447. needConversion = true
  448. }
  449. } else {
  450. needConversion = true
  451. }
  452. }
  453. var replacement ast.Expr
  454. switch n {
  455. case 0:
  456. replacement = &ast.BasicLit{
  457. Kind: token.FLOAT,
  458. Value: "1.0",
  459. }
  460. case 1:
  461. replacement = x
  462. case 2, 3:
  463. r := &ast.BinaryExpr{
  464. X: x,
  465. Op: token.MUL,
  466. Y: x,
  467. }
  468. for i := 3; i <= int(n); i++ {
  469. r = &ast.BinaryExpr{
  470. X: r,
  471. Op: token.MUL,
  472. Y: x,
  473. }
  474. }
  475. rc, ok := astutil.CopyExpr(r)
  476. if !ok {
  477. return
  478. }
  479. replacement = simplifyParentheses(rc)
  480. default:
  481. return
  482. }
  483. if needConversion && n != 0 {
  484. replacement = &ast.CallExpr{
  485. Fun: &ast.Ident{Name: "float64"},
  486. Args: []ast.Expr{replacement},
  487. }
  488. }
  489. report.Report(pass, node, "could expand call to math.Pow",
  490. report.Fixes(edit.Fix("Expand call to math.Pow", edit.ReplaceWithNode(pass.Fset, node, replacement))))
  491. }
  492. code.Preorder(pass, fn, (*ast.CallExpr)(nil))
  493. return nil, nil
  494. }
  495. var checkForLoopIfBreak = pattern.MustParse(`(ForStmt nil nil nil if@(IfStmt nil cond (BranchStmt "BREAK" nil) nil):_)`)
  496. func CheckForLoopIfBreak(pass *analysis.Pass) (interface{}, error) {
  497. fn := func(node ast.Node) {
  498. m, ok := code.Match(pass, checkForLoopIfBreak, node)
  499. if !ok {
  500. return
  501. }
  502. pos := node.Pos() + token.Pos(len("for"))
  503. r := negateDeMorgan(m.State["cond"].(ast.Expr), false)
  504. // FIXME(dh): we're leaving behind an empty line when we
  505. // delete the old if statement. However, we can't just delete
  506. // an additional character, in case there closing curly brace
  507. // is followed by a comment, or Windows newlines.
  508. report.Report(pass, m.State["if"].(ast.Node), "could lift into loop condition",
  509. report.Fixes(edit.Fix("Lift into loop condition",
  510. edit.ReplaceWithString(edit.Range{pos, pos}, " "+report.Render(pass, r)),
  511. edit.Delete(m.State["if"].(ast.Node)))))
  512. }
  513. code.Preorder(pass, fn, (*ast.ForStmt)(nil))
  514. return nil, nil
  515. }
  516. var checkConditionalAssignmentQ = pattern.MustParse(`(AssignStmt x@(Object _) ":=" assign@(Builtin b@(Or "true" "false")))`)
  517. var checkConditionalAssignmentIfQ = pattern.MustParse(`(IfStmt nil cond [(AssignStmt x@(Object _) "=" (Builtin b@(Or "true" "false")))] nil)`)
  518. func CheckConditionalAssignment(pass *analysis.Pass) (interface{}, error) {
  519. fn := func(node ast.Node) {
  520. var body *ast.BlockStmt
  521. switch node := node.(type) {
  522. case *ast.FuncDecl:
  523. body = node.Body
  524. case *ast.FuncLit:
  525. body = node.Body
  526. default:
  527. panic("unreachable")
  528. }
  529. if body == nil {
  530. return
  531. }
  532. stmts := body.List
  533. if len(stmts) < 2 {
  534. return
  535. }
  536. for i, first := range stmts[:len(stmts)-1] {
  537. second := stmts[i+1]
  538. m1, ok := code.Match(pass, checkConditionalAssignmentQ, first)
  539. if !ok {
  540. continue
  541. }
  542. m2, ok := code.Match(pass, checkConditionalAssignmentIfQ, second)
  543. if !ok {
  544. continue
  545. }
  546. if m1.State["x"] != m2.State["x"] {
  547. continue
  548. }
  549. if m1.State["b"] == m2.State["b"] {
  550. continue
  551. }
  552. v := m2.State["cond"].(ast.Expr)
  553. if m1.State["b"] == "true" {
  554. v = &ast.UnaryExpr{
  555. Op: token.NOT,
  556. X: v,
  557. }
  558. }
  559. report.Report(pass, first, "could merge conditional assignment into variable declaration",
  560. report.Fixes(edit.Fix("Merge conditional assignment into variable declaration",
  561. edit.ReplaceWithNode(pass.Fset, m1.State["assign"].(ast.Node), v),
  562. edit.Delete(second))))
  563. }
  564. }
  565. code.Preorder(pass, fn, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil))
  566. return nil, nil
  567. }
  568. func CheckExplicitEmbeddedSelector(pass *analysis.Pass) (interface{}, error) {
  569. type Selector struct {
  570. Node *ast.SelectorExpr
  571. X ast.Expr
  572. Fields []*ast.Ident
  573. }
  574. // extractSelectors extracts uninterrupted sequences of selector expressions.
  575. // For example, for a.b.c().d.e[0].f.g three sequences will be returned: (X=a, X.b.c), (X=a.b.c(), X.d.e), and (X=a.b.c().d.e[0], X.f.g)
  576. //
  577. // It returns nil if the provided selector expression is not the root of a set of sequences.
  578. // For example, for a.b.c, if node is b.c, no selectors will be returned.
  579. extractSelectors := func(expr *ast.SelectorExpr) []Selector {
  580. path, _ := astutil.PathEnclosingInterval(code.File(pass, expr), expr.Pos(), expr.Pos())
  581. for i := len(path) - 1; i >= 0; i-- {
  582. if el, ok := path[i].(*ast.SelectorExpr); ok {
  583. if el != expr {
  584. // this expression is a subset of the entire chain, don't look at it.
  585. return nil
  586. }
  587. break
  588. }
  589. }
  590. inChain := false
  591. var out []Selector
  592. for _, el := range path {
  593. if expr, ok := el.(*ast.SelectorExpr); ok {
  594. if !inChain {
  595. inChain = true
  596. out = append(out, Selector{X: expr.X})
  597. }
  598. sel := &out[len(out)-1]
  599. sel.Fields = append(sel.Fields, expr.Sel)
  600. sel.Node = expr
  601. } else if inChain {
  602. inChain = false
  603. }
  604. }
  605. return out
  606. }
  607. fn := func(node ast.Node) {
  608. expr := node.(*ast.SelectorExpr)
  609. if _, ok := expr.X.(*ast.SelectorExpr); !ok {
  610. // Avoid the expensive call to PathEnclosingInterval for the common 1-level deep selector, which cannot be shortened.
  611. return
  612. }
  613. sels := extractSelectors(expr)
  614. if len(sels) == 0 {
  615. return
  616. }
  617. var edits []analysis.TextEdit
  618. for _, sel := range sels {
  619. fieldLoop:
  620. for base, fields := pass.TypesInfo.TypeOf(sel.X), sel.Fields; len(fields) >= 2; base, fields = pass.TypesInfo.ObjectOf(fields[0]).Type(), fields[1:] {
  621. hop1 := fields[0]
  622. hop2 := fields[1]
  623. // the selector expression might be a qualified identifier, which cannot be simplified
  624. if base == types.Typ[types.Invalid] {
  625. continue fieldLoop
  626. }
  627. // Check if we can skip a field in the chain of selectors.
  628. // We can skip a field 'b' if a.b.c and a.c resolve to the same object and take the same path.
  629. //
  630. // We set addressable to true unconditionally because we've already successfully type-checked the program,
  631. // which means either the selector doesn't need addressability, or it is addressable.
  632. leftObj, leftLeg, _ := types.LookupFieldOrMethod(base, true, pass.Pkg, hop1.Name)
  633. // We can't skip fields that aren't embedded
  634. if !leftObj.(*types.Var).Embedded() {
  635. continue fieldLoop
  636. }
  637. directObj, directPath, _ := types.LookupFieldOrMethod(base, true, pass.Pkg, hop2.Name)
  638. // Fail fast if omitting the embedded field leads to a different object
  639. if directObj != pass.TypesInfo.ObjectOf(hop2) {
  640. continue fieldLoop
  641. }
  642. _, rightLeg, _ := types.LookupFieldOrMethod(leftObj.Type(), true, pass.Pkg, hop2.Name)
  643. // Fail fast if the paths are obviously different
  644. if len(directPath) != len(leftLeg)+len(rightLeg) {
  645. continue fieldLoop
  646. }
  647. // Make sure that omitting the embedded field will take the same path to the final object.
  648. // Multiple paths involving different fields may lead to the same type-checker object, causing different runtime behavior.
  649. for i := range directPath {
  650. if i < len(leftLeg) {
  651. if leftLeg[i] != directPath[i] {
  652. continue fieldLoop
  653. }
  654. } else {
  655. if rightLeg[i-len(leftLeg)] != directPath[i] {
  656. continue fieldLoop
  657. }
  658. }
  659. }
  660. e := edit.Delete(edit.Range{hop1.Pos(), hop2.Pos()})
  661. edits = append(edits, e)
  662. report.Report(pass, hop1, fmt.Sprintf("could remove embedded field %q from selector", hop1.Name),
  663. report.Fixes(edit.Fix(fmt.Sprintf("Remove embedded field %q from selector", hop1.Name), e)))
  664. }
  665. }
  666. // Offer to simplify all selector expressions at once
  667. if len(edits) > 1 {
  668. // Hack to prevent gopls from applying the Unnecessary tag to the diagnostic. It applies the tag when all edits are deletions.
  669. edits = append(edits, edit.ReplaceWithString(edit.Range{node.Pos(), node.Pos()}, ""))
  670. report.Report(pass, node, "could simplify selectors", report.Fixes(edit.Fix("Remove all embedded fields from selector", edits...)))
  671. }
  672. }
  673. code.Preorder(pass, fn, (*ast.SelectorExpr)(nil))
  674. return nil, nil
  675. }
  676. var timeEqualR = pattern.MustParse(`(CallExpr (SelectorExpr lhs (Ident "Equal")) rhs)`)
  677. func CheckTimeEquality(pass *analysis.Pass) (interface{}, error) {
  678. // FIXME(dh): create proper suggested fix for renamed import
  679. fn := func(node ast.Node) {
  680. expr := node.(*ast.BinaryExpr)
  681. if expr.Op != token.EQL {
  682. return
  683. }
  684. if !code.IsOfType(pass, expr.X, "time.Time") || !code.IsOfType(pass, expr.Y, "time.Time") {
  685. return
  686. }
  687. report.Report(pass, node, "probably want to use time.Time.Equal instead",
  688. report.Fixes(edit.Fix("Use time.Time.Equal method",
  689. edit.ReplaceWithPattern(pass.Fset, node, timeEqualR, pattern.State{"lhs": expr.X, "rhs": expr.Y}))))
  690. }
  691. code.Preorder(pass, fn, (*ast.BinaryExpr)(nil))
  692. return nil, nil
  693. }
  694. var byteSlicePrintingQ = pattern.MustParse(`
  695. (Or
  696. (CallExpr
  697. (Function (Or
  698. "fmt.Print"
  699. "fmt.Println"
  700. "fmt.Sprint"
  701. "fmt.Sprintln"
  702. "log.Fatal"
  703. "log.Fatalln"
  704. "log.Panic"
  705. "log.Panicln"
  706. "log.Print"
  707. "log.Println"
  708. "(*log.Logger).Fatal"
  709. "(*log.Logger).Fatalln"
  710. "(*log.Logger).Panic"
  711. "(*log.Logger).Panicln"
  712. "(*log.Logger).Print"
  713. "(*log.Logger).Println")) args)
  714. (CallExpr (Function (Or
  715. "fmt.Fprint"
  716. "fmt.Fprintln")) _:args))`)
  717. var byteSlicePrintingR = pattern.MustParse(`(CallExpr (Ident "string") [arg])`)
  718. func CheckByteSlicePrinting(pass *analysis.Pass) (interface{}, error) {
  719. isStringer := func(T types.Type, ms *types.MethodSet) bool {
  720. sel := ms.Lookup(nil, "String")
  721. if sel == nil {
  722. return false
  723. }
  724. fn, ok := sel.Obj().(*types.Func)
  725. if !ok {
  726. // should be unreachable
  727. return false
  728. }
  729. sig := fn.Type().(*types.Signature)
  730. if sig.Params().Len() != 0 {
  731. return false
  732. }
  733. if sig.Results().Len() != 1 {
  734. return false
  735. }
  736. if !typeutil.IsType(sig.Results().At(0).Type(), "string") {
  737. return false
  738. }
  739. return true
  740. }
  741. fn := func(node ast.Node) {
  742. m, ok := code.Match(pass, byteSlicePrintingQ, node)
  743. if !ok {
  744. return
  745. }
  746. args := m.State["args"].([]ast.Expr)
  747. for _, arg := range args {
  748. T := pass.TypesInfo.TypeOf(arg)
  749. if typeutil.IsType(T.Underlying(), "[]byte") {
  750. ms := types.NewMethodSet(T)
  751. // don't convert arguments that implement fmt.Stringer
  752. if isStringer(T, ms) {
  753. continue
  754. }
  755. fix := edit.Fix("Convert argument to string", edit.ReplaceWithPattern(pass.Fset, arg, byteSlicePrintingR, pattern.State{"arg": arg}))
  756. report.Report(pass, arg, "could convert argument to string", report.Fixes(fix))
  757. }
  758. }
  759. }
  760. code.Preorder(pass, fn, (*ast.CallExpr)(nil))
  761. return nil, nil
  762. }
  763. var (
  764. checkWriteBytesSprintfQ = pattern.MustParse(`
  765. (CallExpr
  766. (SelectorExpr recv (Ident "Write"))
  767. (CallExpr (ArrayType nil (Ident "byte"))
  768. (CallExpr
  769. fn@(Or
  770. (Function "fmt.Sprint")
  771. (Function "fmt.Sprintf")
  772. (Function "fmt.Sprintln"))
  773. args)
  774. ))`)
  775. checkWriteStringSprintfQ = pattern.MustParse(`
  776. (CallExpr
  777. (SelectorExpr recv (Ident "WriteString"))
  778. (CallExpr
  779. fn@(Or
  780. (Function "fmt.Sprint")
  781. (Function "fmt.Sprintf")
  782. (Function "fmt.Sprintln"))
  783. args))`)
  784. writerInterface = types.NewInterfaceType([]*types.Func{
  785. types.NewFunc(token.NoPos, nil, "Write", types.NewSignature(nil,
  786. types.NewTuple(types.NewVar(token.NoPos, nil, "", types.NewSlice(types.Typ[types.Byte]))),
  787. types.NewTuple(
  788. types.NewVar(token.NoPos, nil, "", types.Typ[types.Int]),
  789. types.NewVar(token.NoPos, nil, "", types.Universe.Lookup("error").Type()),
  790. ),
  791. false,
  792. )),
  793. }, nil).Complete()
  794. stringWriterInterface = types.NewInterfaceType([]*types.Func{
  795. types.NewFunc(token.NoPos, nil, "WriteString", types.NewSignature(nil,
  796. types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Universe.Lookup("string").Type())),
  797. types.NewTuple(
  798. types.NewVar(token.NoPos, nil, "", types.Typ[types.Int]),
  799. types.NewVar(token.NoPos, nil, "", types.Universe.Lookup("error").Type()),
  800. ),
  801. false,
  802. )),
  803. }, nil).Complete()
  804. )
  805. func CheckWriteBytesSprintf(pass *analysis.Pass) (interface{}, error) {
  806. fn := func(node ast.Node) {
  807. if m, ok := code.Match(pass, checkWriteBytesSprintfQ, node); ok {
  808. recv := m.State["recv"].(ast.Expr)
  809. recvT := pass.TypesInfo.TypeOf(recv)
  810. if !types.Implements(recvT, writerInterface) {
  811. return
  812. }
  813. name := m.State["fn"].(*types.Func).Name()
  814. newName := "F" + strings.TrimPrefix(name, "S")
  815. msg := fmt.Sprintf("Use fmt.%s(...) instead of Write([]byte(fmt.%s(...)))", newName, name)
  816. args := m.State["args"].([]ast.Expr)
  817. fix := edit.Fix(msg, edit.ReplaceWithNode(pass.Fset, node, &ast.CallExpr{
  818. Fun: &ast.SelectorExpr{
  819. X: ast.NewIdent("fmt"),
  820. Sel: ast.NewIdent(newName),
  821. },
  822. Args: append([]ast.Expr{recv}, args...),
  823. }))
  824. report.Report(pass, node, msg, report.Fixes(fix))
  825. } else if m, ok := code.Match(pass, checkWriteStringSprintfQ, node); ok {
  826. recv := m.State["recv"].(ast.Expr)
  827. recvT := pass.TypesInfo.TypeOf(recv)
  828. if !types.Implements(recvT, stringWriterInterface) {
  829. return
  830. }
  831. // The type needs to implement both StringWriter and Writer.
  832. // If it doesn't implement Writer, then we cannot pass it to fmt.Fprint.
  833. if !types.Implements(recvT, writerInterface) {
  834. return
  835. }
  836. name := m.State["fn"].(*types.Func).Name()
  837. newName := "F" + strings.TrimPrefix(name, "S")
  838. msg := fmt.Sprintf("Use fmt.%s(...) instead of WriteString(fmt.%s(...))", newName, name)
  839. args := m.State["args"].([]ast.Expr)
  840. fix := edit.Fix(msg, edit.ReplaceWithNode(pass.Fset, node, &ast.CallExpr{
  841. Fun: &ast.SelectorExpr{
  842. X: ast.NewIdent("fmt"),
  843. Sel: ast.NewIdent(newName),
  844. },
  845. Args: append([]ast.Expr{recv}, args...),
  846. }))
  847. report.Report(pass, node, msg, report.Fixes(fix))
  848. }
  849. }
  850. code.Preorder(pass, fn, (*ast.CallExpr)(nil))
  851. return nil, nil
  852. }