Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / honnef.co / go / tools@v0.0.1-2020.1.5 / unused / unused_test.go
1 package unused
2
3 import (
4         "fmt"
5         "go/parser"
6         "go/token"
7         "go/types"
8         "os"
9         "sort"
10         "strconv"
11         "strings"
12         "testing"
13         "text/scanner"
14
15         "golang.org/x/tools/go/analysis"
16         "golang.org/x/tools/go/analysis/analysistest"
17         "golang.org/x/tools/go/packages"
18         "honnef.co/go/tools/lint"
19 )
20
21 // parseExpectations parses the content of a "// want ..." comment
22 // and returns the expectations, a mixture of diagnostics ("rx") and
23 // facts (name:"rx").
24 func parseExpectations(text string) ([]string, error) {
25         var scanErr string
26         sc := new(scanner.Scanner).Init(strings.NewReader(text))
27         sc.Error = func(s *scanner.Scanner, msg string) {
28                 scanErr = msg // e.g. bad string escape
29         }
30         sc.Mode = scanner.ScanIdents | scanner.ScanStrings | scanner.ScanRawStrings
31
32         scanRegexp := func(tok rune) (string, error) {
33                 if tok != scanner.String && tok != scanner.RawString {
34                         return "", fmt.Errorf("got %s, want regular expression",
35                                 scanner.TokenString(tok))
36                 }
37                 pattern, _ := strconv.Unquote(sc.TokenText()) // can't fail
38                 return pattern, nil
39         }
40
41         var expects []string
42         for {
43                 tok := sc.Scan()
44                 switch tok {
45                 case scanner.String, scanner.RawString:
46                         rx, err := scanRegexp(tok)
47                         if err != nil {
48                                 return nil, err
49                         }
50                         expects = append(expects, rx)
51
52                 case scanner.EOF:
53                         if scanErr != "" {
54                                 return nil, fmt.Errorf("%s", scanErr)
55                         }
56                         return expects, nil
57
58                 default:
59                         return nil, fmt.Errorf("unexpected %s", scanner.TokenString(tok))
60                 }
61         }
62 }
63
64 func check(t *testing.T, fset *token.FileSet, diagnostics []types.Object) {
65         type key struct {
66                 file string
67                 line int
68         }
69
70         files := map[string]struct{}{}
71         for _, d := range diagnostics {
72                 files[fset.Position(d.Pos()).Filename] = struct{}{}
73         }
74
75         want := make(map[key][]string)
76
77         // processComment parses expectations out of comments.
78         processComment := func(filename string, linenum int, text string) {
79                 text = strings.TrimSpace(text)
80
81                 // Any comment starting with "want" is treated
82                 // as an expectation, even without following whitespace.
83                 if rest := strings.TrimPrefix(text, "want"); rest != text {
84                         expects, err := parseExpectations(rest)
85                         if err != nil {
86                                 t.Errorf("%s:%d: in 'want' comment: %s", filename, linenum, err)
87                                 return
88                         }
89                         if expects != nil {
90                                 want[key{filename, linenum}] = expects
91                         }
92                 }
93         }
94
95         // Extract 'want' comments from Go files.
96         fset2 := token.NewFileSet()
97         for f := range files {
98                 af, err := parser.ParseFile(fset2, f, nil, parser.ParseComments)
99                 if err != nil {
100                         t.Fatal(err)
101                 }
102                 for _, cgroup := range af.Comments {
103                         for _, c := range cgroup.List {
104
105                                 text := strings.TrimPrefix(c.Text, "//")
106                                 if text == c.Text {
107                                         continue // not a //-comment
108                                 }
109
110                                 // Hack: treat a comment of the form "//...// want..."
111                                 // as if it starts at 'want'.
112                                 // This allows us to add comments on comments,
113                                 // as required when testing the buildtag analyzer.
114                                 if i := strings.Index(text, "// want"); i >= 0 {
115                                         text = text[i+len("// "):]
116                                 }
117
118                                 // It's tempting to compute the filename
119                                 // once outside the loop, but it's
120                                 // incorrect because it can change due
121                                 // to //line directives.
122                                 posn := fset2.Position(c.Pos())
123                                 processComment(posn.Filename, posn.Line, text)
124                         }
125                 }
126         }
127
128         checkMessage := func(posn token.Position, name, message string) {
129                 k := key{posn.Filename, posn.Line}
130                 expects := want[k]
131                 var unmatched []string
132                 for i, exp := range expects {
133                         if exp == message {
134                                 // matched: remove the expectation.
135                                 expects[i] = expects[len(expects)-1]
136                                 expects = expects[:len(expects)-1]
137                                 want[k] = expects
138                                 return
139                         }
140                         unmatched = append(unmatched, fmt.Sprintf("%q", exp))
141                 }
142                 if unmatched == nil {
143                         t.Errorf("%v: unexpected: %v", posn, message)
144                 } else {
145                         t.Errorf("%v: %q does not match pattern %s",
146                                 posn, message, strings.Join(unmatched, " or "))
147                 }
148         }
149
150         // Check the diagnostics match expectations.
151         for _, f := range diagnostics {
152                 posn := fset.Position(f.Pos())
153                 checkMessage(posn, "", f.Name())
154         }
155
156         // Reject surplus expectations.
157         //
158         // Sometimes an Analyzer reports two similar diagnostics on a
159         // line with only one expectation. The reader may be confused by
160         // the error message.
161         // TODO(adonovan): print a better error:
162         // "got 2 diagnostics here; each one needs its own expectation".
163         var surplus []string
164         for key, expects := range want {
165                 for _, exp := range expects {
166                         err := fmt.Sprintf("%s:%d: no diagnostic was reported matching %q", key.file, key.line, exp)
167                         surplus = append(surplus, err)
168                 }
169         }
170         sort.Strings(surplus)
171         for _, err := range surplus {
172                 t.Errorf("%s", err)
173         }
174 }
175
176 func TestAll(t *testing.T) {
177         c := NewChecker(false)
178         var stats lint.Stats
179         r, err := lint.NewRunner(&stats)
180         if err != nil {
181                 t.Fatal(err)
182         }
183
184         dir := analysistest.TestData()
185         cfg := &packages.Config{
186                 Dir:   dir,
187                 Tests: true,
188                 Env:   append(os.Environ(), "GOPATH="+dir, "GO111MODULE=off", "GOPROXY=off"),
189         }
190         pkgs, err := r.Run(cfg, []string{"./..."}, []*analysis.Analyzer{c.Analyzer()}, true)
191         if err != nil {
192                 t.Fatal(err)
193         }
194
195         res := c.Result()
196         check(t, pkgs[0].Fset, res)
197 }