initial commit: ifinit and boolnames
This commit is contained in:
119
boolnames/analyzer.go
Normal file
119
boolnames/analyzer.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package boolnames
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var defaultBoolNamePrefixes = []string{"is", "has", "can", "was"}
|
||||
|
||||
var Analyzer = NewAnalyzer(Settings{})
|
||||
|
||||
func NewAnalyzer(settings Settings) *analysis.Analyzer {
|
||||
if len(settings.AllowedPrefixes) == 0 {
|
||||
settings.AllowedPrefixes = defaultBoolNamePrefixes
|
||||
}
|
||||
return &analysis.Analyzer{
|
||||
Name: "boolnames",
|
||||
Doc: `checks that boolean values and functions use appropriate names`,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
Run: func(pass *analysis.Pass) (any, error) {
|
||||
result, ok := pass.ResultOf[inspect.Analyzer]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("inspect analyzer result is unavailable")
|
||||
}
|
||||
|
||||
insp, ok := result.(*inspector.Inspector)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("inspect analyzer returned %T, want *inspector.Inspector", result)
|
||||
}
|
||||
|
||||
insp.Preorder([]ast.Node{(*ast.Ident)(nil)}, func(n ast.Node) {
|
||||
ident, ok := n.(*ast.Ident)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
obj := pass.TypesInfo.Defs[ident]
|
||||
if obj == nil || ident.Name == "_" {
|
||||
return
|
||||
}
|
||||
|
||||
switch obj.(type) {
|
||||
case *types.Var, *types.Const, *types.Func:
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
if !hasBoolType(obj.Type()) {
|
||||
return
|
||||
}
|
||||
|
||||
if settings.IsAcceptableBoolName(ident.Name) {
|
||||
return
|
||||
}
|
||||
|
||||
pass.Reportf(ident.Pos(),
|
||||
"boolean name %q must start with %s",
|
||||
ident.Name,
|
||||
strings.Join(settings.AllowedPrefixes, ", "),
|
||||
)
|
||||
})
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func hasBoolType(t types.Type) bool {
|
||||
isBool := func(t types.Type) bool {
|
||||
basic, ok := types.Unalias(t).Underlying().(*types.Basic)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return basic.Kind() == types.Bool || basic.Kind() == types.UntypedBool
|
||||
}
|
||||
if isBool(t) {
|
||||
return true
|
||||
}
|
||||
|
||||
sig, ok := types.Unalias(t).Underlying().(*types.Signature)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
results := sig.Results()
|
||||
return results.Len() == 1 && isBool(results.At(0).Type())
|
||||
}
|
||||
|
||||
func (settings Settings) IsAcceptableBoolName(s string) bool {
|
||||
if slices.Contains(settings.Whitelist, s) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, pfix := range settings.AllowedPrefixes {
|
||||
if pfix == "" || len(s) <= len(pfix) || s[1:len(pfix)] != pfix[1:] {
|
||||
continue
|
||||
}
|
||||
|
||||
first := s[0]
|
||||
if first != pfix[0] && first != pfix[0]-'a'+'A' {
|
||||
continue
|
||||
}
|
||||
|
||||
sfix := s[len(pfix):]
|
||||
r, _ := utf8.DecodeRuneInString(sfix)
|
||||
return unicode.IsUpper(r)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user