+++ /dev/null
-package unused
-
-import (
- "fmt"
- "go/parser"
- "go/token"
- "go/types"
- "os"
- "sort"
- "strconv"
- "strings"
- "testing"
- "text/scanner"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/analysistest"
- "golang.org/x/tools/go/packages"
- "honnef.co/go/tools/lint"
-)
-
-// parseExpectations parses the content of a "// want ..." comment
-// and returns the expectations, a mixture of diagnostics ("rx") and
-// facts (name:"rx").
-func parseExpectations(text string) ([]string, error) {
- var scanErr string
- sc := new(scanner.Scanner).Init(strings.NewReader(text))
- sc.Error = func(s *scanner.Scanner, msg string) {
- scanErr = msg // e.g. bad string escape
- }
- sc.Mode = scanner.ScanIdents | scanner.ScanStrings | scanner.ScanRawStrings
-
- scanRegexp := func(tok rune) (string, error) {
- if tok != scanner.String && tok != scanner.RawString {
- return "", fmt.Errorf("got %s, want regular expression",
- scanner.TokenString(tok))
- }
- pattern, _ := strconv.Unquote(sc.TokenText()) // can't fail
- return pattern, nil
- }
-
- var expects []string
- for {
- tok := sc.Scan()
- switch tok {
- case scanner.String, scanner.RawString:
- rx, err := scanRegexp(tok)
- if err != nil {
- return nil, err
- }
- expects = append(expects, rx)
-
- case scanner.EOF:
- if scanErr != "" {
- return nil, fmt.Errorf("%s", scanErr)
- }
- return expects, nil
-
- default:
- return nil, fmt.Errorf("unexpected %s", scanner.TokenString(tok))
- }
- }
-}
-
-func check(t *testing.T, fset *token.FileSet, diagnostics []types.Object) {
- type key struct {
- file string
- line int
- }
-
- files := map[string]struct{}{}
- for _, d := range diagnostics {
- files[fset.Position(d.Pos()).Filename] = struct{}{}
- }
-
- want := make(map[key][]string)
-
- // processComment parses expectations out of comments.
- processComment := func(filename string, linenum int, text string) {
- text = strings.TrimSpace(text)
-
- // Any comment starting with "want" is treated
- // as an expectation, even without following whitespace.
- if rest := strings.TrimPrefix(text, "want"); rest != text {
- expects, err := parseExpectations(rest)
- if err != nil {
- t.Errorf("%s:%d: in 'want' comment: %s", filename, linenum, err)
- return
- }
- if expects != nil {
- want[key{filename, linenum}] = expects
- }
- }
- }
-
- // Extract 'want' comments from Go files.
- fset2 := token.NewFileSet()
- for f := range files {
- af, err := parser.ParseFile(fset2, f, nil, parser.ParseComments)
- if err != nil {
- t.Fatal(err)
- }
- for _, cgroup := range af.Comments {
- for _, c := range cgroup.List {
-
- text := strings.TrimPrefix(c.Text, "//")
- if text == c.Text {
- continue // not a //-comment
- }
-
- // Hack: treat a comment of the form "//...// want..."
- // as if it starts at 'want'.
- // This allows us to add comments on comments,
- // as required when testing the buildtag analyzer.
- if i := strings.Index(text, "// want"); i >= 0 {
- text = text[i+len("// "):]
- }
-
- // It's tempting to compute the filename
- // once outside the loop, but it's
- // incorrect because it can change due
- // to //line directives.
- posn := fset2.Position(c.Pos())
- processComment(posn.Filename, posn.Line, text)
- }
- }
- }
-
- checkMessage := func(posn token.Position, name, message string) {
- k := key{posn.Filename, posn.Line}
- expects := want[k]
- var unmatched []string
- for i, exp := range expects {
- if exp == message {
- // matched: remove the expectation.
- expects[i] = expects[len(expects)-1]
- expects = expects[:len(expects)-1]
- want[k] = expects
- return
- }
- unmatched = append(unmatched, fmt.Sprintf("%q", exp))
- }
- if unmatched == nil {
- t.Errorf("%v: unexpected: %v", posn, message)
- } else {
- t.Errorf("%v: %q does not match pattern %s",
- posn, message, strings.Join(unmatched, " or "))
- }
- }
-
- // Check the diagnostics match expectations.
- for _, f := range diagnostics {
- posn := fset.Position(f.Pos())
- checkMessage(posn, "", f.Name())
- }
-
- // Reject surplus expectations.
- //
- // Sometimes an Analyzer reports two similar diagnostics on a
- // line with only one expectation. The reader may be confused by
- // the error message.
- // TODO(adonovan): print a better error:
- // "got 2 diagnostics here; each one needs its own expectation".
- var surplus []string
- for key, expects := range want {
- for _, exp := range expects {
- err := fmt.Sprintf("%s:%d: no diagnostic was reported matching %q", key.file, key.line, exp)
- surplus = append(surplus, err)
- }
- }
- sort.Strings(surplus)
- for _, err := range surplus {
- t.Errorf("%s", err)
- }
-}
-
-func TestAll(t *testing.T) {
- c := NewChecker(false)
- var stats lint.Stats
- r, err := lint.NewRunner(&stats)
- if err != nil {
- t.Fatal(err)
- }
-
- dir := analysistest.TestData()
- cfg := &packages.Config{
- Dir: dir,
- Tests: true,
- Env: append(os.Environ(), "GOPATH="+dir, "GO111MODULE=off", "GOPROXY=off"),
- }
- pkgs, err := r.Run(cfg, []string{"./..."}, []*analysis.Analyzer{c.Analyzer()}, true)
- if err != nil {
- t.Fatal(err)
- }
-
- res := c.Result()
- check(t, pkgs[0].Fset, res)
-}