blob: 5da1cdadfc69144f20a408ea043948db62129b4f [file] [log] [blame]
// 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)
}
}