src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

st1005.go (3679B)


      1 package st1005
      2 
      3 import (
      4 	"go/constant"
      5 	"strings"
      6 	"unicode"
      7 	"unicode/utf8"
      8 
      9 	"honnef.co/go/tools/analysis/code"
     10 	"honnef.co/go/tools/analysis/lint"
     11 	"honnef.co/go/tools/analysis/report"
     12 	"honnef.co/go/tools/go/ir"
     13 	"honnef.co/go/tools/go/ir/irutil"
     14 	"honnef.co/go/tools/internal/passes/buildir"
     15 
     16 	"golang.org/x/tools/go/analysis"
     17 )
     18 
     19 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     20 	Analyzer: &analysis.Analyzer{
     21 		Name:     "ST1005",
     22 		Run:      run,
     23 		Requires: []*analysis.Analyzer{buildir.Analyzer},
     24 	},
     25 	Doc: &lint.RawDocumentation{
     26 		Title: `Incorrectly formatted error string`,
     27 		Text: `Error strings follow a set of guidelines to ensure uniformity and good
     28 composability.
     29 
     30 Quoting Go Code Review Comments:
     31 
     32 > Error strings should not be capitalized (unless beginning with
     33 > proper nouns or acronyms) or end with punctuation, since they are
     34 > usually printed following other context. That is, use
     35 > \'fmt.Errorf("something bad")\' not \'fmt.Errorf("Something bad")\', so
     36 > that \'log.Printf("Reading %s: %v", filename, err)\' formats without a
     37 > spurious capital letter mid-message.`,
     38 		Since:   "2019.1",
     39 		MergeIf: lint.MergeIfAny,
     40 	},
     41 })
     42 
     43 var Analyzer = SCAnalyzer.Analyzer
     44 
     45 func run(pass *analysis.Pass) (any, error) {
     46 	objNames := map[*ir.Package]map[string]bool{}
     47 	irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
     48 	objNames[irpkg] = map[string]bool{}
     49 	for _, m := range irpkg.Members {
     50 		if typ, ok := m.(*ir.Type); ok {
     51 			objNames[irpkg][typ.Name()] = true
     52 		}
     53 	}
     54 	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
     55 		objNames[fn.Package()][fn.Name()] = true
     56 	}
     57 
     58 	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
     59 		if code.IsInTest(pass, fn) {
     60 			// We don't care about malformed error messages in tests;
     61 			// they're usually for direct human consumption, not part
     62 			// of an API
     63 			continue
     64 		}
     65 		for _, block := range fn.Blocks {
     66 		instrLoop:
     67 			for _, ins := range block.Instrs {
     68 				call, ok := ins.(*ir.Call)
     69 				if !ok {
     70 					continue
     71 				}
     72 				if !irutil.IsCallToAny(call.Common(), "errors.New", "fmt.Errorf") {
     73 					continue
     74 				}
     75 
     76 				k, ok := call.Common().Args[0].(*ir.Const)
     77 				if !ok {
     78 					continue
     79 				}
     80 
     81 				s := constant.StringVal(k.Value)
     82 				if len(s) == 0 {
     83 					continue
     84 				}
     85 				switch s[len(s)-1] {
     86 				case '.', ':', '!', '\n':
     87 					report.Report(pass, call, "error strings should not end with punctuation or newlines")
     88 				}
     89 				before, _, ok0 := strings.Cut(s, " ")
     90 				if !ok0 {
     91 					// single word error message, probably not a real
     92 					// error but something used in tests or during
     93 					// debugging
     94 					continue
     95 				}
     96 				word := before
     97 				first, n := utf8.DecodeRuneInString(word)
     98 				if !unicode.IsUpper(first) {
     99 					continue
    100 				}
    101 				for _, c := range word[n:] {
    102 					if unicode.IsUpper(c) || unicode.IsDigit(c) {
    103 						// Word is probably an initialism or multi-word function name. Digits cover elliptic curves like
    104 						// P384.
    105 						continue instrLoop
    106 					}
    107 				}
    108 
    109 				if strings.ContainsRune(word, '(') {
    110 					// Might be a function call
    111 					continue instrLoop
    112 				}
    113 				word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) })
    114 				if objNames[fn.Package()][word] {
    115 					// Word is probably the name of a function or type in this package
    116 					continue
    117 				}
    118 				// First word in error starts with a capital
    119 				// letter, and the word doesn't contain any other
    120 				// capitals, making it unlikely to be an
    121 				// initialism or multi-word function name.
    122 				//
    123 				// It could still be a proper noun, though.
    124 
    125 				report.Report(pass, call, "error strings should not be capitalized")
    126 			}
    127 		}
    128 	}
    129 	return nil, nil
    130 }