+++ /dev/null
-package analysis
-
-import (
- "fmt"
- "reflect"
- "strings"
- "unicode"
-)
-
-// Validate reports an error if any of the analyzers are misconfigured.
-// Checks include:
-// that the name is a valid identifier;
-// that the Requires graph is acyclic;
-// that analyzer fact types are unique;
-// that each fact type is a pointer.
-func Validate(analyzers []*Analyzer) error {
- // Map each fact type to its sole generating analyzer.
- factTypes := make(map[reflect.Type]*Analyzer)
-
- // Traverse the Requires graph, depth first.
- const (
- white = iota
- grey
- black
- finished
- )
- color := make(map[*Analyzer]uint8)
- var visit func(a *Analyzer) error
- visit = func(a *Analyzer) error {
- if a == nil {
- return fmt.Errorf("nil *Analyzer")
- }
- if color[a] == white {
- color[a] = grey
-
- // names
- if !validIdent(a.Name) {
- return fmt.Errorf("invalid analyzer name %q", a)
- }
-
- if a.Doc == "" {
- return fmt.Errorf("analyzer %q is undocumented", a)
- }
-
- // fact types
- for _, f := range a.FactTypes {
- if f == nil {
- return fmt.Errorf("analyzer %s has nil FactType", a)
- }
- t := reflect.TypeOf(f)
- if prev := factTypes[t]; prev != nil {
- return fmt.Errorf("fact type %s registered by two analyzers: %v, %v",
- t, a, prev)
- }
- if t.Kind() != reflect.Ptr {
- return fmt.Errorf("%s: fact type %s is not a pointer", a, t)
- }
- factTypes[t] = a
- }
-
- // recursion
- for _, req := range a.Requires {
- if err := visit(req); err != nil {
- return err
- }
- }
- color[a] = black
- }
-
- if color[a] == grey {
- stack := []*Analyzer{a}
- inCycle := map[string]bool{}
- for len(stack) > 0 {
- current := stack[len(stack)-1]
- stack = stack[:len(stack)-1]
- if color[current] == grey && !inCycle[current.Name] {
- inCycle[current.Name] = true
- stack = append(stack, current.Requires...)
- }
- }
- return &CycleInRequiresGraphError{AnalyzerNames: inCycle}
- }
-
- return nil
- }
- for _, a := range analyzers {
- if err := visit(a); err != nil {
- return err
- }
- }
-
- // Reject duplicates among analyzers.
- // Precondition: color[a] == black.
- // Postcondition: color[a] == finished.
- for _, a := range analyzers {
- if color[a] == finished {
- return fmt.Errorf("duplicate analyzer: %s", a.Name)
- }
- color[a] = finished
- }
-
- return nil
-}
-
-func validIdent(name string) bool {
- for i, r := range name {
- if !(r == '_' || unicode.IsLetter(r) || i > 0 && unicode.IsDigit(r)) {
- return false
- }
- }
- return name != ""
-}
-
-type CycleInRequiresGraphError struct {
- AnalyzerNames map[string]bool
-}
-
-func (e *CycleInRequiresGraphError) Error() string {
- var b strings.Builder
- b.WriteString("cycle detected involving the following analyzers:")
- for n := range e.AnalyzerNames {
- b.WriteByte(' ')
- b.WriteString(n)
- }
- return b.String()
-}