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.
5 // The gopackages command is a diagnostic tool that demonstrates
6 // how to use golang.org/x/tools/go/packages to load, parse,
7 // type-check, and print one or more Go packages.
8 // Its precise output is unspecified and may change.
21 "golang.org/x/tools/go/packages"
22 "golang.org/x/tools/go/types/typeutil"
23 "golang.org/x/tools/internal/tool"
27 tool.Main(context.Background(), &application{Mode: "imports"}, os.Args[1:])
30 type application struct {
31 // Embed the basic profiling flags supported by the tool package
34 Deps bool `flag:"deps" help:"show dependencies too"`
35 Test bool `flag:"test" help:"include any tests implied by the patterns"`
36 Mode string `flag:"mode" help:"mode (one of files, imports, types, syntax, allsyntax)"`
37 Private bool `flag:"private" help:"show non-exported declarations too"`
38 PrintJSON bool `flag:"json" help:"print package in JSON form"`
39 BuildFlags stringListValue `flag:"buildflag" help:"pass argument to underlying build system (may be repeated)"`
42 // Name implements tool.Application returning the binary name.
43 func (app *application) Name() string { return "gopackages" }
45 // Usage implements tool.Application returning empty extra argument usage.
46 func (app *application) Usage() string { return "package..." }
48 // ShortHelp implements tool.Application returning the main binary help.
49 func (app *application) ShortHelp() string {
50 return "gopackages loads, parses, type-checks, and prints one or more Go packages."
53 // DetailedHelp implements tool.Application returning the main binary help.
54 func (app *application) DetailedHelp(f *flag.FlagSet) {
55 fmt.Fprint(f.Output(), `
56 Packages are specified using the notation of "go list",
57 or other underlying build system.
64 // Run takes the args after flag processing and performs the specified query.
65 func (app *application) Run(ctx context.Context, args ...string) error {
67 return tool.CommandLineErrorf("not enough arguments")
70 // Load, parse, and type-check the packages named on the command line.
71 cfg := &packages.Config{
72 Mode: packages.LoadSyntax,
74 BuildFlags: app.BuildFlags,
78 switch strings.ToLower(app.Mode) {
80 cfg.Mode = packages.LoadFiles
82 cfg.Mode = packages.LoadImports
84 cfg.Mode = packages.LoadTypes
86 cfg.Mode = packages.LoadSyntax
88 cfg.Mode = packages.LoadAllSyntax
90 return tool.CommandLineErrorf("invalid mode: %s", app.Mode)
93 lpkgs, err := packages.Load(cfg, args...)
98 // -deps: print dependencies too.
100 // We can't use packages.All because
101 // we need an ordered traversal.
102 var all []*packages.Package // postorder
103 seen := make(map[*packages.Package]bool)
104 var visit func(*packages.Package)
105 visit = func(lpkg *packages.Package) {
110 var importPaths []string
111 for path := range lpkg.Imports {
112 importPaths = append(importPaths, path)
114 sort.Strings(importPaths) // for determinism
115 for _, path := range importPaths {
116 visit(lpkg.Imports[path])
119 all = append(all, lpkg)
122 for _, lpkg := range lpkgs {
128 for _, lpkg := range lpkgs {
134 func (app *application) print(lpkg *packages.Package) {
136 data, _ := json.MarshalIndent(lpkg, "", "\t")
137 os.Stdout.Write(data)
142 // TODO(matloob): If IsTest is added back print "test command" or
143 // "test package" for packages with IsTest == true.
144 if lpkg.Name == "main" {
149 fmt.Printf("Go %s %q:\n", kind, lpkg.ID) // unique ID
150 fmt.Printf("\tpackage %s\n", lpkg.Name)
152 // characterize type info
153 if lpkg.Types == nil {
154 fmt.Printf("\thas no exported type info\n")
155 } else if !lpkg.Types.Complete() {
156 fmt.Printf("\thas incomplete exported type info\n")
157 } else if len(lpkg.Syntax) == 0 {
158 fmt.Printf("\thas complete exported type info\n")
160 fmt.Printf("\thas complete exported type info and typed ASTs\n")
162 if lpkg.Types != nil && lpkg.IllTyped && len(lpkg.Errors) == 0 {
163 fmt.Printf("\thas an error among its dependencies\n")
167 for _, src := range lpkg.GoFiles {
168 fmt.Printf("\tfile %s\n", src)
173 for importPath, imp := range lpkg.Imports {
175 if imp.ID == importPath {
176 line = fmt.Sprintf("\timport %q", importPath)
178 line = fmt.Sprintf("\timport %q => %q", importPath, imp.ID)
180 lines = append(lines, line)
183 for _, line := range lines {
188 for _, err := range lpkg.Errors {
189 fmt.Printf("\t%s\n", err)
192 // package members (TypeCheck or WholeProgram mode)
193 if lpkg.Types != nil {
194 qual := types.RelativeTo(lpkg.Types)
195 scope := lpkg.Types.Scope()
196 for _, name := range scope.Names() {
197 obj := scope.Lookup(name)
198 if !obj.Exported() && !app.Private {
199 continue // skip unexported names
202 fmt.Printf("\t%s\n", types.ObjectString(obj, qual))
203 if _, ok := obj.(*types.TypeName); ok {
204 for _, meth := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
205 if !meth.Obj().Exported() && !app.Private {
206 continue // skip unexported names
208 fmt.Printf("\t%s\n", types.SelectionString(meth, qual))
217 // stringListValue is a flag.Value that accumulates strings.
218 // e.g. --flag=one --flag=two would produce []string{"one", "two"}.
219 type stringListValue []string
221 func newStringListValue(val []string, p *[]string) *stringListValue {
223 return (*stringListValue)(p)
226 func (ss *stringListValue) Get() interface{} { return []string(*ss) }
228 func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) }
230 func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil }