github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/analysis/validate.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package analysis 6 7 import ( 8 "fmt" 9 "reflect" 10 "strings" 11 "unicode" 12 ) 13 14 // Validate reports an error if any of the analyzers are misconfigured. 15 // Checks include: 16 // that the name is a valid identifier; 17 // that the Doc is not empty; 18 // that the Run is non-nil; 19 // that the Requires graph is acyclic; 20 // that analyzer fact types are unique; 21 // that each fact type is a pointer. 22 func Validate(analyzers []*Analyzer) error { 23 // Map each fact type to its sole generating analyzer. 24 factTypes := make(map[reflect.Type]*Analyzer) 25 26 // Traverse the Requires graph, depth first. 27 const ( 28 white = iota 29 grey 30 black 31 finished 32 ) 33 color := make(map[*Analyzer]uint8) 34 var visit func(a *Analyzer) error 35 visit = func(a *Analyzer) error { 36 if a == nil { 37 return fmt.Errorf("nil *Analyzer") 38 } 39 if color[a] == white { 40 color[a] = grey 41 42 // names 43 if !validIdent(a.Name) { 44 return fmt.Errorf("invalid analyzer name %q", a) 45 } 46 47 if a.Doc == "" { 48 return fmt.Errorf("analyzer %q is undocumented", a) 49 } 50 51 if a.Run == nil { 52 return fmt.Errorf("analyzer %q has nil Run", a) 53 } 54 // fact types 55 for _, f := range a.FactTypes { 56 if f == nil { 57 return fmt.Errorf("analyzer %s has nil FactType", a) 58 } 59 t := reflect.TypeOf(f) 60 if prev := factTypes[t]; prev != nil { 61 return fmt.Errorf("fact type %s registered by two analyzers: %v, %v", 62 t, a, prev) 63 } 64 if t.Kind() != reflect.Ptr { 65 return fmt.Errorf("%s: fact type %s is not a pointer", a, t) 66 } 67 factTypes[t] = a 68 } 69 70 // recursion 71 for _, req := range a.Requires { 72 if err := visit(req); err != nil { 73 return err 74 } 75 } 76 color[a] = black 77 } 78 79 if color[a] == grey { 80 stack := []*Analyzer{a} 81 inCycle := map[string]bool{} 82 for len(stack) > 0 { 83 current := stack[len(stack)-1] 84 stack = stack[:len(stack)-1] 85 if color[current] == grey && !inCycle[current.Name] { 86 inCycle[current.Name] = true 87 stack = append(stack, current.Requires...) 88 } 89 } 90 return &CycleInRequiresGraphError{AnalyzerNames: inCycle} 91 } 92 93 return nil 94 } 95 for _, a := range analyzers { 96 if err := visit(a); err != nil { 97 return err 98 } 99 } 100 101 // Reject duplicates among analyzers. 102 // Precondition: color[a] == black. 103 // Postcondition: color[a] == finished. 104 for _, a := range analyzers { 105 if color[a] == finished { 106 return fmt.Errorf("duplicate analyzer: %s", a.Name) 107 } 108 color[a] = finished 109 } 110 111 return nil 112 } 113 114 func validIdent(name string) bool { 115 for i, r := range name { 116 if !(r == '_' || unicode.IsLetter(r) || i > 0 && unicode.IsDigit(r)) { 117 return false 118 } 119 } 120 return name != "" 121 } 122 123 type CycleInRequiresGraphError struct { 124 AnalyzerNames map[string]bool 125 } 126 127 func (e *CycleInRequiresGraphError) Error() string { 128 var b strings.Builder 129 b.WriteString("cycle detected involving the following analyzers:") 130 for n := range e.AnalyzerNames { 131 b.WriteByte(' ') 132 b.WriteString(n) 133 } 134 return b.String() 135 }