golang.org/x/tools@v0.21.0/go/packages/gopackages/main.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  // 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.
     9  package main
    10  
    11  import (
    12  	"context"
    13  	"encoding/json"
    14  	"flag"
    15  	"fmt"
    16  	"go/types"
    17  	"os"
    18  	"sort"
    19  	"strings"
    20  
    21  	"golang.org/x/tools/go/packages"
    22  	"golang.org/x/tools/go/types/typeutil"
    23  	"golang.org/x/tools/internal/tool"
    24  )
    25  
    26  func main() {
    27  	tool.Main(context.Background(), &application{Mode: "imports"}, os.Args[1:])
    28  }
    29  
    30  type application struct {
    31  	// Embed the basic profiling flags supported by the tool package
    32  	tool.Profile
    33  
    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 (if -mode=syntax)"`
    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)"`
    40  }
    41  
    42  // Name implements tool.Application returning the binary name.
    43  func (app *application) Name() string { return "gopackages" }
    44  
    45  // Usage implements tool.Application returning empty extra argument usage.
    46  func (app *application) Usage() string { return "package..." }
    47  
    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."
    51  }
    52  
    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.
    58  
    59  The mode flag determines how much information is computed and printed
    60  for the specified packages. In order of increasing computational cost,
    61  the legal values are:
    62  
    63   -mode=files     shows only the names of the packages' files.
    64   -mode=imports   also shows the imports. (This is the default.)
    65   -mode=types     loads the compiler's export data and displays the
    66                   type of each exported declaration.
    67   -mode=syntax    parses and type checks syntax trees for the initial
    68                   packages. (With the -private flag, the types of
    69                   non-exported declarations are shown too.)
    70                   Type information for dependencies is obtained from
    71                   compiler export data.
    72   -mode=allsyntax is like -mode=syntax but applied to all dependencies.
    73  
    74  Flags:
    75  `)
    76  	f.PrintDefaults()
    77  }
    78  
    79  // Run takes the args after flag processing and performs the specified query.
    80  func (app *application) Run(ctx context.Context, args ...string) error {
    81  	if len(args) == 0 {
    82  		return tool.CommandLineErrorf("not enough arguments")
    83  	}
    84  
    85  	// Load, parse, and type-check the packages named on the command line.
    86  	cfg := &packages.Config{
    87  		Mode:       packages.LoadSyntax,
    88  		Tests:      app.Test,
    89  		BuildFlags: app.BuildFlags,
    90  	}
    91  
    92  	// -mode flag
    93  	switch strings.ToLower(app.Mode) {
    94  	case "files":
    95  		cfg.Mode = packages.LoadFiles
    96  	case "imports":
    97  		cfg.Mode = packages.LoadImports
    98  	case "types":
    99  		cfg.Mode = packages.LoadTypes
   100  	case "syntax":
   101  		cfg.Mode = packages.LoadSyntax
   102  	case "allsyntax":
   103  		cfg.Mode = packages.LoadAllSyntax
   104  	default:
   105  		return tool.CommandLineErrorf("invalid mode: %s", app.Mode)
   106  	}
   107  	cfg.Mode |= packages.NeedModule
   108  
   109  	lpkgs, err := packages.Load(cfg, args...)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	// -deps: print dependencies too.
   115  	if app.Deps {
   116  		// We can't use packages.All because
   117  		// we need an ordered traversal.
   118  		var all []*packages.Package // postorder
   119  		seen := make(map[*packages.Package]bool)
   120  		var visit func(*packages.Package)
   121  		visit = func(lpkg *packages.Package) {
   122  			if !seen[lpkg] {
   123  				seen[lpkg] = true
   124  
   125  				// visit imports
   126  				var importPaths []string
   127  				for path := range lpkg.Imports {
   128  					importPaths = append(importPaths, path)
   129  				}
   130  				sort.Strings(importPaths) // for determinism
   131  				for _, path := range importPaths {
   132  					visit(lpkg.Imports[path])
   133  				}
   134  
   135  				all = append(all, lpkg)
   136  			}
   137  		}
   138  		for _, lpkg := range lpkgs {
   139  			visit(lpkg)
   140  		}
   141  		lpkgs = all
   142  	}
   143  
   144  	for _, lpkg := range lpkgs {
   145  		app.print(lpkg)
   146  	}
   147  	return nil
   148  }
   149  
   150  func (app *application) print(lpkg *packages.Package) {
   151  	if app.PrintJSON {
   152  		data, _ := json.MarshalIndent(lpkg, "", "\t")
   153  		os.Stdout.Write(data)
   154  		return
   155  	}
   156  	// title
   157  	var kind string
   158  	// TODO(matloob): If IsTest is added back print "test command" or
   159  	// "test package" for packages with IsTest == true.
   160  	if lpkg.Name == "main" {
   161  		kind += "command"
   162  	} else {
   163  		kind += "package"
   164  	}
   165  	fmt.Printf("Go %s %q:\n", kind, lpkg.ID) // unique ID
   166  	if mod := lpkg.Module; mod != nil {
   167  		fmt.Printf("\tmodule %s@%s\n", mod.Path, mod.Version)
   168  	}
   169  	fmt.Printf("\tpackage %s\n", lpkg.Name)
   170  
   171  	// characterize type info
   172  	if lpkg.Types == nil {
   173  		fmt.Printf("\thas no exported type info\n")
   174  	} else if !lpkg.Types.Complete() {
   175  		fmt.Printf("\thas incomplete exported type info\n")
   176  	} else if len(lpkg.Syntax) == 0 {
   177  		fmt.Printf("\thas complete exported type info\n")
   178  	} else {
   179  		fmt.Printf("\thas complete exported type info and typed ASTs\n")
   180  	}
   181  	if lpkg.Types != nil && lpkg.IllTyped && len(lpkg.Errors) == 0 {
   182  		fmt.Printf("\thas an error among its dependencies\n")
   183  	}
   184  
   185  	// source files
   186  	for _, src := range lpkg.GoFiles {
   187  		fmt.Printf("\tfile %s\n", src)
   188  	}
   189  
   190  	// imports
   191  	var lines []string
   192  	for importPath, imp := range lpkg.Imports {
   193  		var line string
   194  		if imp.ID == importPath {
   195  			line = fmt.Sprintf("\timport %q", importPath)
   196  		} else {
   197  			line = fmt.Sprintf("\timport %q => %q", importPath, imp.ID)
   198  		}
   199  		lines = append(lines, line)
   200  	}
   201  	sort.Strings(lines)
   202  	for _, line := range lines {
   203  		fmt.Println(line)
   204  	}
   205  
   206  	// errors
   207  	for _, err := range lpkg.Errors {
   208  		fmt.Printf("\t%s\n", err)
   209  	}
   210  
   211  	// types of package members
   212  	if lpkg.Types != nil {
   213  		qual := types.RelativeTo(lpkg.Types)
   214  		scope := lpkg.Types.Scope()
   215  		for _, name := range scope.Names() {
   216  			obj := scope.Lookup(name)
   217  			if !obj.Exported() && !app.Private {
   218  				continue // skip unexported names
   219  			}
   220  
   221  			fmt.Printf("\t%s\n", types.ObjectString(obj, qual))
   222  			if _, ok := obj.(*types.TypeName); ok {
   223  				for _, meth := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
   224  					if !meth.Obj().Exported() && !app.Private {
   225  						continue // skip unexported names
   226  					}
   227  					fmt.Printf("\t%s\n", types.SelectionString(meth, qual))
   228  				}
   229  			}
   230  		}
   231  	}
   232  
   233  	fmt.Println()
   234  }
   235  
   236  // stringListValue is a flag.Value that accumulates strings.
   237  // e.g. --flag=one --flag=two would produce []string{"one", "two"}.
   238  type stringListValue []string
   239  
   240  func newStringListValue(val []string, p *[]string) *stringListValue {
   241  	*p = val
   242  	return (*stringListValue)(p)
   243  }
   244  
   245  func (ss *stringListValue) Get() interface{} { return []string(*ss) }
   246  
   247  func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) }
   248  
   249  func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil }