Source file src/cmd/go/internal/doc/doc.go

     1  // Copyright 2015 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  // Package doc implements the “go doc” command.
     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   // -u flag
   169  	matchCase  bool   // -c flag
   170  	chdir      string // -C flag
   171  	showAll    bool   // -all flag
   172  	showCmd    bool   // -cmd flag
   173  	showEx     bool   // -ex flag
   174  	showSrc    bool   // -src flag
   175  	short      bool   // -short flag
   176  	serveHTTP  bool   // -http flag
   177  )
   178  
   179  // usage is a replacement usage function for the flags package.
   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  // do is the workhorse, broken out of runDoc to make testing easier.
   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  		// Special case: if there are no arguments, try to go to an appropriate page
   219  		// depending on whether we're in a module or workspace. The pkgsite homepage
   220  		// is often not the most useful page.
   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  				// If there's a module, go to the module's doc page.
   225  				return doPkgsite(ctx, mod, "")
   226  			}
   227  			gowork, err := runCmd(nil, "go", "env", "GOWORK")
   228  			if err == nil && gowork != "" {
   229  				// Outside a module, but in a workspace, go to the home page
   230  				// with links to each of the modules' pages.
   231  				return doPkgsite(ctx, "", "")
   232  			}
   233  			// Outside a module or workspace, go to the documentation for the standard library.
   234  			return doPkgsite(ctx, "std", "")
   235  		}
   236  
   237  		// If args are provided, we need to figure out which page to open on the pkgsite
   238  		// instance. Run the logic below to determine a match for a symbol, method,
   239  		// or field, but don't actually print the documentation to the output.
   240  		writer = io.Discard
   241  	}
   242  	var paths []string
   243  	var symbol, method string
   244  	// Loop until something is printed.
   245  	dirs.Reset()
   246  	for i := 0; ; i++ {
   247  		buildPackage, userPath, sym, more := parseArgs(ctx, flagSet, flagSet.Args())
   248  		if i > 0 && !more { // Ignore the "more" bit on the first iteration.
   249  			return failMessage(paths, symbol, method)
   250  		}
   251  		if buildPackage == nil {
   252  			return fmt.Errorf("no such package: %s", userPath)
   253  		}
   254  
   255  		// The builtin package needs special treatment: its symbols are lower
   256  		// case but we want to see them, always.
   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() // The package exists, so we got some output.
   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  // returns a path followed by a fragment (or an error)
   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  		// go/build couldn't determine the import path, probably
   324  		// because this was a relative path into a module. Use
   325  		// go list to get the import path.
   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  // failMessage creates a nicely formatted error message when there is no result to show.
   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  // parseArgs analyzes the arguments (if any) and returns the package
   359  // it represents, the part of the argument the user used to identify
   360  // the path (or "" if it's the current package) and the symbol
   361  // (possibly with a .method) within that package.
   362  // parseSymbol is used to analyze the symbol itself.
   363  // The boolean final argument reports whether it is possible that
   364  // there may be more directories worth looking at. It will only
   365  // be true if the package path is a partial match for some directory
   366  // and there may be more matches. For example, if the argument
   367  // is rand.Float64, we must scan both crypto/rand and math/rand
   368  // to find the symbol, and the first call will return crypto/rand, true.
   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  		// Version query: force no root
   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  		// Easy: current directory.
   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  	// We have an argument. If it is a directory name beginning with . or ..,
   401  	// use the absolute path name. This discriminates "./errors" from "errors"
   402  	// if the current directory contains a non-standard errors package.
   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  		// Done below.
   422  	case 2:
   423  		// Package must be findable and importable.
   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  	// Usual case: one argument.
   440  	// If it contains slashes, it begins with either a package path
   441  	// or an absolute directory.
   442  	// First, is it a complete package path as it is? If so, we are done.
   443  	// This avoids confusion over package paths that have other
   444  	// package paths as their prefix.
   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  	// Another disambiguator: If the argument starts with an upper
   458  	// case letter, it can only be a symbol in the current directory.
   459  	// Kills the problem caused by case-insensitive file systems
   460  	// matching an upper case name as a package name.
   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  	// If it has a slash, it must be a package path but there is a symbol.
   468  	// It's the last package path we care about.
   469  	slash := strings.LastIndex(arg, "/")
   470  	// There may be periods in the package path before or after the slash
   471  	// and between a symbol and method.
   472  	// Split the string at various periods to see what we find.
   473  	// In general there may be ambiguities but this should almost always
   474  	// work.
   475  	var period int
   476  	// slash+1: if there's no slash, the value is -1 and start is 0; otherwise
   477  	// start is the byte after the slash.
   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  		// Have we identified a package already?
   488  		pkg, err := loadPackage(ctx, loader, arg[0:period])
   489  		if err == nil {
   490  			return pkg, arg[0:period], symbol, false
   491  		}
   492  		// See if we have the basename or tail of a package, as in json for encoding/json
   493  		// or ivy/value for robpike.io/ivy/value.
   494  		pkgName := arg[:period]
   495  		if pkg, _, ok := findPackage(pkgName, importPkg); ok {
   496  			return pkg, arg[0:period], symbol, true
   497  		}
   498  		dirs.Reset() // Next iteration of for loop must scan all the directories again.
   499  	}
   500  
   501  	// Try inference from $PATH before giving up.
   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  	// If it has a slash, we've failed.
   522  	if slash >= 0 {
   523  		// build.Import should always include the path in its error message,
   524  		// and we should avoid repeating it. Unfortunately, build.Import doesn't
   525  		// return a structured error. That can't easily be fixed, since it
   526  		// invokes 'go list' and returns the error text from the loaded package.
   527  		// TODO(golang.org/issue/34750): load using golang.org/x/tools/go/packages
   528  		// instead of go/build.
   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  	// Guess it's a symbol in the current directory.
   537  	return mustLoadPackage(ctx, loader, wd), "", arg, false
   538  }
   539  
   540  // findPackage returns the first successfully imported package matching the query pkg.
   541  // It updates dirs.offset to the candidate's nextOffset so that subsequent searches
   542  // work across retry loops.
   543  //
   544  // (pkg, nil, true)  => imported a package
   545  // (nil, err, false) => all imports failed (along with last error)
   546  // (nil, nil, false) => no matching packages
   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  	// TODO(adonovan): once go1.28 tree opens, refactor matchingPackages to use
   567  	// iter.Seq[string] to encapsulate iteration state and avoid global vars.
   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) { // Upper case symbol cannot be a package name.
   576  		return nil
   577  	}
   578  	pkg = path.Clean(pkg)
   579  	pkgSuffix := "/" + pkg
   580  	deferInternal := !hasPathElement(pkg, "internal")
   581  	// Prefer non-internal packages unless pkg is itself internal.
   582  	// Scanning directories is cheap compared to loading packages, so
   583  	// collect all matches and sort internal matches to the end.
   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  	// If the last non-internal match is returned, later retries should
   611  	// not fall through to internal-only matches for the same package path.
   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  // dotPaths lists all the dotted paths legal on Unix-like and
   648  // Windows-like file systems. We check them all, as the chance
   649  // of error is minute and even on Windows people will use ./
   650  // sometimes.
   651  var dotPaths = []string{
   652  	`./`,
   653  	`../`,
   654  	`.\`,
   655  	`..\`,
   656  }
   657  
   658  // isDotSlash reports whether the path begins with a reference
   659  // to the local . or .. directory.
   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  // parseSymbol breaks str apart into a symbol and method.
   673  // Both may be missing or the method may be missing.
   674  // If present, each must be a valid Go identifier.
   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  // isExported reports whether the name is an exported identifier.
   693  // If the unexported flag (-u) is true, isExported returns true because
   694  // it means that we treat the name as if it is exported.
   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  // splitGopath splits $GOPATH into a list of roots.
   709  func splitGopath() []string {
   710  	return filepath.SplitList(cfg.BuildContext.GOPATH)
   711  }
   712  

View as plain text