qf1006.go raw
1 package qf1006
2
3 import (
4 "go/ast"
5 "go/token"
6
7 "honnef.co/go/tools/analysis/code"
8 "honnef.co/go/tools/analysis/edit"
9 "honnef.co/go/tools/analysis/lint"
10 "honnef.co/go/tools/analysis/report"
11 "honnef.co/go/tools/go/ast/astutil"
12 "honnef.co/go/tools/pattern"
13
14 "golang.org/x/tools/go/analysis"
15 "golang.org/x/tools/go/analysis/passes/inspect"
16 )
17
18 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
19 Analyzer: &analysis.Analyzer{
20 Name: "QF1006",
21 Run: run,
22 Requires: []*analysis.Analyzer{inspect.Analyzer},
23 },
24 Doc: &lint.RawDocumentation{
25 Title: `Lift \'if\'+\'break\' into loop condition`,
26 Before: `
27 for {
28 if done {
29 break
30 }
31 ...
32 }`,
33
34 After: `
35 for !done {
36 ...
37 }`,
38 Since: "2021.1",
39 Severity: lint.SeverityHint,
40 },
41 })
42
43 var Analyzer = SCAnalyzer.Analyzer
44
45 var checkForLoopIfBreak = pattern.MustParse(`(ForStmt nil nil nil if@(IfStmt nil cond (BranchStmt "BREAK" nil) nil):_)`)
46
47 func run(pass *analysis.Pass) (interface{}, error) {
48 fn := func(node ast.Node) {
49 m, ok := code.Match(pass, checkForLoopIfBreak, node)
50 if !ok {
51 return
52 }
53
54 pos := node.Pos() + token.Pos(len("for"))
55 r := astutil.NegateDeMorgan(m.State["cond"].(ast.Expr), false)
56
57 // FIXME(dh): we're leaving behind an empty line when we
58 // delete the old if statement. However, we can't just delete
59 // an additional character, in case there closing curly brace
60 // is followed by a comment, or Windows newlines.
61 report.Report(pass, m.State["if"].(ast.Node), "could lift into loop condition",
62 report.Fixes(edit.Fix("Lift into loop condition",
63 edit.ReplaceWithString(edit.Range{pos, pos}, " "+report.Render(pass, r)),
64 edit.Delete(m.State["if"].(ast.Node)))))
65 }
66 code.Preorder(pass, fn, (*ast.ForStmt)(nil))
67 return nil, nil
68 }
69