1
2
3
4
5
6 package doc
7
8 import (
9 "bytes"
10 "context"
11 "flag"
12 "fmt"
13 "go/build"
14 "go/token"
15 "io"
16 "log"
17 "os"
18 "os/exec"
19 "path"
20 "path/filepath"
21 "strings"
22
23 "cmd/go/internal/base"
24 "cmd/go/internal/cfg"
25 "cmd/go/internal/load"
26 "cmd/go/internal/modload"
27 "cmd/go/internal/search"
28 "cmd/internal/telemetry/counter"
29 )
30
31 var CmdDoc = &base.Command{
32 Run: runDoc,
33 UsageLine: "go doc [doc flags] [package|[package.]symbol[.methodOrField]]",
34 CustomFlags: true,
35 Short: "show documentation for package or symbol",
36 Long: `
37 Doc prints the documentation comments associated with the item identified by its
38 arguments (a package, const, func, type, var, method, or struct field)
39 followed by a one-line summary of each of the first-level items "under"
40 that item (package-level declarations for a package, methods for a type,
41 etc.).
42
43 Doc accepts zero, one, or two arguments.
44
45 Given no arguments, that is, when run as
46
47 go doc
48
49 it prints the package documentation for the package in the current directory.
50 If the package is a command (package main), the exported symbols of the package
51 are elided from the presentation unless the -cmd flag is provided.
52
53 When run with one argument, the argument is treated as a Go-syntax-like
54 representation of the item to be documented. What the argument selects depends
55 on what is installed in GOROOT and GOPATH, as well as the form of the argument,
56 which is schematically one of these:
57
58 go doc <pkg>
59 go doc <sym>[.<methodOrField>]
60 go doc [<pkg>.]<sym>[.<methodOrField>]
61 go doc [<pkg>.][<sym>.]<methodOrField>
62
63 The first item in this list matched by the argument is the one whose documentation
64 is printed. (See the examples below.) However, if the argument starts with a capital
65 letter it is assumed to identify a symbol or method in the current directory.
66
67 For packages, the order of scanning is determined lexically in breadth-first order.
68 That is, the package presented is the one that matches the search and is nearest
69 the root and lexically first at its level of the hierarchy. The GOROOT tree is
70 always scanned in its entirety before GOPATH.
71
72 If there is no package specified or matched, the package in the current
73 directory is selected, so "go doc Foo" shows the documentation for symbol Foo in
74 the current package.
75
76 The package path must be either a qualified path or a proper suffix of a
77 path. The go tool's usual package mechanism does not apply: package path
78 elements like . and ... are not implemented by go doc.
79
80 When run with two arguments, the first is a package path (full path or suffix),
81 and the second is a symbol, or symbol with method or struct field:
82
83 go doc <pkg> <sym>[.<methodOrField>]
84
85 In all forms, when matching symbols, lower-case letters in the argument match
86 either case but upper-case letters match exactly. This means that there may be
87 multiple matches of a lower-case argument in a package if different symbols have
88 different cases. If this occurs, documentation for all matches is printed.
89
90 Examples:
91 go doc
92 Show documentation for current package.
93 go doc -http
94 Serve HTML documentation over HTTP for the current package.
95 go doc Foo
96 Show documentation for Foo in the current package.
97 (Foo starts with a capital letter so it cannot match
98 a package path.)
99 go doc encoding/json
100 Show documentation for the encoding/json package.
101 go doc json
102 Shorthand for encoding/json.
103 go doc json.Number (or go doc json.number)
104 Show documentation and method summary for json.Number.
105 go doc json.Number.Int64 (or go doc json.number.int64)
106 Show documentation for json.Number's Int64 method.
107 go doc cmd/doc
108 Show package docs for the doc command.
109 go doc -cmd cmd/doc
110 Show package docs and exported symbols within the doc command.
111 go doc template.new
112 Show documentation for html/template's New function.
113 (html/template is lexically before text/template)
114 go doc text/template.new # One argument
115 Show documentation for text/template's New function.
116 go doc text/template new # Two arguments
117 Show documentation for text/template's New function.
118
119 At least in the current tree, these invocations all print the
120 documentation for json.Decoder's Decode method:
121
122 go doc json.Decoder.Decode
123 go doc json.decoder.decode
124 go doc json.decode
125 cd go/src/encoding/json; go doc decode
126
127 Flags:
128 -all
129 Show all the documentation for the package.
130 -c
131 Respect case when matching symbols.
132 -cmd
133 Treat a command (package main) like a regular package.
134 Otherwise package main's exported symbols are hidden
135 when showing the package's top-level documentation.
136 -ex
137 Include executable examples.
138 -http
139 Serve HTML docs over HTTP.
140 -short
141 One-line representation for each symbol. Cannot be
142 combined with -all.
143 -src
144 Show the full source code for the symbol. This will
145 display the full Go source of its declaration and
146 definition, such as a function definition (including
147 the body), type declaration or enclosing const
148 block. The output may therefore include unexported
149 details.
150 -u
151 Show documentation for unexported as well as exported
152 symbols, methods, and fields.
153 `,
154 }
155
156 func runDoc(ctx context.Context, cmd *base.Command, args []string) {
157 log.SetFlags(0)
158 log.SetPrefix("doc: ")
159 dirsInit()
160 var flagSet flag.FlagSet
161 err := do(ctx, os.Stdout, &flagSet, args)
162 if err != nil {
163 log.Fatal(err)
164 }
165 }
166
167 var (
168 unexported bool
169 matchCase bool
170 chdir string
171 showAll bool
172 showCmd bool
173 showEx bool
174 showSrc bool
175 short bool
176 serveHTTP bool
177 )
178
179
180 func usage(flagSet *flag.FlagSet) {
181 fmt.Fprintf(os.Stderr, "Usage of [go] doc:\n")
182 fmt.Fprintf(os.Stderr, "\tgo doc\n")
183 fmt.Fprintf(os.Stderr, "\tgo doc <pkg>\n")
184 fmt.Fprintf(os.Stderr, "\tgo doc <sym>[.<methodOrField>]\n")
185 fmt.Fprintf(os.Stderr, "\tgo doc [<pkg>.]<sym>[.<methodOrField>]\n")
186 fmt.Fprintf(os.Stderr, "\tgo doc [<pkg>.][<sym>.]<methodOrField>\n")
187 fmt.Fprintf(os.Stderr, "\tgo doc <pkg> <sym>[.<methodOrField>]\n")
188 fmt.Fprintf(os.Stderr, "For more information run\n")
189 fmt.Fprintf(os.Stderr, "\tgo help doc\n\n")
190 os.Exit(2)
191 }
192
193
194 func do(ctx context.Context, writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
195 flagSet.Usage = func() { usage(flagSet) }
196 unexported = false
197 matchCase = false
198 flagSet.StringVar(&chdir, "C", "", "change to `dir` before running command")
199 flagSet.BoolVar(&unexported, "u", false, "show unexported symbols as well as exported")
200 flagSet.BoolVar(&matchCase, "c", false, "symbol matching honors case (paths not affected)")
201 flagSet.BoolVar(&showAll, "all", false, "show all documentation for package")
202 flagSet.BoolVar(&showEx, "ex", false, "show executable examples for symbol or package")
203 flagSet.BoolVar(&showCmd, "cmd", false, "show symbols with package docs even if package is a command")
204 flagSet.BoolVar(&showSrc, "src", false, "show source code for symbol")
205 flagSet.BoolVar(&short, "short", false, "one-line representation for each symbol")
206 flagSet.BoolVar(&serveHTTP, "http", false, "serve HTML docs over HTTP")
207 flagSet.Parse(args)
208 counter.CountFlags("doc/flag:", *flagSet)
209 if chdir != "" {
210 if err := os.Chdir(chdir); err != nil {
211 return err
212 }
213 }
214 if showAll && short {
215 return fmt.Errorf("cannot combine -all and -short")
216 }
217 if serveHTTP {
218
219
220
221 if len(flagSet.Args()) == 0 {
222 mod, err := runCmd(append(os.Environ(), "GOWORK=off"), "go", "list", "-m")
223 if err == nil && mod != "" && mod != "command-line-arguments" {
224
225 return doPkgsite(ctx, mod, "")
226 }
227 gowork, err := runCmd(nil, "go", "env", "GOWORK")
228 if err == nil && gowork != "" {
229
230
231 return doPkgsite(ctx, "", "")
232 }
233
234 return doPkgsite(ctx, "std", "")
235 }
236
237
238
239
240 writer = io.Discard
241 }
242 var paths []string
243 var symbol, method string
244
245 dirs.Reset()
246 for i := 0; ; i++ {
247 buildPackage, userPath, sym, more := parseArgs(ctx, flagSet, flagSet.Args())
248 if i > 0 && !more {
249 return failMessage(paths, symbol, method)
250 }
251 if buildPackage == nil {
252 return fmt.Errorf("no such package: %s", userPath)
253 }
254
255
256
257 if buildPackage.ImportPath == "builtin" {
258 unexported = true
259 }
260
261 symbol, method = parseSymbol(flagSet, sym)
262 pkg := parsePackage(writer, buildPackage, userPath)
263 paths = append(paths, pkg.prettyPath())
264
265 defer func() {
266 pkg.flush()
267 e := recover()
268 if e == nil {
269 return
270 }
271 pkgError, ok := e.(PackageError)
272 if ok {
273 err = pkgError
274 return
275 }
276 panic(e)
277 }()
278
279 var found bool
280 switch {
281 case symbol == "":
282 pkg.packageDoc()
283 found = true
284 case method == "":
285 if pkg.symbolDoc(symbol) {
286 found = true
287 }
288 case pkg.printMethodDoc(symbol, method):
289 found = true
290 case pkg.printFieldDoc(symbol, method):
291 found = true
292 }
293 if found {
294 if serveHTTP {
295 path, fragment, err := objectPath(userPath, pkg, symbol, method)
296 if err != nil {
297 return err
298 }
299 return doPkgsite(ctx, path, fragment)
300 }
301 return nil
302 }
303 }
304 }
305
306 func runCmd(env []string, cmdline ...string) (string, error) {
307 var stdout, stderr strings.Builder
308 cmd := exec.Command(cmdline[0], cmdline[1:]...)
309 cmd.Env = env
310 cmd.Stdout = &stdout
311 cmd.Stderr = &stderr
312 if err := cmd.Run(); err != nil {
313 return "", fmt.Errorf("go doc: %s: %v\n%s\n", strings.Join(cmdline, " "), err, stderr.String())
314 }
315 return strings.TrimSpace(stdout.String()), nil
316 }
317
318
319 func objectPath(userPath string, pkg *Package, symbol, method string) (string, string, error) {
320 var err error
321 path := pkg.build.ImportPath
322 if path == "." {
323
324
325
326 path, err = runCmd(nil, "go", "list", userPath)
327 if err != nil {
328 return "", "", err
329 }
330 }
331
332 object := symbol
333 if symbol != "" && method != "" {
334 object = symbol + "." + method
335 }
336 return path, object, nil
337 }
338
339
340 func failMessage(paths []string, symbol, method string) error {
341 var b bytes.Buffer
342 if len(paths) > 1 {
343 b.WriteString("s")
344 }
345 b.WriteString(" ")
346 for i, path := range paths {
347 if i > 0 {
348 b.WriteString(", ")
349 }
350 b.WriteString(path)
351 }
352 if method == "" {
353 return fmt.Errorf("no symbol %s in package%s", symbol, &b)
354 }
355 return fmt.Errorf("no method or field %s.%s in package%s", symbol, method, &b)
356 }
357
358
359
360
361
362
363
364
365
366
367
368
369 func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *load.Package, path, symbol string, more bool) {
370 wd, err := os.Getwd()
371 if err != nil {
372 log.Fatal(err)
373 }
374 loader := modload.NewLoader()
375 if testGOPATH {
376 loader = modload.NewDisabledState()
377 }
378 if len(args) > 0 && strings.Index(args[0], "@") >= 0 {
379
380 loader.ForceUseModules = true
381 loader.RootMode = modload.NoRoot
382 modload.Init(loader)
383 } else if loader.WillBeEnabled() {
384 loader.InitWorkfile()
385 modload.Init(loader)
386 modload.LoadModFile(loader, context.TODO())
387 }
388
389 if len(args) == 0 {
390
391 return mustLoadPackage(ctx, loader, wd), "", "", false
392 }
393 arg := args[0]
394
395 var version string
396 if i := strings.Index(arg, "@"); i >= 0 {
397 arg, version = arg[:i], arg[i+1:]
398 }
399
400
401
402
403 if isDotSlash(arg) {
404 arg = filepath.Join(wd, arg)
405 }
406 if version != "" && (build.IsLocalImport(filepath.ToSlash(arg)) || filepath.IsAbs(arg)) {
407 log.Fatal("cannot use @version with local or absolute paths")
408 }
409
410 importPkg := func(p string) (*load.Package, error) {
411 if version != "" {
412 return loadVersioned(ctx, loader, p, version)
413 }
414 return loadPackage(ctx, loader, p)
415 }
416
417 switch len(args) {
418 default:
419 usage(flagSet)
420 case 1:
421
422 case 2:
423
424 pkg, err := importPkg(arg)
425 if err == nil {
426 return pkg, arg, args[1], false
427 }
428 if p, findErr, ok := findPackage(arg, importPkg); ok {
429 pkg = p
430 return pkg, arg, args[1], true
431 } else if findErr != nil {
432 err = findErr
433 }
434 if version != "" {
435 log.Fatal(err)
436 }
437 return nil, arg, args[1], false
438 }
439
440
441
442
443
444
445 var importErr error
446 if filepath.IsAbs(arg) {
447 pkg, importErr = loadPackage(ctx, loader, arg)
448 if importErr == nil {
449 return pkg, arg, "", false
450 }
451 } else {
452 pkg, importErr = importPkg(arg)
453 if importErr == nil {
454 return pkg, arg, "", false
455 }
456 }
457
458
459
460
461 if !strings.ContainsAny(arg, `/\`) && token.IsExported(arg) {
462 pkg, err := loadPackage(ctx, loader, ".")
463 if err == nil {
464 return pkg, "", arg, false
465 }
466 }
467
468
469 slash := strings.LastIndex(arg, "/")
470
471
472
473
474
475 var period int
476
477
478 for start := slash + 1; start < len(arg); start = period + 1 {
479 period = strings.Index(arg[start:], ".")
480 symbol := ""
481 if period < 0 {
482 period = len(arg)
483 } else {
484 period += start
485 symbol = arg[period+1:]
486 }
487
488 pkg, err := loadPackage(ctx, loader, arg[0:period])
489 if err == nil {
490 return pkg, arg[0:period], symbol, false
491 }
492
493
494 pkgName := arg[:period]
495 if pkg, _, ok := findPackage(pkgName, importPkg); ok {
496 return pkg, arg[0:period], symbol, true
497 }
498 dirs.Reset()
499 }
500
501
502 if slash < 0 && !isDotSlash(arg) && !filepath.IsAbs(arg) {
503 if pkgPath, v, ok := inferVersion(arg); ok {
504 if version == "" {
505 version = v
506 }
507 pkg, err := loadVersioned(ctx, loader, pkgPath, version)
508 if err == nil {
509 return pkg, pkgPath, "", false
510 }
511 }
512 }
513
514 if version != "" {
515 if importErr != nil {
516 log.Fatal(importErr)
517 }
518 log.Fatalf("no such package %q at version %q", arg, version)
519 }
520
521
522 if slash >= 0 {
523
524
525
526
527
528
529 importErrStr := importErr.Error()
530 if strings.Contains(importErrStr, arg[:period]) {
531 log.Fatal(importErrStr)
532 } else {
533 log.Fatalf("no such package %s: %s", arg[:period], importErrStr)
534 }
535 }
536
537 return mustLoadPackage(ctx, loader, wd), "", arg, false
538 }
539
540
541
542
543
544
545
546
547 func findPackage(pkg string, importPkg func(string) (*load.Package, error)) (*load.Package, error, bool) {
548 var lastErr error
549 for _, m := range matchingPackages(pkg) {
550 p, err := importPkg(m.importPath)
551 if err == nil {
552 dirs.offset = m.nextOffset
553 return p, nil, true
554 }
555 lastErr = err
556 }
557 return nil, lastErr, false
558 }
559
560 type packageMatch struct {
561 importPath string
562 nextOffset int
563 }
564
565 func matchingPackages(pkg string) []packageMatch {
566
567
568 if filepath.IsAbs(pkg) {
569 if dirs.offset == 0 {
570 dirs.offset = -1
571 return []packageMatch{{importPath: pkg, nextOffset: -1}}
572 }
573 return nil
574 }
575 if pkg == "" || token.IsExported(pkg) {
576 return nil
577 }
578 pkg = path.Clean(pkg)
579 pkgSuffix := "/" + pkg
580 deferInternal := !hasPathElement(pkg, "internal")
581
582
583
584 var matches []packageMatch
585 var nonInternal []packageMatch
586 var internal []packageMatch
587 for {
588 d, ok := dirs.Next()
589 if !ok {
590 break
591 }
592 if d.importPath != pkg && !strings.HasSuffix(d.importPath, pkgSuffix) {
593 continue
594 }
595 m := packageMatch{importPath: d.importPath, nextOffset: dirs.offset}
596 if !deferInternal {
597 matches = append(matches, m)
598 } else if hasPathElement(d.importPath, "internal") {
599 internal = append(internal, m)
600 } else {
601 nonInternal = append(nonInternal, m)
602 }
603 }
604 if !deferInternal {
605 return matches
606 }
607 if len(nonInternal) == 0 {
608 return internal
609 }
610
611
612 nonInternal[len(nonInternal)-1].nextOffset = dirs.offset
613 return append(nonInternal, internal...)
614 }
615
616 func loadPackage(ctx context.Context, loader *modload.Loader, pattern string) (*load.Package, error) {
617 if !search.NewMatch(pattern).IsLiteral() {
618 return nil, fmt.Errorf("pattern %q does not specify a single package", pattern)
619 }
620
621 pkgOpts := load.PackageOpts{
622 IgnoreImports: true,
623 SuppressBuildInfo: true,
624 SuppressEmbedFiles: true,
625 }
626 pkgs := load.PackagesAndErrors(loader, ctx, pkgOpts, []string{pattern})
627
628 if len(pkgs) != 1 {
629 return nil, fmt.Errorf("path %q matched multiple packages", pattern)
630 }
631
632 p := pkgs[0]
633 if p.Error != nil {
634 return nil, p.Error
635 }
636 return p, nil
637 }
638
639 func mustLoadPackage(ctx context.Context, loader *modload.Loader, dir string) *load.Package {
640 pkg, err := loadPackage(ctx, loader, dir)
641 if err != nil {
642 log.Fatal(err)
643 }
644 return pkg
645 }
646
647
648
649
650
651 var dotPaths = []string{
652 `./`,
653 `../`,
654 `.\`,
655 `..\`,
656 }
657
658
659
660 func isDotSlash(arg string) bool {
661 if arg == "." || arg == ".." {
662 return true
663 }
664 for _, dotPath := range dotPaths {
665 if strings.HasPrefix(arg, dotPath) {
666 return true
667 }
668 }
669 return false
670 }
671
672
673
674
675 func parseSymbol(flagSet *flag.FlagSet, str string) (symbol, method string) {
676 if str == "" {
677 return
678 }
679 elem := strings.Split(str, ".")
680 switch len(elem) {
681 case 1:
682 case 2:
683 method = elem[1]
684 default:
685 log.Printf("too many periods in symbol specification")
686 usage(flagSet)
687 }
688 symbol = elem[0]
689 return
690 }
691
692
693
694
695 func isExported(name string) bool {
696 return unexported || token.IsExported(name)
697 }
698
699 func hasPathElement(p, elem string) bool {
700 for part := range strings.SplitSeq(path.Clean(p), "/") {
701 if part == elem {
702 return true
703 }
704 }
705 return false
706 }
707
708
709 func splitGopath() []string {
710 return filepath.SplitList(cfg.BuildContext.GOPATH)
711 }
712
View as plain text