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 }