github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/error-strings.go (about)

     1  package rule
     2  
     3  import (
     4  	"go/ast"
     5  	"go/token"
     6  	"strconv"
     7  	"unicode"
     8  	"unicode/utf8"
     9  
    10  	"github.com/mgechev/revive/lint"
    11  )
    12  
    13  // ErrorStringsRule lints given else constructs.
    14  type ErrorStringsRule struct{}
    15  
    16  // Apply applies the rule to given file.
    17  func (r *ErrorStringsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
    18  	var failures []lint.Failure
    19  
    20  	fileAst := file.AST
    21  	walker := lintErrorStrings{
    22  		file:    file,
    23  		fileAst: fileAst,
    24  		onFailure: func(failure lint.Failure) {
    25  			failures = append(failures, failure)
    26  		},
    27  	}
    28  
    29  	ast.Walk(walker, fileAst)
    30  
    31  	return failures
    32  }
    33  
    34  // Name returns the rule name.
    35  func (r *ErrorStringsRule) Name() string {
    36  	return "error-strings"
    37  }
    38  
    39  type lintErrorStrings struct {
    40  	file      *lint.File
    41  	fileAst   *ast.File
    42  	onFailure func(lint.Failure)
    43  }
    44  
    45  func (w lintErrorStrings) Visit(n ast.Node) ast.Visitor {
    46  	ce, ok := n.(*ast.CallExpr)
    47  	if !ok {
    48  		return w
    49  	}
    50  	if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") {
    51  		return w
    52  	}
    53  	if len(ce.Args) < 1 {
    54  		return w
    55  	}
    56  	str, ok := ce.Args[0].(*ast.BasicLit)
    57  	if !ok || str.Kind != token.STRING {
    58  		return w
    59  	}
    60  	s, _ := strconv.Unquote(str.Value) // can assume well-formed Go
    61  	if s == "" {
    62  		return w
    63  	}
    64  	clean, conf := lintErrorString(s)
    65  	if clean {
    66  		return w
    67  	}
    68  
    69  	w.onFailure(lint.Failure{
    70  		Node:       str,
    71  		Confidence: conf,
    72  		Category:   "errors",
    73  		Failure:    "error strings should not be capitalized or end with punctuation or a newline",
    74  	})
    75  	return w
    76  }
    77  
    78  func lintErrorString(s string) (isClean bool, conf float64) {
    79  	const basicConfidence = 0.8
    80  	const capConfidence = basicConfidence - 0.2
    81  	first, firstN := utf8.DecodeRuneInString(s)
    82  	last, _ := utf8.DecodeLastRuneInString(s)
    83  	if last == '.' || last == ':' || last == '!' || last == '\n' {
    84  		return false, basicConfidence
    85  	}
    86  	if unicode.IsUpper(first) {
    87  		// People use proper nouns and exported Go identifiers in error strings,
    88  		// so decrease the confidence of warnings for capitalization.
    89  		if len(s) <= firstN {
    90  			return false, capConfidence
    91  		}
    92  		// Flag strings starting with something that doesn't look like an initialism.
    93  		if second, _ := utf8.DecodeRuneInString(s[firstN:]); !unicode.IsUpper(second) {
    94  			return false, capConfidence
    95  		}
    96  	}
    97  	return true, 0
    98  }