gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/tools/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"`
    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  Flags:
    60  `)
    61  	f.PrintDefaults()
    62  }
    63  
    64  // Run takes the args after flag processing and performs the specified query.
    65  func (app *application) Run(ctx context.Context, args ...string) error {
    66  	if len(args) == 0 {
    67  		return tool.CommandLineErrorf("not enough arguments")
    68  	}
    69  
    70  	// Load, parse, and type-check the packages named on the command line.
    71  	cfg := &packages.Config{
    72  		Mode:       packages.LoadSyntax,
    73  		Tests:      app.Test,
    74  		BuildFlags: app.BuildFlags,
    75  	}
    76  
    77  	// -mode flag
    78  	switch strings.ToLower(app.Mode) {
    79  	case "files":
    80  		cfg.Mode = packages.LoadFiles
    81  	case "imports":
    82  		cfg.Mode = packages.LoadImports
    83  	case "types":
    84  		cfg.Mode = packages.LoadTypes
    85  	case "syntax":
    86  		cfg.Mode = packages.LoadSyntax
    87  	case "allsyntax":
    88  		cfg.Mode = packages.LoadAllSyntax
    89  	default:
    90  		return tool.CommandLineErrorf("invalid mode: %s", app.Mode)
    91  	}
    92  
    93  	lpkgs, err := packages.Load(cfg, args...)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	// -deps: print dependencies too.
    99  	if app.Deps {
   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) {
   106  			if !seen[lpkg] {
   107  				seen[lpkg] = true
   108  
   109  				// visit imports
   110  				var importPaths []string
   111  				for path := range lpkg.Imports {
   112  					importPaths = append(importPaths, path)
   113  				}
   114  				sort.Strings(importPaths) // for determinism
   115  				for _, path := range importPaths {
   116  					visit(lpkg.Imports[path])
   117  				}
   118  
   119  				all = append(all, lpkg)
   120  			}
   121  		}
   122  		for _, lpkg := range lpkgs {
   123  			visit(lpkg)
   124  		}
   125  		lpkgs = all
   126  	}
   127  
   128  	for _, lpkg := range lpkgs {
   129  		app.print(lpkg)
   130  	}
   131  	return nil
   132  }
   133  
   134  func (app *application) print(lpkg *packages.Package) {
   135  	if app.PrintJSON {
   136  		data, _ := json.MarshalIndent(lpkg, "", "\t")
   137  		os.Stdout.Write(data)
   138  		return
   139  	}
   140  	// title
   141  	var kind string
   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" {
   145  		kind += "command"
   146  	} else {
   147  		kind += "package"
   148  	}
   149  	fmt.Printf("Go %s %q:\n", kind, lpkg.ID) // unique ID
   150  	fmt.Printf("\tpackage %s\n", lpkg.Name)
   151  
   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")
   159  	} else {
   160  		fmt.Printf("\thas complete exported type info and typed ASTs\n")
   161  	}
   162  	if lpkg.Types != nil && lpkg.IllTyped && len(lpkg.Errors) == 0 {
   163  		fmt.Printf("\thas an error among its dependencies\n")
   164  	}
   165  
   166  	// source files
   167  	for _, src := range lpkg.GoFiles {
   168  		fmt.Printf("\tfile %s\n", src)
   169  	}
   170  
   171  	// imports
   172  	var lines []string
   173  	for importPath, imp := range lpkg.Imports {
   174  		var line string
   175  		if imp.ID == importPath {
   176  			line = fmt.Sprintf("\timport %q", importPath)
   177  		} else {
   178  			line = fmt.Sprintf("\timport %q => %q", importPath, imp.ID)
   179  		}
   180  		lines = append(lines, line)
   181  	}
   182  	sort.Strings(lines)
   183  	for _, line := range lines {
   184  		fmt.Println(line)
   185  	}
   186  
   187  	// errors
   188  	for _, err := range lpkg.Errors {
   189  		fmt.Printf("\t%s\n", err)
   190  	}
   191  
   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
   200  			}
   201  
   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
   207  					}
   208  					fmt.Printf("\t%s\n", types.SelectionString(meth, qual))
   209  				}
   210  			}
   211  		}
   212  	}
   213  
   214  	fmt.Println()
   215  }
   216  
   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
   220  
   221  func newStringListValue(val []string, p *[]string) *stringListValue {
   222  	*p = val
   223  	return (*stringListValue)(p)
   224  }
   225  
   226  func (ss *stringListValue) Get() interface{} { return []string(*ss) }
   227  
   228  func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) }
   229  
   230  func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil }