diff --git a/src/cmd/vet/main.go b/src/cmd/vet/main.go index f0309cba94..055deb9b67 100644 --- a/src/cmd/vet/main.go +++ b/src/cmd/vet/main.go @@ -8,14 +8,17 @@ package main import ( "bytes" + "encoding/json" "flag" "fmt" "go/ast" "go/build" + "go/importer" "go/parser" "go/printer" "go/token" "go/types" + "io" "io/ioutil" "os" "path/filepath" @@ -30,6 +33,8 @@ var ( source = flag.Bool("source", false, "import from source instead of compiled object files") tags = flag.String("tags", "", "space-separated list of build tags to apply when parsing") tagList = []string{} // exploded version of tags flag; set in main + + mustTypecheck bool ) var exitCode = 0 @@ -226,6 +231,18 @@ func main() { if flag.NArg() == 0 { Usage() } + + // Special case for "go vet" passing an explicit configuration: + // single argument ending in vet.cfg. + // Once we have a more general mechanism for obtaining this + // information from build tools like the go command, + // vet should be changed to use it. This vet.cfg hack is an + // experiment to learn about what form that information should take. + if flag.NArg() == 1 && strings.HasSuffix(flag.Arg(0), "vet.cfg") { + doPackageCfg(flag.Arg(0)) + os.Exit(exitCode) + } + for _, name := range flag.Args() { // Is it a directory? fi, err := os.Stat(name) @@ -266,6 +283,65 @@ func prefixDirectory(directory string, names []string) { } } +// vetConfig is the JSON config struct prepared by the Go command. +type vetConfig struct { + Compiler string + Dir string + GoFiles []string + ImportMap map[string]string + PackageFile map[string]string + + imp types.Importer +} + +func (v *vetConfig) Import(path string) (*types.Package, error) { + if v.imp == nil { + v.imp = importer.For(v.Compiler, v.openPackageFile) + } + if path == "unsafe" { + return v.imp.Import("unsafe") + } + p := v.ImportMap[path] + if p == "" { + return nil, fmt.Errorf("unknown import path %q", path) + } + if v.PackageFile[p] == "" { + return nil, fmt.Errorf("unknown package file for import %q", path) + } + return v.imp.Import(p) +} + +func (v *vetConfig) openPackageFile(path string) (io.ReadCloser, error) { + file := v.PackageFile[path] + if file == "" { + // Note that path here has been translated via v.ImportMap, + // unlike in the error in Import above. We prefer the error in + // Import, but it's worth diagnosing this one too, just in case. + return nil, fmt.Errorf("unknown package file for %q", path) + } + f, err := os.Open(file) + if err != nil { + return nil, err + } + return f, nil +} + +// doPackageCfg analyzes a single package described in a config file. +func doPackageCfg(cfgFile string) { + js, err := ioutil.ReadFile(cfgFile) + if err != nil { + errorf("%v", err) + } + var vcfg vetConfig + if err := json.Unmarshal(js, &vcfg); err != nil { + errorf("parsing vet config %s: %v", cfgFile, err) + } + stdImporter = &vcfg + inittypes() + mustTypecheck = true + doPackage(vcfg.GoFiles, nil) +} + // doPackageDir analyzes the single package found in the directory, if there is one, // plus a test package, if there is one. func doPackageDir(directory string) { @@ -353,6 +429,9 @@ func doPackage(names []string, basePkg *Package) *Package { if err != nil { // Note that we only report this error when *verbose. Println(err) + if mustTypecheck { + errorf("%v", err) + } } // Check.