| // Copyright 2021 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 ( |
| "go/ast" |
| "go/token" |
| "strconv" |
| "testing" |
| ) |
| |
| const ( |
| errorfMinArgCount = 4 |
| errorfFormatIndex = 2 |
| ) |
| |
| // TestErrorCalls makes sure that check.errorf calls have at least |
| // errorfMinArgCount arguments (otherwise we should use check.error) |
| // and use balanced parentheses/brackets. |
| func TestErrorCalls(t *testing.T) { |
| fset := token.NewFileSet() |
| files, err := pkgFiles(fset, ".") |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| for _, file := range files { |
| ast.Inspect(file, func(n ast.Node) bool { |
| call, _ := n.(*ast.CallExpr) |
| if call == nil { |
| return true |
| } |
| selx, _ := call.Fun.(*ast.SelectorExpr) |
| if selx == nil { |
| return true |
| } |
| if !(isName(selx.X, "check") && isName(selx.Sel, "errorf")) { |
| return true |
| } |
| // check.errorf calls should have at least errorfMinArgCount arguments: |
| // position, code, format string, and arguments to format |
| if n := len(call.Args); n < errorfMinArgCount { |
| t.Errorf("%s: got %d arguments, want at least %d", fset.Position(call.Pos()), n, errorfMinArgCount) |
| return false |
| } |
| format := call.Args[errorfFormatIndex] |
| ast.Inspect(format, func(n ast.Node) bool { |
| if lit, _ := n.(*ast.BasicLit); lit != nil && lit.Kind == token.STRING { |
| if s, err := strconv.Unquote(lit.Value); err == nil { |
| if !balancedParentheses(s) { |
| t.Errorf("%s: unbalanced parentheses/brackets", fset.Position(lit.ValuePos)) |
| } |
| } |
| return false |
| } |
| return true |
| }) |
| return false |
| }) |
| } |
| } |
| |
| func isName(n ast.Node, name string) bool { |
| if n, ok := n.(*ast.Ident); ok { |
| return n.Name == name |
| } |
| return false |
| } |
| |
| func balancedParentheses(s string) bool { |
| var stack []byte |
| for _, ch := range s { |
| var open byte |
| switch ch { |
| case '(', '[', '{': |
| stack = append(stack, byte(ch)) |
| continue |
| case ')': |
| open = '(' |
| case ']': |
| open = '[' |
| case '}': |
| open = '{' |
| default: |
| continue |
| } |
| // closing parenthesis/bracket must have matching opening |
| top := len(stack) - 1 |
| if top < 0 || stack[top] != open { |
| return false |
| } |
| stack = stack[:top] |
| } |
| return len(stack) == 0 |
| } |