1
2
3
4
5
6
7
8
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
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
43 func (app *application) Name() string { return "gopackages" }
44
45
46 func (app *application) Usage() string { return "package..." }
47
48
49 func (app *application) ShortHelp() string {
50 return "gopackages loads, parses, type-checks, and prints one or more Go packages."
51 }
52
53
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
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
86 cfg := &packages.Config{
87 Mode: packages.LoadSyntax,
88 Tests: app.Test,
89 BuildFlags: app.BuildFlags,
90 }
91
92
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
115 if app.Deps {
116
117
118 var all []*packages.Package
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
126 var importPaths []string
127 for path := range lpkg.Imports {
128 importPaths = append(importPaths, path)
129 }
130 sort.Strings(importPaths)
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
157 var kind string
158
159
160 if lpkg.Name == "main" {
161 kind += "command"
162 } else {
163 kind += "package"
164 }
165 fmt.Printf("Go %s %q:\n", kind, lpkg.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
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
186 for _, src := range lpkg.GoFiles {
187 fmt.Printf("\tfile %s\n", src)
188 }
189
190
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
207 for _, err := range lpkg.Errors {
208 fmt.Printf("\t%s\n", err)
209 }
210
211
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
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
226 }
227 fmt.Printf("\t%s\n", types.SelectionString(meth, qual))
228 }
229 }
230 }
231 }
232
233 fmt.Println()
234 }
235
236
237
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 }
250
View as plain text