errorcalls_test.go raw

   1  // Copyright 2021 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package types_test
   6  
   7  import (
   8  	"go/ast"
   9  	"go/token"
  10  	"strconv"
  11  	"testing"
  12  )
  13  
  14  const (
  15  	errorfMinArgCount = 4
  16  	errorfFormatIndex = 2
  17  )
  18  
  19  // TestErrorCalls makes sure that check.errorf calls have at least
  20  // errorfMinArgCount arguments (otherwise we should use check.error)
  21  // and use balanced parentheses/brackets.
  22  func TestErrorCalls(t *testing.T) {
  23  	fset := token.NewFileSet()
  24  	files, err := pkgFiles(fset, ".")
  25  	if err != nil {
  26  		t.Fatal(err)
  27  	}
  28  
  29  	for _, file := range files {
  30  		ast.Inspect(file, func(n ast.Node) bool {
  31  			call, _ := n.(*ast.CallExpr)
  32  			if call == nil {
  33  				return true
  34  			}
  35  			selx, _ := call.Fun.(*ast.SelectorExpr)
  36  			if selx == nil {
  37  				return true
  38  			}
  39  			if !(isName(selx.X, "check") && isName(selx.Sel, "errorf")) {
  40  				return true
  41  			}
  42  			// check.errorf calls should have at least errorfMinArgCount arguments:
  43  			// position, code, format string, and arguments to format
  44  			if n := len(call.Args); n < errorfMinArgCount {
  45  				t.Errorf("%s: got %d arguments, want at least %d", fset.Position(call.Pos()), n, errorfMinArgCount)
  46  				return false
  47  			}
  48  			format := call.Args[errorfFormatIndex]
  49  			ast.Inspect(format, func(n ast.Node) bool {
  50  				if lit, _ := n.(*ast.BasicLit); lit != nil && lit.Kind == token.STRING {
  51  					if s, err := strconv.Unquote(lit.Value); err == nil {
  52  						if !balancedParentheses(s) {
  53  							t.Errorf("%s: unbalanced parentheses/brackets", fset.Position(lit.ValuePos))
  54  						}
  55  					}
  56  					return false
  57  				}
  58  				return true
  59  			})
  60  			return false
  61  		})
  62  	}
  63  }
  64  
  65  func isName(n ast.Node, name string) bool {
  66  	if n, ok := n.(*ast.Ident); ok {
  67  		return n.Name == name
  68  	}
  69  	return false
  70  }
  71  
  72  func balancedParentheses(s string) bool {
  73  	var stack []byte
  74  	for _, ch := range s {
  75  		var open byte
  76  		switch ch {
  77  		case '(', '[', '{':
  78  			stack = append(stack, byte(ch))
  79  			continue
  80  		case ')':
  81  			open = '('
  82  		case ']':
  83  			open = '['
  84  		case '}':
  85  			open = '{'
  86  		default:
  87  			continue
  88  		}
  89  		// closing parenthesis/bracket must have matching opening
  90  		top := len(stack) - 1
  91  		if top < 0 || stack[top] != open {
  92  			return false
  93  		}
  94  		stack = stack[:top]
  95  	}
  96  	return len(stack) == 0
  97  }
  98