224 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2018 The Go Authors. All rights reserved.
 | 
						|
// Use of this source code is governed by a BSD-style
 | 
						|
// license that can be found in the LICENSE file.
 | 
						|
 | 
						|
// Package analysisflags defines helpers for processing flags of
 | 
						|
// analysis driver tools.
 | 
						|
package analysisflags
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/sha256"
 | 
						|
	"encoding/json"
 | 
						|
	"flag"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"log"
 | 
						|
	"os"
 | 
						|
	"strconv"
 | 
						|
 | 
						|
	"golang.org/x/tools/go/analysis"
 | 
						|
)
 | 
						|
 | 
						|
// Parse creates a flag for each of the analyzer's flags,
 | 
						|
// including (in multi mode) an --analysis.enable flag,
 | 
						|
// parses the flags, then filters and returns the list of
 | 
						|
// analyzers enabled by flags.
 | 
						|
func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
 | 
						|
	// Connect each analysis flag to the command line as -analysis.flag.
 | 
						|
	type analysisFlag struct {
 | 
						|
		Name  string
 | 
						|
		Bool  bool
 | 
						|
		Usage string
 | 
						|
	}
 | 
						|
	var analysisFlags []analysisFlag
 | 
						|
 | 
						|
	enabled := make(map[*analysis.Analyzer]*triState)
 | 
						|
	for _, a := range analyzers {
 | 
						|
		var prefix string
 | 
						|
 | 
						|
		// Add -analysis.enable flag.
 | 
						|
		if multi {
 | 
						|
			prefix = a.Name + "."
 | 
						|
 | 
						|
			enable := new(triState)
 | 
						|
			enableName := prefix + "enable"
 | 
						|
			enableUsage := "enable " + a.Name + " analysis"
 | 
						|
			flag.Var(enable, enableName, enableUsage)
 | 
						|
			enabled[a] = enable
 | 
						|
			analysisFlags = append(analysisFlags, analysisFlag{enableName, true, enableUsage})
 | 
						|
		}
 | 
						|
 | 
						|
		a.Flags.VisitAll(func(f *flag.Flag) {
 | 
						|
			if !multi && flag.Lookup(f.Name) != nil {
 | 
						|
				log.Printf("%s flag -%s would conflict with driver; skipping", a.Name, f.Name)
 | 
						|
				return
 | 
						|
			}
 | 
						|
 | 
						|
			name := prefix + f.Name
 | 
						|
			flag.Var(f.Value, name, f.Usage)
 | 
						|
 | 
						|
			var isBool bool
 | 
						|
			if b, ok := f.Value.(interface{ IsBoolFlag() bool }); ok {
 | 
						|
				isBool = b.IsBoolFlag()
 | 
						|
			}
 | 
						|
			analysisFlags = append(analysisFlags, analysisFlag{name, isBool, f.Usage})
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	// standard flags: -flags, -V.
 | 
						|
	printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
 | 
						|
	addVersionFlag()
 | 
						|
 | 
						|
	flag.Parse() // (ExitOnError)
 | 
						|
 | 
						|
	// -flags: print flags so that go vet knows which ones are legitimate.
 | 
						|
	if *printflags {
 | 
						|
		data, err := json.MarshalIndent(analysisFlags, "", "\t")
 | 
						|
		if err != nil {
 | 
						|
			log.Fatal(err)
 | 
						|
		}
 | 
						|
		os.Stdout.Write(data)
 | 
						|
		os.Exit(0)
 | 
						|
	}
 | 
						|
 | 
						|
	// If any --foo.enable flag is true,  run only those analyzers. Otherwise,
 | 
						|
	// if any --foo.enable flag is false, run all but those analyzers.
 | 
						|
	if multi {
 | 
						|
		var hasTrue, hasFalse bool
 | 
						|
		for _, ts := range enabled {
 | 
						|
			switch *ts {
 | 
						|
			case setTrue:
 | 
						|
				hasTrue = true
 | 
						|
			case setFalse:
 | 
						|
				hasFalse = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		var keep []*analysis.Analyzer
 | 
						|
		if hasTrue {
 | 
						|
			for _, a := range analyzers {
 | 
						|
				if *enabled[a] == setTrue {
 | 
						|
					keep = append(keep, a)
 | 
						|
				}
 | 
						|
			}
 | 
						|
			analyzers = keep
 | 
						|
		} else if hasFalse {
 | 
						|
			for _, a := range analyzers {
 | 
						|
				if *enabled[a] != setFalse {
 | 
						|
					keep = append(keep, a)
 | 
						|
				}
 | 
						|
			}
 | 
						|
			analyzers = keep
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return analyzers
 | 
						|
}
 | 
						|
 | 
						|
// addVersionFlag registers a -V flag that, if set,
 | 
						|
// prints the executable version and exits 0.
 | 
						|
//
 | 
						|
// It is a variable not a function to permit easy
 | 
						|
// overriding in the copy vendored in $GOROOT/src/cmd/vet:
 | 
						|
//
 | 
						|
// func init() { addVersionFlag = objabi.AddVersionFlag }
 | 
						|
var addVersionFlag = func() {
 | 
						|
	flag.Var(versionFlag{}, "V", "print version and exit")
 | 
						|
}
 | 
						|
 | 
						|
// versionFlag minimally complies with the -V protocol required by "go vet".
 | 
						|
type versionFlag struct{}
 | 
						|
 | 
						|
func (versionFlag) IsBoolFlag() bool { return true }
 | 
						|
func (versionFlag) Get() interface{} { return nil }
 | 
						|
func (versionFlag) String() string   { return "" }
 | 
						|
func (versionFlag) Set(s string) error {
 | 
						|
	if s != "full" {
 | 
						|
		log.Fatalf("unsupported flag value: -V=%s", s)
 | 
						|
	}
 | 
						|
 | 
						|
	// This replicates the miminal subset of
 | 
						|
	// cmd/internal/objabi.AddVersionFlag, which is private to the
 | 
						|
	// go tool yet forms part of our command-line interface.
 | 
						|
	// TODO(adonovan): clarify the contract.
 | 
						|
 | 
						|
	// Print the tool version so the build system can track changes.
 | 
						|
	// Formats:
 | 
						|
	//   $progname version devel ... buildID=...
 | 
						|
	//   $progname version go1.9.1
 | 
						|
	progname := os.Args[0]
 | 
						|
	f, err := os.Open(progname)
 | 
						|
	if err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
	h := sha256.New()
 | 
						|
	if _, err := io.Copy(h, f); err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
	f.Close()
 | 
						|
	fmt.Printf("%s version devel comments-go-here buildID=%02x\n",
 | 
						|
		progname, string(h.Sum(nil)))
 | 
						|
	os.Exit(0)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// A triState is a boolean that knows whether
 | 
						|
// it has been set to either true or false.
 | 
						|
// It is used to identify whether a flag appears;
 | 
						|
// the standard boolean flag cannot
 | 
						|
// distinguish missing from unset.
 | 
						|
// It also satisfies flag.Value.
 | 
						|
type triState int
 | 
						|
 | 
						|
const (
 | 
						|
	unset triState = iota
 | 
						|
	setTrue
 | 
						|
	setFalse
 | 
						|
)
 | 
						|
 | 
						|
func triStateFlag(name string, value triState, usage string) *triState {
 | 
						|
	flag.Var(&value, name, usage)
 | 
						|
	return &value
 | 
						|
}
 | 
						|
 | 
						|
// triState implements flag.Value, flag.Getter, and flag.boolFlag.
 | 
						|
// They work like boolean flags: we can say vet -printf as well as vet -printf=true
 | 
						|
func (ts *triState) Get() interface{} {
 | 
						|
	return *ts == setTrue
 | 
						|
}
 | 
						|
 | 
						|
func (ts triState) isTrue() bool {
 | 
						|
	return ts == setTrue
 | 
						|
}
 | 
						|
 | 
						|
func (ts *triState) Set(value string) error {
 | 
						|
	b, err := strconv.ParseBool(value)
 | 
						|
	if err != nil {
 | 
						|
		// This error message looks poor but package "flag" adds
 | 
						|
		// "invalid boolean value %q for -foo.enable: %s"
 | 
						|
		return fmt.Errorf("want true or false")
 | 
						|
	}
 | 
						|
	if b {
 | 
						|
		*ts = setTrue
 | 
						|
	} else {
 | 
						|
		*ts = setFalse
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (ts *triState) String() string {
 | 
						|
	switch *ts {
 | 
						|
	case unset:
 | 
						|
		return "true"
 | 
						|
	case setTrue:
 | 
						|
		return "true"
 | 
						|
	case setFalse:
 | 
						|
		return "false"
 | 
						|
	}
 | 
						|
	panic("not reached")
 | 
						|
}
 | 
						|
 | 
						|
func (ts triState) IsBoolFlag() bool {
 | 
						|
	return true
 | 
						|
}
 |