src/cmd/go/internal/doc/pkg.go GO 1,277 lines View on github.com → Search inside
1// Copyright 2015 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package doc67import (8	"bytes"9	"fmt"10	"go/ast"11	"go/doc"12	"go/format"13	"go/parser"14	"go/printer"15	"go/token"16	"io"17	"io/fs"18	"log"19	"path/filepath"20	"strings"21	"unicode"22	"unicode/utf8"2324	"cmd/go/internal/cfg"25	"cmd/go/internal/load"26)2728const (29	punchedCardWidth = 8030	indent           = "    "31)3233type Package struct {34	writer      io.Writer    // Destination for output.35	name        string       // Package name, json for encoding/json.36	userPath    string       // String the user used to find this package.37	pkg         *ast.Package // Parsed package.38	file        *ast.File    // Merged from all files in the package39	doc         *doc.Package40	build       *load.Package41	typedValue  map[*doc.Value]bool // Consts and vars related to types.42	constructor map[*doc.Func]bool  // Constructors.43	fs          *token.FileSet      // Needed for printing.44	buf         pkgBuffer45}4647func (pkg *Package) ToText(w io.Writer, text, prefix, codePrefix string) {48	d := pkg.doc.Parser().Parse(text)49	pr := pkg.doc.Printer()50	pr.TextPrefix = prefix51	pr.TextCodePrefix = codePrefix52	w.Write(pr.Text(d))53}5455// pkgBuffer is a wrapper for bytes.Buffer that prints a package clause the56// first time Write is called.57type pkgBuffer struct {58	pkg     *Package59	printed bool // Prevent repeated package clauses.60	bytes.Buffer61}6263func (pb *pkgBuffer) Write(p []byte) (int, error) {64	pb.packageClause()65	return pb.Buffer.Write(p)66}6768func (pb *pkgBuffer) packageClause() {69	if !pb.printed {70		pb.printed = true71		// Only show package clause for commands if requested explicitly.72		if pb.pkg.pkg.Name != "main" || showCmd {73			pb.pkg.packageClause()74		}75	}76}7778type PackageError string // type returned by pkg.Fatalf.7980func (p PackageError) Error() string {81	return string(p)82}8384// prettyPath returns a version of the package path that is suitable for an85// error message. It obeys the import comment if present. Also, since86// pkg.build.ImportPath is sometimes the unhelpful "" or ".", it looks for a87// directory name in GOROOT or GOPATH if that happens.88func (pkg *Package) prettyPath() string {89	path := pkg.build.ImportComment90	if path == "" {91		path = pkg.build.ImportPath92	}93	if path != "." && path != "" {94		return path95	}96	// Convert the source directory into a more useful path.97	// Also convert everything to slash-separated paths for uniform handling.98	path = filepath.Clean(filepath.ToSlash(pkg.build.Dir))99	// Can we find a decent prefix?100	if cfg.GOROOT != "" {101		goroot := filepath.Join(cfg.GOROOT, "src")102		if p, ok := trim(path, filepath.ToSlash(goroot)); ok {103			return p104		}105	}106	for _, gopath := range splitGopath() {107		if p, ok := trim(path, filepath.ToSlash(gopath)); ok {108			return p109		}110	}111	return path112}113114// trim trims the directory prefix from the path, paying attention115// to the path separator. If they are the same string or the prefix116// is not present the original is returned. The boolean reports whether117// the prefix is present. That path and prefix have slashes for separators.118func trim(path, prefix string) (string, bool) {119	if !strings.HasPrefix(path, prefix) {120		return path, false121	}122	if path == prefix {123		return path, true124	}125	if path[len(prefix)] == '/' {126		return path[len(prefix)+1:], true127	}128	return path, false // Textual prefix but not a path prefix.129}130131// pkg.Fatalf is like log.Fatalf, but panics so it can be recovered in the132// main do function, so it doesn't cause an exit. Allows testing to work133// without running a subprocess. The log prefix will be added when134// logged in main; it is not added here.135func (pkg *Package) Fatalf(format string, args ...any) {136	panic(PackageError(fmt.Sprintf(format, args...)))137}138139// parsePackage turns the build package we found into a parsed package140// we can then use to generate documentation.141func parsePackage(writer io.Writer, pkg *load.Package, userPath string) *Package {142	// include tells parser.ParseDir which files to include.143	// That means the file must be in the build package's GoFiles, CgoFiles,144	// TestGoFiles or XTestGoFiles list only (no tag-ignored files, swig or145	// other non-Go files).146	include := func(info fs.FileInfo) bool {147		files := [][]string{pkg.GoFiles, pkg.CgoFiles, pkg.TestGoFiles, pkg.XTestGoFiles}148		for _, f := range files {149			for _, name := range f {150				if name == info.Name() {151					return true152				}153			}154		}155		return false156	}157	fset := token.NewFileSet()158	pkgs, err := parser.ParseDir(fset, pkg.Dir, include, parser.ParseComments)159	if err != nil {160		log.Fatal(err)161	}162	if len(pkgs) == 0 {163		log.Fatalf("no source-code package in directory %s", pkg.Dir)164	}165	astPkg := pkgs[pkg.Name]166167	// TODO: go/doc does not include typed constants in the constants168	// list, which is what we want. For instance, time.Sunday is of type169	// time.Weekday, so it is defined in the type but not in the170	// Consts list for the package. This prevents171	//	go doc time.Sunday172	// from finding the symbol. Work around this for now, but we173	// should fix it in go/doc.174	// A similar story applies to factory functions.175	mode := doc.AllDecls176	if showSrc {177		mode |= doc.PreserveAST // See comment for Package.emit.178	}179	var allGoFiles []*ast.File180	for _, p := range pkgs {181		for _, f := range p.Files {182			allGoFiles = append(allGoFiles, f)183		}184	}185	docPkg, err := doc.NewFromFiles(fset, allGoFiles, pkg.ImportPath, mode)186	if err != nil {187		log.Fatal(err)188	}189	typedValue := make(map[*doc.Value]bool)190	constructor := make(map[*doc.Func]bool)191	for _, typ := range docPkg.Types {192		docPkg.Consts = append(docPkg.Consts, typ.Consts...)193		docPkg.Vars = append(docPkg.Vars, typ.Vars...)194		docPkg.Funcs = append(docPkg.Funcs, typ.Funcs...)195		if isExported(typ.Name) {196			for _, value := range typ.Consts {197				typedValue[value] = true198			}199			for _, value := range typ.Vars {200				typedValue[value] = true201			}202			for _, fun := range typ.Funcs {203				// We don't count it as a constructor bound to the type204				// if the type itself is not exported.205				constructor[fun] = true206			}207		}208	}209210	p := &Package{211		writer:      writer,212		name:        pkg.Name,213		userPath:    userPath,214		pkg:         astPkg,215		file:        ast.MergePackageFiles(astPkg, 0),216		doc:         docPkg,217		typedValue:  typedValue,218		constructor: constructor,219		build:       pkg,220		fs:          fset,221	}222	p.buf.pkg = p223	return p224}225226func (pkg *Package) Printf(format string, args ...any) {227	fmt.Fprintf(&pkg.buf, format, args...)228}229230func (pkg *Package) flush() {231	_, err := pkg.writer.Write(pkg.buf.Bytes())232	if err != nil {233		log.Fatal(err)234	}235	pkg.buf.Reset() // Not needed, but it's a flush.236}237238var newlineBytes = []byte("\n\n") // We never ask for more than 2.239240// newlines guarantees there are n newlines at the end of the buffer.241func (pkg *Package) newlines(n int) {242	for !bytes.HasSuffix(pkg.buf.Bytes(), newlineBytes[:n]) {243		pkg.buf.WriteRune('\n')244	}245}246247// emit prints the node. If showSrc is true, it ignores the provided comment,248// assuming the comment is in the node itself. Otherwise, the go/doc package249// clears the stuff we don't want to print anyway. It's a bit of a magic trick.250func (pkg *Package) emit(comment string, node ast.Node) {251	if node != nil {252		var arg any = node253		if showSrc {254			// Need an extra little dance to get internal comments to appear.255			arg = &printer.CommentedNode{256				Node:     node,257				Comments: pkg.file.Comments,258			}259		}260		err := format.Node(&pkg.buf, pkg.fs, arg)261		if err != nil {262			log.Fatal(err)263		}264		if comment != "" && !showSrc {265			pkg.newlines(1)266			pkg.ToText(&pkg.buf, comment, indent, indent+indent)267			pkg.newlines(2) // Blank line after comment to separate from next item.268		} else {269			pkg.newlines(1)270		}271	}272}273274// oneLineNode returns a one-line summary of the given input node.275func (pkg *Package) oneLineNode(node ast.Node) string {276	const maxDepth = 10277	return pkg.oneLineNodeDepth(node, maxDepth)278}279280// oneLineNodeDepth returns a one-line summary of the given input node.281// The depth specifies the maximum depth when traversing the AST.282func (pkg *Package) oneLineNodeDepth(node ast.Node, depth int) string {283	const dotDotDot = "..."284	if depth == 0 {285		return dotDotDot286	}287	depth--288289	switch n := node.(type) {290	case nil:291		return ""292293	case *ast.GenDecl:294		// Formats const and var declarations.295		trailer := ""296		if len(n.Specs) > 1 {297			trailer = " " + dotDotDot298		}299300		// Find the first relevant spec.301		typ := ""302		for i, spec := range n.Specs {303			valueSpec := spec.(*ast.ValueSpec) // Must succeed; we can't mix types in one GenDecl.304305			// The type name may carry over from a previous specification in the306			// case of constants and iota.307			if valueSpec.Type != nil {308				typ = fmt.Sprintf(" %s", pkg.oneLineNodeDepth(valueSpec.Type, depth))309			} else if len(valueSpec.Values) > 0 {310				typ = ""311			}312313			if !isExported(valueSpec.Names[0].Name) {314				continue315			}316			val := ""317			if i < len(valueSpec.Values) && valueSpec.Values[i] != nil {318				val = fmt.Sprintf(" = %s", pkg.oneLineNodeDepth(valueSpec.Values[i], depth))319			}320			return fmt.Sprintf("%s %s%s%s%s", n.Tok, valueSpec.Names[0], typ, val, trailer)321		}322		return ""323324	case *ast.FuncDecl:325		// Formats func declarations.326		name := n.Name.Name327		recv := pkg.oneLineNodeDepth(n.Recv, depth)328		if len(recv) > 0 {329			recv = "(" + recv + ") "330		}331		fnc := pkg.oneLineNodeDepth(n.Type, depth)332		fnc = strings.TrimPrefix(fnc, "func")333		return fmt.Sprintf("func %s%s%s", recv, name, fnc)334335	case *ast.TypeSpec:336		sep := " "337		if n.Assign.IsValid() {338			sep = " = "339		}340		tparams := pkg.formatTypeParams(n.TypeParams, depth)341		return fmt.Sprintf("type %s%s%s%s", n.Name.Name, tparams, sep, pkg.oneLineNodeDepth(n.Type, depth))342343	case *ast.FuncType:344		var params []string345		if n.Params != nil {346			for _, field := range n.Params.List {347				params = append(params, pkg.oneLineField(field, depth))348			}349		}350		needParens := false351		var results []string352		if n.Results != nil {353			needParens = needParens || len(n.Results.List) > 1354			for _, field := range n.Results.List {355				needParens = needParens || len(field.Names) > 0356				results = append(results, pkg.oneLineField(field, depth))357			}358		}359360		tparam := pkg.formatTypeParams(n.TypeParams, depth)361		param := joinStrings(params)362		if len(results) == 0 {363			return fmt.Sprintf("func%s(%s)", tparam, param)364		}365		result := joinStrings(results)366		if !needParens {367			return fmt.Sprintf("func%s(%s) %s", tparam, param, result)368		}369		return fmt.Sprintf("func%s(%s) (%s)", tparam, param, result)370371	case *ast.StructType:372		if n.Fields == nil || len(n.Fields.List) == 0 {373			return "struct{}"374		}375		return "struct{ ... }"376377	case *ast.InterfaceType:378		if n.Methods == nil || len(n.Methods.List) == 0 {379			return "interface{}"380		}381		return "interface{ ... }"382383	case *ast.FieldList:384		if n == nil || len(n.List) == 0 {385			return ""386		}387		if len(n.List) == 1 {388			return pkg.oneLineField(n.List[0], depth)389		}390		return dotDotDot391392	case *ast.FuncLit:393		return pkg.oneLineNodeDepth(n.Type, depth) + " { ... }"394395	case *ast.CompositeLit:396		typ := pkg.oneLineNodeDepth(n.Type, depth)397		if len(n.Elts) == 0 {398			return fmt.Sprintf("%s{}", typ)399		}400		return fmt.Sprintf("%s{ %s }", typ, dotDotDot)401402	case *ast.ArrayType:403		length := pkg.oneLineNodeDepth(n.Len, depth)404		element := pkg.oneLineNodeDepth(n.Elt, depth)405		return fmt.Sprintf("[%s]%s", length, element)406407	case *ast.MapType:408		key := pkg.oneLineNodeDepth(n.Key, depth)409		value := pkg.oneLineNodeDepth(n.Value, depth)410		return fmt.Sprintf("map[%s]%s", key, value)411412	case *ast.CallExpr:413		fnc := pkg.oneLineNodeDepth(n.Fun, depth)414		var args []string415		for _, arg := range n.Args {416			args = append(args, pkg.oneLineNodeDepth(arg, depth))417		}418		return fmt.Sprintf("%s(%s)", fnc, joinStrings(args))419420	case *ast.UnaryExpr:421		return fmt.Sprintf("%s%s", n.Op, pkg.oneLineNodeDepth(n.X, depth))422423	case *ast.Ident:424		return n.Name425426	default:427		// As a fallback, use default formatter for all unknown node types.428		buf := new(strings.Builder)429		format.Node(buf, pkg.fs, node)430		s := buf.String()431		if strings.Contains(s, "\n") {432			return dotDotDot433		}434		return s435	}436}437438func (pkg *Package) formatTypeParams(list *ast.FieldList, depth int) string {439	if list.NumFields() == 0 {440		return ""441	}442	var tparams []string443	for _, field := range list.List {444		tparams = append(tparams, pkg.oneLineField(field, depth))445	}446	return "[" + joinStrings(tparams) + "]"447}448449// oneLineField returns a one-line summary of the field.450func (pkg *Package) oneLineField(field *ast.Field, depth int) string {451	var names []string452	for _, name := range field.Names {453		names = append(names, name.Name)454	}455	if len(names) == 0 {456		return pkg.oneLineNodeDepth(field.Type, depth)457	}458	return joinStrings(names) + " " + pkg.oneLineNodeDepth(field.Type, depth)459}460461// joinStrings formats the input as a comma-separated list,462// but truncates the list at some reasonable length if necessary.463func joinStrings(ss []string) string {464	var n int465	for i, s := range ss {466		n += len(s) + len(", ")467		if n > punchedCardWidth {468			ss = append(ss[:i:i], "...")469			break470		}471	}472	return strings.Join(ss, ", ")473}474475// printHeader prints a header for the section named s, adding a blank line on each side.476func (pkg *Package) printHeader(s string) {477	pkg.Printf("\n%s\n\n", s)478}479480// constsDoc prints all const documentation, if any, including a header.481// The one argument is the valueDoc registry.482func (pkg *Package) constsDoc(printed map[*ast.GenDecl]bool) {483	var header bool484	for _, value := range pkg.doc.Consts {485		// Constants and variables come in groups, and valueDoc prints486		// all the items in the group. We only need to find one exported symbol.487		for _, name := range value.Names {488			if isExported(name) && !pkg.typedValue[value] {489				if !header {490					pkg.printHeader("CONSTANTS")491					header = true492				}493				pkg.valueDoc(value, printed)494				break495			}496		}497	}498}499500// varsDoc prints all var documentation, if any, including a header.501// Printed is the valueDoc registry.502func (pkg *Package) varsDoc(printed map[*ast.GenDecl]bool) {503	var header bool504	for _, value := range pkg.doc.Vars {505		// Constants and variables come in groups, and valueDoc prints506		// all the items in the group. We only need to find one exported symbol.507		for _, name := range value.Names {508			if isExported(name) && !pkg.typedValue[value] {509				if !header {510					pkg.printHeader("VARIABLES")511					header = true512				}513				pkg.valueDoc(value, printed)514				break515			}516		}517	}518}519520// funcsDoc prints all func documentation, if any, including a header.521func (pkg *Package) funcsDoc() {522	var header bool523	for _, fun := range pkg.doc.Funcs {524		if isExported(fun.Name) && !pkg.constructor[fun] {525			if !header {526				pkg.printHeader("FUNCTIONS")527				header = true528			}529			pkg.emit(fun.Doc, fun.Decl)530		}531	}532}533534// typesDoc prints all type documentation, if any, including a header.535func (pkg *Package) typesDoc() {536	var header bool537	for _, typ := range pkg.doc.Types {538		if isExported(typ.Name) {539			if !header {540				pkg.printHeader("TYPES")541				header = true542			}543			pkg.typeDoc(typ)544		}545	}546}547548// packageDoc prints the docs for the package.549func (pkg *Package) packageDoc() {550	pkg.Printf("") // Trigger the package clause; we know the package exists.551	if showAll || !short {552		pkg.ToText(&pkg.buf, pkg.doc.Doc, "", indent)553		pkg.newlines(1)554	}555556	switch {557	case showAll:558		printed := make(map[*ast.GenDecl]bool) // valueDoc registry559		pkg.constsDoc(printed)560		pkg.varsDoc(printed)561		pkg.funcsDoc()562		pkg.typesDoc()563564	case pkg.pkg.Name == "main" && !showCmd:565		// Show only package docs for commands.566		return567568	default:569		if !short {570			pkg.newlines(2) // Guarantee blank line before the components.571		}572		pkg.valueSummary(pkg.doc.Consts, false)573		pkg.valueSummary(pkg.doc.Vars, false)574		pkg.funcSummary(pkg.doc.Funcs, false)575		pkg.typeSummary()576		pkg.exampleSummary(pkg.doc.Examples, false)577	}578579	if !short {580		pkg.bugs()581	}582}583584// packageClause prints the package clause.585func (pkg *Package) packageClause() {586	if short {587		return588	}589	importPath := pkg.build.ImportComment590	if importPath == "" {591		importPath = pkg.build.ImportPath592	}593594	// If we're using modules, the import path derived from module code locations wins.595	// If we did a file system scan, we knew the import path when we found the directory.596	// But if we started with a directory name, we never knew the import path.597	// Either way, we don't know it now, and it's cheap to (re)compute it.598	if usingModules {599		for _, root := range codeRoots() {600			if pkg.build.Dir == root.dir {601				importPath = root.importPath602				break603			}604			if strings.HasPrefix(pkg.build.Dir, root.dir+string(filepath.Separator)) {605				suffix := filepath.ToSlash(pkg.build.Dir[len(root.dir)+1:])606				if root.importPath == "" {607					importPath = suffix608				} else {609					importPath = root.importPath + "/" + suffix610				}611				break612			}613		}614	}615616	pkg.Printf("package %s // import %q\n\n", pkg.name, importPath)617	if !usingModules && importPath != pkg.build.ImportPath {618		pkg.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath)619	}620}621622// valueSummary prints a one-line summary for each set of values and constants.623// If all the types in a constant or variable declaration belong to the same624// type they can be printed by typeSummary, and so can be suppressed here.625func (pkg *Package) valueSummary(values []*doc.Value, showGrouped bool) {626	var isGrouped map[*doc.Value]bool627	if !showGrouped {628		isGrouped = make(map[*doc.Value]bool)629		for _, typ := range pkg.doc.Types {630			if !isExported(typ.Name) {631				continue632			}633			for _, c := range typ.Consts {634				isGrouped[c] = true635			}636			for _, v := range typ.Vars {637				isGrouped[v] = true638			}639		}640	}641642	for _, value := range values {643		if !isGrouped[value] {644			if decl := pkg.oneLineNode(value.Decl); decl != "" {645				pkg.Printf("%s\n", decl)646			}647		}648	}649}650651// funcSummary prints a one-line summary for each function. Constructors652// are printed by typeSummary, below, and so can be suppressed here.653func (pkg *Package) funcSummary(funcs []*doc.Func, showConstructors bool) {654	for _, fun := range funcs {655		// Exported functions only. The go/doc package does not include methods here.656		if isExported(fun.Name) {657			if showConstructors || !pkg.constructor[fun] {658				pkg.Printf("%s\n", pkg.oneLineNode(fun.Decl))659				if showEx {660					pkg.exampleSummary(fun.Examples, false)661				}662			}663		}664	}665}666667// exampleSummary prints a one-line summary for each example.668func (pkg *Package) exampleSummary(exs []*doc.Example, showDoc bool) {669	if !showEx {670		return671	}672	for _, ex := range exs {673		pkg.Printf(indent+"func Example%s()\n", ex.Name)674		if showDoc && ex.Doc != "" {675			pkg.ToText(&pkg.buf, ex.Doc, indent+indent, indent+indent)676		}677	}678}679680// typeSummary prints a one-line summary for each type, followed by its constructors.681func (pkg *Package) typeSummary() {682	for _, typ := range pkg.doc.Types {683		for _, spec := range typ.Decl.Specs {684			typeSpec := spec.(*ast.TypeSpec) // Must succeed.685			if isExported(typeSpec.Name.Name) {686				pkg.Printf("%s\n", pkg.oneLineNode(typeSpec))687				// Now print the consts, vars, and constructors.688				for _, c := range typ.Consts {689					if decl := pkg.oneLineNode(c.Decl); decl != "" {690						pkg.Printf(indent+"%s\n", decl)691					}692				}693				for _, v := range typ.Vars {694					if decl := pkg.oneLineNode(v.Decl); decl != "" {695						pkg.Printf(indent+"%s\n", decl)696					}697				}698				for _, constructor := range typ.Funcs {699					if isExported(constructor.Name) {700						pkg.Printf(indent+"%s\n", pkg.oneLineNode(constructor.Decl))701						pkg.exampleSummary(constructor.Examples, false)702					}703				}704				pkg.exampleSummary(typ.Examples, false)705			}706		}707	}708}709710// bugs prints the BUGS information for the package.711// TODO: Provide access to TODOs and NOTEs as well (very noisy so off by default)?712func (pkg *Package) bugs() {713	if pkg.doc.Notes["BUG"] == nil {714		return715	}716	pkg.Printf("\n")717	for _, note := range pkg.doc.Notes["BUG"] {718		pkg.Printf("%s: %v\n", "BUG", note.Body)719	}720}721722// findValues finds the doc.Values that describe the symbol.723func (pkg *Package) findValues(symbol string, docValues []*doc.Value) (values []*doc.Value) {724	for _, value := range docValues {725		for _, name := range value.Names {726			if match(symbol, name) {727				values = append(values, value)728			}729		}730	}731	return732}733734// findFuncs finds the doc.Funcs that describes the symbol.735func (pkg *Package) findFuncs(symbol string) (funcs []*doc.Func) {736	for _, fun := range pkg.doc.Funcs {737		if match(symbol, fun.Name) {738			funcs = append(funcs, fun)739		}740	}741	return742}743744// findTypes finds the doc.Types that describes the symbol.745// If symbol is empty, it finds all exported types.746func (pkg *Package) findTypes(symbol string) (types []*doc.Type) {747	for _, typ := range pkg.doc.Types {748		if symbol == "" && isExported(typ.Name) || match(symbol, typ.Name) {749			types = append(types, typ)750		}751	}752	return753}754755// findExamples finds any examples that describe the symbol.756func (pkg *Package) findExamples(symbol string) (examples []*doc.Example) {757	if !strings.HasPrefix(symbol, "Example") {758		return759	}760	symbol = strings.TrimPrefix(symbol, "Example")761	var all []*doc.Example762	all = append(all, pkg.doc.Examples...)763	for _, typ := range pkg.doc.Types {764		all = append(all, typ.Examples...)765		for _, fun := range typ.Funcs {766			all = append(all, fun.Examples...)767		}768		for _, fun := range typ.Methods {769			all = append(all, fun.Examples...)770		}771	}772	for _, fun := range pkg.doc.Funcs {773		all = append(all, fun.Examples...)774	}775776	// always include unexported in below match(), so Example_one777	// is still matched despite trimming the Example_ part.778	u := unexported779	unexported = true780	for _, ex := range all {781		if match(symbol, ex.Name) {782			examples = append(examples, ex)783		}784	}785	unexported = u786	return787}788789// findTypeSpec returns the ast.TypeSpec within the declaration that defines the symbol.790// The name must match exactly.791func (pkg *Package) findTypeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec {792	for _, spec := range decl.Specs {793		typeSpec := spec.(*ast.TypeSpec) // Must succeed.794		if symbol == typeSpec.Name.Name {795			return typeSpec796		}797	}798	return nil799}800801// symbolDoc prints the docs for symbol. There may be multiple matches.802// If symbol matches a type, output includes its methods factories and associated constants.803// If there is no top-level symbol, symbolDoc looks for methods that match.804func (pkg *Package) symbolDoc(symbol string) bool {805	found := false806	// Functions.807	for _, fun := range pkg.findFuncs(symbol) {808		// Symbol is a function.809		decl := fun.Decl810		found = true811		if short {812			pkg.Printf("%s\n", pkg.oneLineNode(decl))813			pkg.exampleSummary(fun.Examples, false)814			continue815		}816		pkg.emit(fun.Doc, decl)817		pkg.exampleSummary(fun.Examples, true)818	}819	for _, ex := range pkg.findExamples(symbol) {820		pkg.emitExample(ex)821		found = true822	}823	// Constants and variables behave the same.824	values := pkg.findValues(symbol, pkg.doc.Consts)825	values = append(values, pkg.findValues(symbol, pkg.doc.Vars)...)826	printed := make(map[*ast.GenDecl]bool) // valueDoc registry827	for _, value := range values {828		pkg.valueDoc(value, printed)829		found = true830	}831	// Types.832	for _, typ := range pkg.findTypes(symbol) {833		pkg.typeDoc(typ)834		found = true835	}836	if !found {837		// See if there are methods.838		if !pkg.printMethodDoc("", symbol) {839			return false840		}841	}842	return true843}844845// valueDoc prints the docs for a constant or variable. The printed map records846// which values have been printed already to avoid duplication. Otherwise, a847// declaration like:848//849//	const ( c = 1; C = 2 )850//851// … could be printed twice if the -u flag is set, as it matches twice.852func (pkg *Package) valueDoc(value *doc.Value, printed map[*ast.GenDecl]bool) {853	if printed[value.Decl] {854		return855	}856	// Print each spec only if there is at least one exported symbol in it.857	// (See issue 11008.)858	// TODO: Should we elide unexported symbols from a single spec?859	// It's an unlikely scenario, probably not worth the trouble.860	// TODO: Would be nice if go/doc did this for us.861	specs := make([]ast.Spec, 0, len(value.Decl.Specs))862	var typ ast.Expr863	for _, spec := range value.Decl.Specs {864		vspec := spec.(*ast.ValueSpec)865866		// The type name may carry over from a previous specification in the867		// case of constants and iota.868		if vspec.Type != nil {869			typ = vspec.Type870		}871872		for _, ident := range vspec.Names {873			if showSrc || isExported(ident.Name) {874				if vspec.Type == nil && vspec.Values == nil && typ != nil {875					// This a standalone identifier, as in the case of iota usage.876					// Thus, assume the type comes from the previous type.877					vspec.Type = &ast.Ident{878						Name:    pkg.oneLineNode(typ),879						NamePos: vspec.End() - 1,880					}881				}882883				specs = append(specs, vspec)884				typ = nil // Only inject type on first exported identifier885				break886			}887		}888	}889	if len(specs) == 0 {890		return891	}892	value.Decl.Specs = specs893	printed[value.Decl] = true894	if short {895		pkg.Printf("%s\n", pkg.oneLineNode(value.Decl))896		return897	}898	pkg.emit(value.Doc, value.Decl)899}900901// typeDoc prints the docs for a type, including constructors and other items902// related to it.903func (pkg *Package) typeDoc(typ *doc.Type) {904	decl := typ.Decl905	spec := pkg.findTypeSpec(decl, typ.Name)906	trimUnexportedElems(spec)907	// If there are multiple types defined, reduce to just this one.908	if len(decl.Specs) > 1 {909		decl.Specs = []ast.Spec{spec}910	}911	if short {912		pkg.Printf("%s\n", pkg.oneLineNode(spec))913		return914	}915	pkg.emit(typ.Doc, decl)916	pkg.newlines(2)917	// Show associated methods, constants, etc.918	if showAll {919		printed := make(map[*ast.GenDecl]bool) // valueDoc registry920		// We can use append here to print consts, then vars. Ditto for funcs and methods.921		values := typ.Consts922		values = append(values, typ.Vars...)923		for _, value := range values {924			for _, name := range value.Names {925				if isExported(name) {926					pkg.valueDoc(value, printed)927					break928				}929			}930		}931		funcs := typ.Funcs932		funcs = append(funcs, typ.Methods...)933		for _, fun := range funcs {934			if isExported(fun.Name) {935				pkg.emit(fun.Doc, fun.Decl)936				if fun.Doc == "" {937					pkg.newlines(2)938				}939			}940		}941	} else {942		pkg.valueSummary(typ.Consts, true)943		pkg.valueSummary(typ.Vars, true)944		pkg.funcSummary(typ.Funcs, true)945		pkg.exampleSummary(typ.Examples, false)946		pkg.funcSummary(typ.Methods, true)947	}948}949950// trimUnexportedElems modifies spec in place to elide unexported fields from951// structs and methods from interfaces (unless the unexported flag is set or we952// are asked to show the original source).953func trimUnexportedElems(spec *ast.TypeSpec) {954	if showSrc {955		return956	}957	switch typ := spec.Type.(type) {958	case *ast.StructType:959		typ.Fields = trimUnexportedFields(typ.Fields, false)960	case *ast.InterfaceType:961		typ.Methods = trimUnexportedFields(typ.Methods, true)962	}963}964965// trimUnexportedFields returns the field list trimmed of unexported fields.966func trimUnexportedFields(fields *ast.FieldList, isInterface bool) *ast.FieldList {967	what := "methods"968	if !isInterface {969		what = "fields"970	}971972	trimmed := false973	list := make([]*ast.Field, 0, len(fields.List))974	for _, field := range fields.List {975		// When printing fields we normally print field.Doc.976		// Here we are going to pass the AST to go/format,977		// which will print the comments from the AST,978		// not field.Doc which is from go/doc.979		// The two are similar but not identical;980		// for example, field.Doc does not include directives.981		// In order to consistently print field.Doc,982		// we replace the comment in the AST with field.Doc.983		// That will cause go/format to print what we want.984		// See issue #56592.985		if field.Doc != nil {986			doc := field.Doc987			text := doc.Text()988989			trailingBlankLine := len(doc.List[len(doc.List)-1].Text) == 2990			if !trailingBlankLine {991				// Remove trailing newline.992				lt := len(text)993				if lt > 0 && text[lt-1] == '\n' {994					text = text[:lt-1]995				}996			}997998			start := doc.List[0].Slash999			doc.List = doc.List[:0]1000			for line := range strings.SplitSeq(text, "\n") {1001				prefix := "// "1002				if len(line) > 0 && line[0] == '\t' {1003					prefix = "//"1004				}1005				doc.List = append(doc.List, &ast.Comment{1006					Text: prefix + line,1007				})1008			}1009			doc.List[0].Slash = start1010		}10111012		names := field.Names1013		if len(names) == 0 {1014			// Embedded type. Use the name of the type. It must be of the form ident or1015			// pkg.ident (for structs and interfaces), or *ident or *pkg.ident (structs only).1016			// Or a type embedded in a constraint.1017			// Nothing else is allowed.1018			ty := field.Type1019			if se, ok := field.Type.(*ast.StarExpr); !isInterface && ok {1020				// The form *ident or *pkg.ident is only valid on1021				// embedded types in structs.1022				ty = se.X1023			}1024			constraint := false1025			switch ident := ty.(type) {1026			case *ast.Ident:1027				if isInterface && ident.Obj == nil &&1028					(ident.Name == "error" || ident.Name == "comparable") {1029					// For documentation purposes, we consider the builtin error1030					// and comparable types special when embedded in an interface,1031					// such that they always get shown publicly.1032					list = append(list, field)1033					continue1034				}1035				names = []*ast.Ident{ident}1036			case *ast.SelectorExpr:1037				// An embedded type may refer to a type in another package.1038				names = []*ast.Ident{ident.Sel}1039			default:1040				// An approximation or union or type1041				// literal in an interface.1042				constraint = true1043			}1044			if names == nil && !constraint {1045				// Can only happen if AST is incorrect. Safe to continue with a nil list.1046				log.Print("invalid program: unexpected type for embedded field")1047			}1048		}1049		// Trims if any is unexported. Good enough in practice.1050		ok := true1051		if !unexported {1052			for _, name := range names {1053				if !isExported(name.Name) {1054					trimmed = true1055					ok = false1056					break1057				}1058			}1059		}1060		if ok {1061			list = append(list, field)1062		}1063	}1064	if !trimmed {1065		return fields1066	}1067	unexportedField := &ast.Field{1068		Type: &ast.Ident{1069			// Hack: printer will treat this as a field with a named type.1070			// Setting Name and NamePos to ("", fields.Closing-1) ensures that1071			// when Pos and End are called on this field, they return the1072			// position right before closing '}' character.1073			Name:    "",1074			NamePos: fields.Closing - 1,1075		},1076		Comment: &ast.CommentGroup{1077			List: []*ast.Comment{{Text: fmt.Sprintf("// Has unexported %s.\n", what)}},1078		},1079	}1080	return &ast.FieldList{1081		Opening: fields.Opening,1082		List:    append(list, unexportedField),1083		Closing: fields.Closing,1084	}1085}10861087// printMethodDoc prints the docs for matches of symbol.method.1088// If symbol is empty, it prints all methods for any concrete type1089// that match the name. It reports whether it found any methods.1090func (pkg *Package) printMethodDoc(symbol, method string) bool {1091	types := pkg.findTypes(symbol)1092	if types == nil {1093		if symbol == "" {1094			return false1095		}1096		pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)1097	}1098	found := false1099	for _, typ := range types {1100		if len(typ.Methods) > 0 {1101			for _, meth := range typ.Methods {1102				if match(method, meth.Name) {1103					decl := meth.Decl1104					pkg.emit(meth.Doc, decl)1105					pkg.exampleSummary(meth.Examples, true)1106					found = true1107				}1108			}1109			continue1110		}1111		if symbol == "" {1112			continue1113		}1114		// Type may be an interface. The go/doc package does not attach1115		// an interface's methods to the doc.Type. We need to dig around.1116		spec := pkg.findTypeSpec(typ.Decl, typ.Name)1117		inter, ok := spec.Type.(*ast.InterfaceType)1118		if !ok {1119			// Not an interface type.1120			continue1121		}11221123		// Collect and print only the methods that match.1124		var methods []*ast.Field1125		for _, iMethod := range inter.Methods.List {1126			// This is an interface, so there can be only one name.1127			// TODO: Anonymous methods (embedding)1128			if len(iMethod.Names) == 0 {1129				continue1130			}1131			name := iMethod.Names[0].Name1132			if match(method, name) {1133				methods = append(methods, iMethod)1134				found = true1135			}1136		}1137		if found {1138			pkg.Printf("type %s ", spec.Name)1139			inter.Methods.List, methods = methods, inter.Methods.List1140			err := format.Node(&pkg.buf, pkg.fs, inter)1141			if err != nil {1142				log.Fatal(err)1143			}1144			pkg.newlines(1)1145			// Restore the original methods.1146			inter.Methods.List = methods1147		}1148	}1149	return found1150}11511152// printFieldDoc prints the docs for matches of symbol.fieldName.1153// It reports whether it found any field.1154// Both symbol and fieldName must be non-empty or it returns false.1155func (pkg *Package) printFieldDoc(symbol, fieldName string) bool {1156	if symbol == "" || fieldName == "" {1157		return false1158	}1159	types := pkg.findTypes(symbol)1160	if types == nil {1161		pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)1162	}1163	found := false1164	numUnmatched := 01165	for _, typ := range types {1166		// Type must be a struct.1167		spec := pkg.findTypeSpec(typ.Decl, typ.Name)1168		structType, ok := spec.Type.(*ast.StructType)1169		if !ok {1170			// Not a struct type.1171			continue1172		}1173		for _, field := range structType.Fields.List {1174			// TODO: Anonymous fields.1175			for _, name := range field.Names {1176				if !match(fieldName, name.Name) {1177					numUnmatched++1178					continue1179				}1180				if !found {1181					pkg.Printf("type %s struct {\n", typ.Name)1182				}1183				if field.Doc != nil {1184					// To present indented blocks in comments correctly, process the comment as1185					// a unit before adding the leading // to each line.1186					docBuf := new(bytes.Buffer)1187					pkg.ToText(docBuf, field.Doc.Text(), "", indent)1188					for line := range bytes.Lines(docBuf.Bytes()) {1189						line = bytes.TrimSuffix(line, []byte{'\n'})1190						fmt.Fprintf(&pkg.buf, "%s// %s\n", indent, line)1191					}1192				}1193				s := pkg.oneLineNode(field.Type)1194				lineComment := ""1195				if field.Comment != nil {1196					lineComment = fmt.Sprintf("  %s", field.Comment.List[0].Text)1197				}1198				pkg.Printf("%s%s %s%s\n", indent, name, s, lineComment)1199				found = true1200			}1201		}1202	}1203	if found {1204		if numUnmatched > 0 {1205			pkg.Printf("\n    // ... other fields elided ...\n")1206		}1207		pkg.Printf("}\n")1208	}1209	return found1210}12111212// match reports whether the user's symbol matches the program's.1213// A lower-case character in the user's string matches either case in the program's.1214// The program string must be exported.1215func match(user, program string) bool {1216	if !isExported(program) {1217		return false1218	}1219	if matchCase {1220		return user == program1221	}1222	for _, u := range user {1223		p, w := utf8.DecodeRuneInString(program)1224		program = program[w:]1225		if u == p {1226			continue1227		}1228		if unicode.IsLower(u) && simpleFold(u) == simpleFold(p) {1229			continue1230		}1231		return false1232	}1233	return program == ""1234}12351236// simpleFold returns the minimum rune equivalent to r1237// under Unicode-defined simple case folding.1238func simpleFold(r rune) rune {1239	for {1240		r1 := unicode.SimpleFold(r)1241		if r1 <= r {1242			return r1 // wrapped around, found min1243		}1244		r = r11245	}1246}12471248// emitExample prints an example and its output.1249func (pkg *Package) emitExample(ex *doc.Example) {1250	pkg.buf.printed = true // omit the package clause1251	var err error1252	if ex.Play != nil {1253		err = format.Node(&pkg.buf, pkg.fs, ex.Play)1254	} else {1255		// If code is an *ast.BlockStmt, trim the braces and indentation1256		// by just printing the enclosed List of ast.[]Stmt.1257		b, ok := ex.Code.(*ast.BlockStmt)1258		if !ok {1259			err = format.Node(&pkg.buf, pkg.fs, ex.Code)1260		} else {1261			err = format.Node(&pkg.buf, pkg.fs, b.List)1262		}1263	}1264	if err != nil {1265		log.Fatal(err)1266	}1267	if ex.Output != "" {1268		pkg.newlines(2)1269		pkg.Printf("Output: ")1270		if strings.Count(ex.Output, "\n") > 1 {1271			pkg.newlines(1)1272		}1273		pkg.Printf("%s", ex.Output)1274	}1275	pkg.newlines(1)1276}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.