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 }