136 lines
3.0 KiB
Go
136 lines
3.0 KiB
Go
package lintpack
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/go-toolsmith/astfmt"
|
|
)
|
|
|
|
type checkerProto struct {
|
|
info *CheckerInfo
|
|
constructor func(*Context) *Checker
|
|
}
|
|
|
|
// prototypes is a set of registered checkers that are not yet instantiated.
|
|
// Registration should be done with AddChecker function.
|
|
// Initialized checkers can be obtained with NewChecker function.
|
|
var prototypes = make(map[string]checkerProto)
|
|
|
|
func getCheckersInfo() []*CheckerInfo {
|
|
infoList := make([]*CheckerInfo, 0, len(prototypes))
|
|
for _, proto := range prototypes {
|
|
infoCopy := *proto.info
|
|
infoList = append(infoList, &infoCopy)
|
|
}
|
|
sort.Slice(infoList, func(i, j int) bool {
|
|
return infoList[i].Name < infoList[j].Name
|
|
})
|
|
return infoList
|
|
}
|
|
|
|
func addChecker(info *CheckerInfo, constructor func(*CheckerContext) FileWalker) {
|
|
if _, ok := prototypes[info.Name]; ok {
|
|
panic(fmt.Sprintf("checker with name %q already registered", info.Name))
|
|
}
|
|
|
|
// Validate param value type.
|
|
for pname, param := range info.Params {
|
|
switch param.Value.(type) {
|
|
case string, int, bool:
|
|
// OK.
|
|
default:
|
|
panic(fmt.Sprintf("unsupported %q param type value: %T",
|
|
pname, param.Value))
|
|
}
|
|
}
|
|
|
|
trimDocumentation := func(info *CheckerInfo) {
|
|
fields := []*string{
|
|
&info.Summary,
|
|
&info.Details,
|
|
&info.Before,
|
|
&info.After,
|
|
&info.Note,
|
|
}
|
|
for _, f := range fields {
|
|
*f = strings.TrimSpace(*f)
|
|
}
|
|
}
|
|
|
|
trimDocumentation(info)
|
|
|
|
if err := validateCheckerInfo(info); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
proto := checkerProto{
|
|
info: info,
|
|
constructor: func(ctx *Context) *Checker {
|
|
var c Checker
|
|
c.Info = info
|
|
c.ctx = CheckerContext{
|
|
Context: ctx,
|
|
printer: astfmt.NewPrinter(ctx.FileSet),
|
|
}
|
|
c.fileWalker = constructor(&c.ctx)
|
|
return &c
|
|
},
|
|
}
|
|
|
|
prototypes[info.Name] = proto
|
|
}
|
|
|
|
func newChecker(ctx *Context, info *CheckerInfo) *Checker {
|
|
proto, ok := prototypes[info.Name]
|
|
if !ok {
|
|
panic(fmt.Sprintf("checker with name %q not registered", info.Name))
|
|
}
|
|
return proto.constructor(ctx)
|
|
}
|
|
|
|
func validateCheckerInfo(info *CheckerInfo) error {
|
|
steps := []func(*CheckerInfo) error{
|
|
validateCheckerName,
|
|
validateCheckerDocumentation,
|
|
validateCheckerTags,
|
|
}
|
|
|
|
for _, step := range steps {
|
|
if err := step(info); err != nil {
|
|
return fmt.Errorf("%q validation error: %v", info.Name, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var validIdentRE = regexp.MustCompile(`^\w+$`)
|
|
|
|
func validateCheckerName(info *CheckerInfo) error {
|
|
if !validIdentRE.MatchString(info.Name) {
|
|
return fmt.Errorf("checker name contains illegal chars")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateCheckerDocumentation(info *CheckerInfo) error {
|
|
// TODO(Quasilyte): validate documentation.
|
|
return nil
|
|
}
|
|
|
|
func validateCheckerTags(info *CheckerInfo) error {
|
|
tagSet := make(map[string]bool)
|
|
for _, tag := range info.Tags {
|
|
if tagSet[tag] {
|
|
return fmt.Errorf("duplicated tag %q", tag)
|
|
}
|
|
if !validIdentRE.MatchString(tag) {
|
|
return fmt.Errorf("checker tag %q contains illegal chars", tag)
|
|
}
|
|
tagSet[tag] = true
|
|
}
|
|
return nil
|
|
}
|