github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/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 Requires graph is acyclic; 18 // that analyzer fact types are unique; 19 // that each fact type is a pointer. 20 func Validate(analyzers []*Analyzer) error { 21 // Map each fact type to its sole generating analyzer. 22 factTypes := make(map[reflect.Type]*Analyzer) 23 24 // Traverse the Requires graph, depth first. 25 const ( 26 white = iota 27 grey 28 black 29 finished 30 ) 31 color := make(map[*Analyzer]uint8) 32 var visit func(a *Analyzer) error 33 visit = func(a *Analyzer) error { 34 if a == nil { 35 return fmt.Errorf("nil *Analyzer") 36 } 37 if color[a] == white { 38 color[a] = grey 39 40 // names 41 if !validIdent(a.Name) { 42 return fmt.Errorf("invalid analyzer name %q", a) 43 } 44 45 if a.Doc == "" { 46 return fmt.Errorf("analyzer %q is undocumented", a) 47 } 48 49 // fact types 50 for _, f := range a.FactTypes { 51 if f == nil { 52 return fmt.Errorf("analyzer %s has nil FactType", a) 53 } 54 t := reflect.TypeOf(f) 55 if prev := factTypes[t]; prev != nil { 56 return fmt.Errorf("fact type %s registered by two analyzers: %v, %v", 57 t, a, prev) 58 } 59 if t.Kind() != reflect.Ptr { 60 return fmt.Errorf("%s: fact type %s is not a pointer", a, t) 61 } 62 factTypes[t] = a 63 } 64 65 // recursion 66 for _, req := range a.Requires { 67 if err := visit(req); err != nil { 68 return err 69 } 70 } 71 color[a] = black 72 } 73 74 if color[a] == grey { 75 stack := []*Analyzer{a} 76 inCycle := map[string]bool{} 77 for len(stack) > 0 { 78 current := stack[len(stack)-1] 79 stack = stack[:len(stack)-1] 80 if color[current] == grey && !inCycle[current.Name] { 81 inCycle[current.Name] = true 82 stack = append(stack, current.Requires...) 83 } 84 } 85 return &CycleInRequiresGraphError{AnalyzerNames: inCycle} 86 } 87 88 return nil 89 } 90 for _, a := range analyzers { 91 if err := visit(a); err != nil { 92 return err 93 } 94 } 95 96 // Reject duplicates among analyzers. 97 // Precondition: color[a] == black. 98 // Postcondition: color[a] == finished. 99 for _, a := range analyzers { 100 if color[a] == finished { 101 return fmt.Errorf("duplicate analyzer: %s", a.Name) 102 } 103 color[a] = finished 104 } 105 106 return nil 107 } 108 109 func validIdent(name string) bool { 110 for i, r := range name { 111 if !(r == '_' || unicode.IsLetter(r) || i > 0 && unicode.IsDigit(r)) { 112 return false 113 } 114 } 115 return name != "" 116 } 117 118 type CycleInRequiresGraphError struct { 119 AnalyzerNames map[string]bool 120 } 121 122 func (e *CycleInRequiresGraphError) Error() string { 123 var b strings.Builder 124 b.WriteString("cycle detected involving the following analyzers:") 125 for n := range e.AnalyzerNames { 126 b.WriteByte(' ') 127 b.WriteString(n) 128 } 129 return b.String() 130 }