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  }