| // Copyright 2020 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package types_test |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/constant" |
| "go/importer" |
| "go/parser" |
| "go/token" |
| "reflect" |
| "strings" |
| "testing" |
| |
| . "go/types" |
| ) |
| |
| func TestErrorCodeExamples(t *testing.T) { |
| walkCodes(t, func(name string, value int, spec *ast.ValueSpec) { |
| t.Run(name, func(t *testing.T) { |
| doc := spec.Doc.Text() |
| examples := strings.Split(doc, "Example:") |
| for i := 1; i < len(examples); i++ { |
| example := examples[i] |
| err := checkExample(t, example) |
| if err == nil { |
| t.Fatalf("no error in example #%d", i) |
| } |
| typerr, ok := err.(Error) |
| if !ok { |
| t.Fatalf("not a types.Error: %v", err) |
| } |
| if got := readCode(typerr); got != value { |
| t.Errorf("%s: example #%d returned code %d (%s), want %d", name, i, got, err, value) |
| } |
| } |
| }) |
| }) |
| } |
| |
| func walkCodes(t *testing.T, f func(string, int, *ast.ValueSpec)) { |
| t.Helper() |
| fset := token.NewFileSet() |
| files, err := pkgFiles(fset, ".", parser.ParseComments) // from self_test.go |
| if err != nil { |
| t.Fatal(err) |
| } |
| conf := Config{Importer: importer.Default()} |
| info := &Info{ |
| Types: make(map[ast.Expr]TypeAndValue), |
| Defs: make(map[*ast.Ident]Object), |
| Uses: make(map[*ast.Ident]Object), |
| } |
| _, err = conf.Check("types", fset, files, info) |
| if err != nil { |
| t.Fatal(err) |
| } |
| for _, file := range files { |
| for _, decl := range file.Decls { |
| decl, ok := decl.(*ast.GenDecl) |
| if !ok || decl.Tok != token.CONST { |
| continue |
| } |
| for _, spec := range decl.Specs { |
| spec, ok := spec.(*ast.ValueSpec) |
| if !ok || len(spec.Names) == 0 { |
| continue |
| } |
| obj := info.ObjectOf(spec.Names[0]) |
| if named, ok := obj.Type().(*Named); ok && named.Obj().Name() == "errorCode" { |
| if len(spec.Names) != 1 { |
| t.Fatalf("bad Code declaration for %q: got %d names, want exactly 1", spec.Names[0].Name, len(spec.Names)) |
| } |
| codename := spec.Names[0].Name |
| value := int(constant.Val(obj.(*Const).Val()).(int64)) |
| f(codename, value, spec) |
| } |
| } |
| } |
| } |
| } |
| |
| func readCode(err Error) int { |
| v := reflect.ValueOf(err) |
| return int(v.FieldByName("go116code").Int()) |
| } |
| |
| func checkExample(t *testing.T, example string) error { |
| t.Helper() |
| fset := token.NewFileSet() |
| src := fmt.Sprintf("package p\n\n%s", example) |
| file, err := parser.ParseFile(fset, "example.go", src, 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| conf := Config{ |
| FakeImportC: true, |
| Importer: importer.Default(), |
| } |
| _, err = conf.Check("example", fset, []*ast.File{file}, nil) |
| return err |
| } |
| |
| func TestErrorCodeStyle(t *testing.T) { |
| // The set of error codes is large and intended to be self-documenting, so |
| // this test enforces some style conventions. |
| forbiddenInIdent := []string{ |
| // use invalid instead |
| "illegal", |
| // words with a common short-form |
| "argument", |
| "assertion", |
| "assignment", |
| "boolean", |
| "channel", |
| "condition", |
| "declaration", |
| "expression", |
| "function", |
| "initial", // use init for initializer, initialization, etc. |
| "integer", |
| "interface", |
| "iterat", // use iter for iterator, iteration, etc. |
| "literal", |
| "operation", |
| "package", |
| "pointer", |
| "receiver", |
| "signature", |
| "statement", |
| "variable", |
| } |
| forbiddenInComment := []string{ |
| // lhs and rhs should be spelled-out. |
| "lhs", "rhs", |
| // builtin should be hyphenated. |
| "builtin", |
| // Use dot-dot-dot. |
| "ellipsis", |
| } |
| nameHist := make(map[int]int) |
| longestName := "" |
| maxValue := 0 |
| |
| walkCodes(t, func(name string, value int, spec *ast.ValueSpec) { |
| if name == "_" { |
| return |
| } |
| nameHist[len(name)]++ |
| if value > maxValue { |
| maxValue = value |
| } |
| if len(name) > len(longestName) { |
| longestName = name |
| } |
| if token.IsExported(name) { |
| // This is an experimental API, and errorCode values should not be |
| // exported. |
| t.Errorf("%q is exported", name) |
| } |
| if name[0] != '_' || !token.IsExported(name[1:]) { |
| t.Errorf("%q should start with _, followed by an exported identifier", name) |
| } |
| lower := strings.ToLower(name) |
| for _, bad := range forbiddenInIdent { |
| if strings.Contains(lower, bad) { |
| t.Errorf("%q contains forbidden word %q", name, bad) |
| } |
| } |
| doc := spec.Doc.Text() |
| if !strings.HasPrefix(doc, name) { |
| t.Errorf("doc for %q does not start with identifier", name) |
| } |
| lowerComment := strings.ToLower(strings.TrimPrefix(doc, name)) |
| for _, bad := range forbiddenInComment { |
| if strings.Contains(lowerComment, bad) { |
| t.Errorf("doc for %q contains forbidden word %q", name, bad) |
| } |
| } |
| }) |
| |
| if testing.Verbose() { |
| var totChars, totCount int |
| for chars, count := range nameHist { |
| totChars += chars * count |
| totCount += count |
| } |
| avg := float64(totChars) / float64(totCount) |
| fmt.Println() |
| fmt.Printf("%d error codes\n", totCount) |
| fmt.Printf("average length: %.2f chars\n", avg) |
| fmt.Printf("max length: %d (%s)\n", len(longestName), longestName) |
| } |
| } |