231 lines
6.3 KiB
Go
231 lines
6.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.
|
|
|
|
// The gopackages command is a diagnostic tool that demonstrates
|
|
// how to use golang.org/x/tools/go/packages to load, parse,
|
|
// type-check, and print one or more Go packages.
|
|
// Its precise output is unspecified and may change.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"go/types"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/packages"
|
|
"golang.org/x/tools/go/types/typeutil"
|
|
"golang.org/x/tools/internal/tool"
|
|
)
|
|
|
|
func main() {
|
|
tool.Main(context.Background(), &application{Mode: "imports"}, os.Args[1:])
|
|
}
|
|
|
|
type application struct {
|
|
// Embed the basic profiling flags supported by the tool package
|
|
tool.Profile
|
|
|
|
Deps bool `flag:"deps" help:"show dependencies too"`
|
|
Test bool `flag:"test" help:"include any tests implied by the patterns"`
|
|
Mode string `flag:"mode" help:"mode (one of files, imports, types, syntax, allsyntax)"`
|
|
Private bool `flag:"private" help:"show non-exported declarations too"`
|
|
PrintJSON bool `flag:"json" help:"print package in JSON form"`
|
|
BuildFlags stringListValue `flag:"buildflag" help:"pass argument to underlying build system (may be repeated)"`
|
|
}
|
|
|
|
// Name implements tool.Application returning the binary name.
|
|
func (app *application) Name() string { return "gopackages" }
|
|
|
|
// Usage implements tool.Application returning empty extra argument usage.
|
|
func (app *application) Usage() string { return "package..." }
|
|
|
|
// ShortHelp implements tool.Application returning the main binary help.
|
|
func (app *application) ShortHelp() string {
|
|
return "gopackages loads, parses, type-checks, and prints one or more Go packages."
|
|
}
|
|
|
|
// DetailedHelp implements tool.Application returning the main binary help.
|
|
func (app *application) DetailedHelp(f *flag.FlagSet) {
|
|
fmt.Fprint(f.Output(), `
|
|
Packages are specified using the notation of "go list",
|
|
or other underlying build system.
|
|
|
|
Flags:
|
|
`)
|
|
f.PrintDefaults()
|
|
}
|
|
|
|
// Run takes the args after flag processing and performs the specified query.
|
|
func (app *application) Run(ctx context.Context, args ...string) error {
|
|
if len(args) == 0 {
|
|
return tool.CommandLineErrorf("not enough arguments")
|
|
}
|
|
|
|
// Load, parse, and type-check the packages named on the command line.
|
|
cfg := &packages.Config{
|
|
Mode: packages.LoadSyntax,
|
|
Tests: app.Test,
|
|
BuildFlags: app.BuildFlags,
|
|
}
|
|
|
|
// -mode flag
|
|
switch strings.ToLower(app.Mode) {
|
|
case "files":
|
|
cfg.Mode = packages.LoadFiles
|
|
case "imports":
|
|
cfg.Mode = packages.LoadImports
|
|
case "types":
|
|
cfg.Mode = packages.LoadTypes
|
|
case "syntax":
|
|
cfg.Mode = packages.LoadSyntax
|
|
case "allsyntax":
|
|
cfg.Mode = packages.LoadAllSyntax
|
|
default:
|
|
return tool.CommandLineErrorf("invalid mode: %s", app.Mode)
|
|
}
|
|
|
|
lpkgs, err := packages.Load(cfg, args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// -deps: print dependencies too.
|
|
if app.Deps {
|
|
// We can't use packages.All because
|
|
// we need an ordered traversal.
|
|
var all []*packages.Package // postorder
|
|
seen := make(map[*packages.Package]bool)
|
|
var visit func(*packages.Package)
|
|
visit = func(lpkg *packages.Package) {
|
|
if !seen[lpkg] {
|
|
seen[lpkg] = true
|
|
|
|
// visit imports
|
|
var importPaths []string
|
|
for path := range lpkg.Imports {
|
|
importPaths = append(importPaths, path)
|
|
}
|
|
sort.Strings(importPaths) // for determinism
|
|
for _, path := range importPaths {
|
|
visit(lpkg.Imports[path])
|
|
}
|
|
|
|
all = append(all, lpkg)
|
|
}
|
|
}
|
|
for _, lpkg := range lpkgs {
|
|
visit(lpkg)
|
|
}
|
|
lpkgs = all
|
|
}
|
|
|
|
for _, lpkg := range lpkgs {
|
|
app.print(lpkg)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (app *application) print(lpkg *packages.Package) {
|
|
if app.PrintJSON {
|
|
data, _ := json.MarshalIndent(lpkg, "", "\t")
|
|
os.Stdout.Write(data)
|
|
return
|
|
}
|
|
// title
|
|
var kind string
|
|
// TODO(matloob): If IsTest is added back print "test command" or
|
|
// "test package" for packages with IsTest == true.
|
|
if lpkg.Name == "main" {
|
|
kind += "command"
|
|
} else {
|
|
kind += "package"
|
|
}
|
|
fmt.Printf("Go %s %q:\n", kind, lpkg.ID) // unique ID
|
|
fmt.Printf("\tpackage %s\n", lpkg.Name)
|
|
|
|
// characterize type info
|
|
if lpkg.Types == nil {
|
|
fmt.Printf("\thas no exported type info\n")
|
|
} else if !lpkg.Types.Complete() {
|
|
fmt.Printf("\thas incomplete exported type info\n")
|
|
} else if len(lpkg.Syntax) == 0 {
|
|
fmt.Printf("\thas complete exported type info\n")
|
|
} else {
|
|
fmt.Printf("\thas complete exported type info and typed ASTs\n")
|
|
}
|
|
if lpkg.Types != nil && lpkg.IllTyped && len(lpkg.Errors) == 0 {
|
|
fmt.Printf("\thas an error among its dependencies\n")
|
|
}
|
|
|
|
// source files
|
|
for _, src := range lpkg.GoFiles {
|
|
fmt.Printf("\tfile %s\n", src)
|
|
}
|
|
|
|
// imports
|
|
var lines []string
|
|
for importPath, imp := range lpkg.Imports {
|
|
var line string
|
|
if imp.ID == importPath {
|
|
line = fmt.Sprintf("\timport %q", importPath)
|
|
} else {
|
|
line = fmt.Sprintf("\timport %q => %q", importPath, imp.ID)
|
|
}
|
|
lines = append(lines, line)
|
|
}
|
|
sort.Strings(lines)
|
|
for _, line := range lines {
|
|
fmt.Println(line)
|
|
}
|
|
|
|
// errors
|
|
for _, err := range lpkg.Errors {
|
|
fmt.Printf("\t%s\n", err)
|
|
}
|
|
|
|
// package members (TypeCheck or WholeProgram mode)
|
|
if lpkg.Types != nil {
|
|
qual := types.RelativeTo(lpkg.Types)
|
|
scope := lpkg.Types.Scope()
|
|
for _, name := range scope.Names() {
|
|
obj := scope.Lookup(name)
|
|
if !obj.Exported() && !app.Private {
|
|
continue // skip unexported names
|
|
}
|
|
|
|
fmt.Printf("\t%s\n", types.ObjectString(obj, qual))
|
|
if _, ok := obj.(*types.TypeName); ok {
|
|
for _, meth := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
|
|
if !meth.Obj().Exported() && !app.Private {
|
|
continue // skip unexported names
|
|
}
|
|
fmt.Printf("\t%s\n", types.SelectionString(meth, qual))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fmt.Println()
|
|
}
|
|
|
|
// stringListValue is a flag.Value that accumulates strings.
|
|
// e.g. --flag=one --flag=two would produce []string{"one", "two"}.
|
|
type stringListValue []string
|
|
|
|
func newStringListValue(val []string, p *[]string) *stringListValue {
|
|
*p = val
|
|
return (*stringListValue)(p)
|
|
}
|
|
|
|
func (ss *stringListValue) Get() interface{} { return []string(*ss) }
|
|
|
|
func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) }
|
|
|
|
func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil }
|