Source file src/go/types/errorcalls_test.go

     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  

View as plain text