1
2
3
4
5 package analysis
6
7 import (
8 "fmt"
9 "reflect"
10 "strings"
11 "unicode"
12 )
13
14
15
16
17
18
19
20
21
22
23
24 func Validate(analyzers []*Analyzer) error {
25
26 factTypes := make(map[reflect.Type]*Analyzer)
27
28
29 const (
30 white = iota
31 grey
32 black
33 finished
34 )
35 color := make(map[*Analyzer]uint8)
36 var visit func(a *Analyzer) error
37 visit = func(a *Analyzer) error {
38 if a == nil {
39 return fmt.Errorf("nil *Analyzer")
40 }
41 if color[a] == white {
42 color[a] = grey
43
44
45 if !validIdent(a.Name) {
46 return fmt.Errorf("invalid analyzer name %q", a)
47 }
48
49 if a.Doc == "" {
50 return fmt.Errorf("analyzer %q is undocumented", a)
51 }
52
53 if a.Run == nil {
54 return fmt.Errorf("analyzer %q has nil Run", a)
55 }
56
57 for _, f := range a.FactTypes {
58 if f == nil {
59 return fmt.Errorf("analyzer %s has nil FactType", a)
60 }
61 t := reflect.TypeOf(f)
62 if prev := factTypes[t]; prev != nil {
63 return fmt.Errorf("fact type %s registered by two analyzers: %v, %v",
64 t, a, prev)
65 }
66 if t.Kind() != reflect.Ptr {
67 return fmt.Errorf("%s: fact type %s is not a pointer", a, t)
68 }
69 factTypes[t] = a
70 }
71
72
73 for _, req := range a.Requires {
74 if err := visit(req); err != nil {
75 return err
76 }
77 }
78 color[a] = black
79 }
80
81 if color[a] == grey {
82 stack := []*Analyzer{a}
83 inCycle := map[string]bool{}
84 for len(stack) > 0 {
85 current := stack[len(stack)-1]
86 stack = stack[:len(stack)-1]
87 if color[current] == grey && !inCycle[current.Name] {
88 inCycle[current.Name] = true
89 stack = append(stack, current.Requires...)
90 }
91 }
92 return &CycleInRequiresGraphError{AnalyzerNames: inCycle}
93 }
94
95 return nil
96 }
97 for _, a := range analyzers {
98 if err := visit(a); err != nil {
99 return err
100 }
101 }
102
103
104
105
106 for _, a := range analyzers {
107 if color[a] == finished {
108 return fmt.Errorf("duplicate analyzer: %s", a.Name)
109 }
110 color[a] = finished
111 }
112
113 return nil
114 }
115
116 func validIdent(name string) bool {
117 for i, r := range name {
118 if !(r == '_' || unicode.IsLetter(r) || i > 0 && unicode.IsDigit(r)) {
119 return false
120 }
121 }
122 return name != ""
123 }
124
125 type CycleInRequiresGraphError struct {
126 AnalyzerNames map[string]bool
127 }
128
129 func (e *CycleInRequiresGraphError) Error() string {
130 var b strings.Builder
131 b.WriteString("cycle detected involving the following analyzers:")
132 for n := range e.AnalyzerNames {
133 b.WriteByte(' ')
134 b.WriteString(n)
135 }
136 return b.String()
137 }
138
View as plain text