github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/internal/checker/checker_test.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package checker_test 6 7 import ( 8 "fmt" 9 "go/ast" 10 "io/ioutil" 11 "path/filepath" 12 "testing" 13 14 "golang.org/x/tools/go/analysis" 15 "golang.org/x/tools/go/analysis/analysistest" 16 "golang.org/x/tools/go/analysis/internal/checker" 17 "golang.org/x/tools/go/analysis/passes/inspect" 18 "golang.org/x/tools/go/ast/inspector" 19 "golang.org/x/tools/internal/testenv" 20 ) 21 22 func TestApplyFixes(t *testing.T) { 23 testenv.NeedsGoPackages(t) 24 25 files := map[string]string{ 26 "rename/test.go": `package rename 27 28 func Foo() { 29 bar := 12 30 _ = bar 31 } 32 33 // the end 34 `} 35 want := `package rename 36 37 func Foo() { 38 baz := 12 39 _ = baz 40 } 41 42 // the end 43 ` 44 45 testdata, cleanup, err := analysistest.WriteFiles(files) 46 if err != nil { 47 t.Fatal(err) 48 } 49 path := filepath.Join(testdata, "src/rename/test.go") 50 checker.Fix = true 51 checker.Run([]string{"file=" + path}, []*analysis.Analyzer{analyzer}) 52 53 contents, err := ioutil.ReadFile(path) 54 if err != nil { 55 t.Fatal(err) 56 } 57 58 got := string(contents) 59 if got != want { 60 t.Errorf("contents of rewritten file\ngot: %s\nwant: %s", got, want) 61 } 62 63 defer cleanup() 64 } 65 66 var analyzer = &analysis.Analyzer{ 67 Name: "rename", 68 Requires: []*analysis.Analyzer{inspect.Analyzer}, 69 Run: run, 70 } 71 72 var other = &analysis.Analyzer{ // like analyzer but with a different Name. 73 Name: "other", 74 Requires: []*analysis.Analyzer{inspect.Analyzer}, 75 Run: run, 76 } 77 78 func run(pass *analysis.Pass) (interface{}, error) { 79 const ( 80 from = "bar" 81 to = "baz" 82 conflict = "conflict" // add conflicting edits to package conflict. 83 duplicate = "duplicate" // add duplicate edits to package conflict. 84 other = "other" // add conflicting edits to package other from different analyzers. 85 ) 86 87 if pass.Analyzer.Name == other { 88 if pass.Pkg.Name() != other { 89 return nil, nil // only apply Analyzer other to packages named other 90 } 91 } 92 93 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 94 nodeFilter := []ast.Node{(*ast.Ident)(nil)} 95 inspect.Preorder(nodeFilter, func(n ast.Node) { 96 ident := n.(*ast.Ident) 97 if ident.Name == from { 98 msg := fmt.Sprintf("renaming %q to %q", from, to) 99 edits := []analysis.TextEdit{ 100 {Pos: ident.Pos(), End: ident.End(), NewText: []byte(to)}, 101 } 102 switch pass.Pkg.Name() { 103 case conflict: 104 edits = append(edits, []analysis.TextEdit{ 105 {Pos: ident.Pos() - 1, End: ident.End(), NewText: []byte(to)}, 106 {Pos: ident.Pos(), End: ident.End() - 1, NewText: []byte(to)}, 107 {Pos: ident.Pos(), End: ident.End(), NewText: []byte("lorem ipsum")}, 108 }...) 109 case duplicate: 110 edits = append(edits, edits...) 111 case other: 112 if pass.Analyzer.Name == other { 113 edits[0].Pos = edits[0].Pos + 1 // shift by one to mismatch analyzer and other 114 } 115 } 116 pass.Report(analysis.Diagnostic{ 117 Pos: ident.Pos(), 118 End: ident.End(), 119 Message: msg, 120 SuggestedFixes: []analysis.SuggestedFix{{Message: msg, TextEdits: edits}}}) 121 } 122 }) 123 124 return nil, nil 125 } 126 127 func TestRunDespiteErrors(t *testing.T) { 128 testenv.NeedsGoPackages(t) 129 130 files := map[string]string{ 131 "rderr/test.go": `package rderr 132 133 // Foo deliberately has a type error 134 func Foo(s string) int { 135 return s + 1 136 } 137 `} 138 139 testdata, cleanup, err := analysistest.WriteFiles(files) 140 if err != nil { 141 t.Fatal(err) 142 } 143 path := filepath.Join(testdata, "src/rderr/test.go") 144 145 // A no-op analyzer that should finish regardless of 146 // parse or type errors in the code. 147 noop := &analysis.Analyzer{ 148 Name: "noop", 149 Requires: []*analysis.Analyzer{inspect.Analyzer}, 150 Run: func(pass *analysis.Pass) (interface{}, error) { 151 return nil, nil 152 }, 153 RunDespiteErrors: true, 154 } 155 156 // A no-op analyzer that should finish regardless of 157 // parse or type errors in the code. 158 noopWithFact := &analysis.Analyzer{ 159 Name: "noopfact", 160 Requires: []*analysis.Analyzer{inspect.Analyzer}, 161 Run: func(pass *analysis.Pass) (interface{}, error) { 162 return nil, nil 163 }, 164 RunDespiteErrors: true, 165 FactTypes: []analysis.Fact{&EmptyFact{}}, 166 } 167 168 for _, test := range []struct { 169 name string 170 pattern []string 171 analyzers []*analysis.Analyzer 172 code int 173 }{ 174 // parse/type errors 175 {name: "skip-error", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{analyzer}, code: 1}, 176 // RunDespiteErrors allows a driver to run an Analyzer even after parse/type errors. 177 // 178 // The noop analyzer doesn't use facts, so the driver loads only the root 179 // package from source. For the rest, it asks 'go list' for export data, 180 // which fails because the compiler encounters the type error. Since the 181 // errors come from 'go list', the driver doesn't run the analyzer. 182 {name: "despite-error", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{noop}, code: 1}, 183 // The noopfact analyzer does use facts, so the driver loads source for 184 // all dependencies, does type checking itself, recognizes the error as a 185 // type error, and runs the analyzer. 186 {name: "despite-error-fact", pattern: []string{"file=" + path}, analyzers: []*analysis.Analyzer{noopWithFact}, code: 0}, 187 // combination of parse/type errors and no errors 188 {name: "despite-error-and-no-error", pattern: []string{"file=" + path, "sort"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 1}, 189 // non-existing package error 190 {name: "no-package", pattern: []string{"xyz"}, analyzers: []*analysis.Analyzer{analyzer}, code: 1}, 191 {name: "no-package-despite-error", pattern: []string{"abc"}, analyzers: []*analysis.Analyzer{noop}, code: 1}, 192 {name: "no-multi-package-despite-error", pattern: []string{"xyz", "abc"}, analyzers: []*analysis.Analyzer{noop}, code: 1}, 193 // combination of type/parsing and different errors 194 {name: "different-errors", pattern: []string{"file=" + path, "xyz"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 1}, 195 // non existing dir error 196 {name: "no-match-dir", pattern: []string{"file=non/existing/dir"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 1}, 197 // no errors 198 {name: "no-errors", pattern: []string{"sort"}, analyzers: []*analysis.Analyzer{analyzer, noop}, code: 0}, 199 } { 200 if test.name == "despite-error" && testenv.Go1Point() < 20 { 201 // The behavior in the comment on the despite-error test only occurs for Go 1.20+. 202 continue 203 } 204 if got := checker.Run(test.pattern, test.analyzers); got != test.code { 205 t.Errorf("got incorrect exit code %d for test %s; want %d", got, test.name, test.code) 206 } 207 } 208 209 defer cleanup() 210 } 211 212 type EmptyFact struct{} 213 214 func (f *EmptyFact) AFact() {}