Source file src/go/doc/doc.go

     1  // Copyright 2009 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 extracts source code documentation from a Go AST.
     6  package doc
     7  
     8  import (
     9  	"fmt"
    10  	"go/ast"
    11  	"go/doc/comment"
    12  	"go/token"
    13  	"strings"
    14  )
    15  
    16  // Package is the documentation for an entire package.
    17  type Package struct {
    18  	Doc        string
    19  	Name       string
    20  	ImportPath string
    21  	Imports    []string
    22  	Filenames  []string
    23  	Notes      map[string][]*Note
    24  
    25  	// Deprecated: For backward compatibility Bugs is still populated,
    26  	// but all new code should use Notes instead.
    27  	Bugs []string
    28  
    29  	// declarations
    30  	Consts []*Value
    31  	Types  []*Type
    32  	Vars   []*Value
    33  	Funcs  []*Func
    34  
    35  	// Examples is a sorted list of examples associated with
    36  	// the package. Examples are extracted from _test.go files
    37  	// provided to NewFromFiles.
    38  	Examples []*Example
    39  
    40  	importByName map[string]string
    41  	syms         map[string]bool
    42  }
    43  
    44  // Value is the documentation for a (possibly grouped) var or const declaration.
    45  type Value struct {
    46  	Doc   string
    47  	Names []string // var or const names in declaration order
    48  	Decl  *ast.GenDecl
    49  
    50  	order int
    51  }
    52  
    53  // Type is the documentation for a type declaration.
    54  type Type struct {
    55  	Doc  string
    56  	Name string
    57  	Decl *ast.GenDecl
    58  
    59  	// associated declarations
    60  	Consts  []*Value // sorted list of constants of (mostly) this type
    61  	Vars    []*Value // sorted list of variables of (mostly) this type
    62  	Funcs   []*Func  // sorted list of functions returning this type
    63  	Methods []*Func  // sorted list of methods (including embedded ones) of this type
    64  
    65  	// Examples is a sorted list of examples associated with
    66  	// this type. Examples are extracted from _test.go files
    67  	// provided to NewFromFiles.
    68  	Examples []*Example
    69  }
    70  
    71  // Func is the documentation for a func declaration.
    72  type Func struct {
    73  	Doc  string
    74  	Name string
    75  	Decl *ast.FuncDecl
    76  
    77  	// methods
    78  	// (for functions, these fields have the respective zero value)
    79  	Recv  string // actual   receiver "T" or "*T" possibly followed by type parameters [P1, ..., Pn]
    80  	Orig  string // original receiver "T" or "*T"
    81  	Level int    // embedding level; 0 means not embedded
    82  
    83  	// Examples is a sorted list of examples associated with this
    84  	// function or method. Examples are extracted from _test.go files
    85  	// provided to NewFromFiles.
    86  	Examples []*Example
    87  }
    88  
    89  // A Note represents a marked comment starting with "MARKER(uid): note body".
    90  // Any note with a marker of 2 or more upper case [A-Z] letters and a uid of
    91  // at least one character is recognized. The ":" following the uid is optional.
    92  // Notes are collected in the Package.Notes map indexed by the notes marker.
    93  type Note struct {
    94  	Pos, End token.Pos // position range of the comment containing the marker
    95  	UID      string    // uid found with the marker
    96  	Body     string    // note body text
    97  }
    98  
    99  // Mode values control the operation of [New] and [NewFromFiles].
   100  type Mode int
   101  
   102  const (
   103  	// AllDecls says to extract documentation for all package-level
   104  	// declarations, not just exported ones.
   105  	AllDecls Mode = 1 << iota
   106  
   107  	// AllMethods says to show all embedded methods, not just the ones of
   108  	// invisible (unexported) anonymous fields.
   109  	AllMethods
   110  
   111  	// PreserveAST says to leave the AST unmodified. Originally, pieces of
   112  	// the AST such as function bodies were nil-ed out to save memory in
   113  	// godoc, but not all programs want that behavior.
   114  	PreserveAST
   115  )
   116  
   117  // New computes the package documentation for the given package AST.
   118  // New takes ownership of the AST pkg and may edit or overwrite it.
   119  // To have the [Examples] fields populated, use [NewFromFiles] and include
   120  // the package's _test.go files.
   121  func New(pkg *ast.Package, importPath string, mode Mode) *Package {
   122  	var r reader
   123  	r.readPackage(pkg, mode)
   124  	r.computeMethodSets()
   125  	r.cleanupTypes()
   126  	p := &Package{
   127  		Doc:        r.doc,
   128  		Name:       pkg.Name,
   129  		ImportPath: importPath,
   130  		Imports:    sortedKeys(r.imports),
   131  		Filenames:  r.filenames,
   132  		Notes:      r.notes,
   133  		Bugs:       noteBodies(r.notes["BUG"]),
   134  		Consts:     sortedValues(r.values, token.CONST),
   135  		Types:      sortedTypes(r.types, mode&AllMethods != 0),
   136  		Vars:       sortedValues(r.values, token.VAR),
   137  		Funcs:      sortedFuncs(r.funcs, true),
   138  
   139  		importByName: r.importByName,
   140  		syms:         make(map[string]bool),
   141  	}
   142  
   143  	p.collectValues(p.Consts)
   144  	p.collectValues(p.Vars)
   145  	p.collectTypes(p.Types)
   146  	p.collectFuncs(p.Funcs)
   147  
   148  	return p
   149  }
   150  
   151  func (p *Package) collectValues(values []*Value) {
   152  	for _, v := range values {
   153  		for _, name := range v.Names {
   154  			p.syms[name] = true
   155  		}
   156  	}
   157  }
   158  
   159  func (p *Package) collectTypes(types []*Type) {
   160  	for _, t := range types {
   161  		if p.syms[t.Name] {
   162  			// Shouldn't be any cycles but stop just in case.
   163  			continue
   164  		}
   165  		p.syms[t.Name] = true
   166  		p.collectValues(t.Consts)
   167  		p.collectValues(t.Vars)
   168  		p.collectFuncs(t.Funcs)
   169  		p.collectFuncs(t.Methods)
   170  		p.collectInterfaceMethods(t)
   171  		p.collectStructFields(t)
   172  	}
   173  }
   174  
   175  func (p *Package) collectFuncs(funcs []*Func) {
   176  	for _, f := range funcs {
   177  		if f.Recv != "" {
   178  			r := strings.TrimPrefix(f.Recv, "*")
   179  			if i := strings.IndexByte(r, '['); i >= 0 {
   180  				r = r[:i] // remove type parameters
   181  			}
   182  			p.syms[r+"."+f.Name] = true
   183  		} else {
   184  			p.syms[f.Name] = true
   185  		}
   186  	}
   187  }
   188  
   189  // collectInterfaceMethods adds methods of interface types within t to p.syms.
   190  // Note that t.Methods will contain methods of non-interface types, but not interface types.
   191  // Adding interface methods to t.Methods might make sense, but would cause us to
   192  // include those methods in the documentation index. Adding interface methods to p.syms
   193  // here allows us to linkify references like [io.Reader.Read] without making any other
   194  // changes to the documentation formatting at this time.
   195  //
   196  // If we do start adding interface methods to t.Methods in the future,
   197  // collectInterfaceMethods can be dropped as redundant with collectFuncs(t.Methods).
   198  func (p *Package) collectInterfaceMethods(t *Type) {
   199  	for _, s := range t.Decl.Specs {
   200  		spec, ok := s.(*ast.TypeSpec)
   201  		if !ok {
   202  			continue
   203  		}
   204  		list, isStruct := fields(spec.Type)
   205  		if isStruct {
   206  			continue
   207  		}
   208  		for _, field := range list {
   209  			for _, name := range field.Names {
   210  				p.syms[t.Name+"."+name.Name] = true
   211  			}
   212  		}
   213  	}
   214  }
   215  
   216  func (p *Package) collectStructFields(t *Type) {
   217  	for _, s := range t.Decl.Specs {
   218  		spec, ok := s.(*ast.TypeSpec)
   219  		if !ok {
   220  			continue
   221  		}
   222  		list, isStruct := fields(spec.Type)
   223  		if !isStruct {
   224  			continue
   225  		}
   226  		for _, field := range list {
   227  			for _, name := range field.Names {
   228  				p.syms[t.Name+"."+name.Name] = true
   229  			}
   230  		}
   231  	}
   232  }
   233  
   234  // NewFromFiles computes documentation for a package.
   235  //
   236  // The package is specified by a list of *ast.Files and corresponding
   237  // file set, which must not be nil.
   238  //
   239  // NewFromFiles uses all provided files when computing documentation,
   240  // so it is the caller's responsibility to provide only the files that
   241  // match the desired build context. "go/build".Context.MatchFile can
   242  // be used for determining whether a file matches a build context with
   243  // the desired GOOS and GOARCH values, and other build constraints.
   244  // The import path of the package is specified by importPath.
   245  //
   246  // Examples found in _test.go files are associated with the corresponding
   247  // type, function, method, or the package, based on their name.
   248  // If the example has a suffix in its name, it is set in the
   249  // [Example.Suffix] field. [Examples] with malformed names are skipped.
   250  //
   251  // Optionally, a single extra argument of type [Mode] can be provided to
   252  // control low-level aspects of the documentation extraction behavior.
   253  //
   254  // NewFromFiles takes ownership of the AST files and may edit them,
   255  // unless the PreserveAST Mode bit is on.
   256  func NewFromFiles(fset *token.FileSet, files []*ast.File, importPath string, opts ...any) (*Package, error) {
   257  	// Check for invalid API usage.
   258  	if fset == nil {
   259  		panic(fmt.Errorf("doc.NewFromFiles: no token.FileSet provided (fset == nil)"))
   260  	}
   261  	var mode Mode
   262  	switch len(opts) { // There can only be 0 or 1 options, so a simple switch works for now.
   263  	case 0:
   264  		// Nothing to do.
   265  	case 1:
   266  		m, ok := opts[0].(Mode)
   267  		if !ok {
   268  			panic(fmt.Errorf("doc.NewFromFiles: option argument type must be doc.Mode"))
   269  		}
   270  		mode = m
   271  	default:
   272  		panic(fmt.Errorf("doc.NewFromFiles: there must not be more than 1 option argument"))
   273  	}
   274  
   275  	// Collect .go and _test.go files.
   276  	var (
   277  		pkgName     string
   278  		goFiles     = make(map[string]*ast.File)
   279  		testGoFiles []*ast.File
   280  	)
   281  	for i, file := range files {
   282  		f := fset.File(file.Pos())
   283  		if f == nil {
   284  			return nil, fmt.Errorf("file files[%d] is not found in the provided file set", i)
   285  		}
   286  		switch filename := f.Name(); {
   287  		case strings.HasSuffix(filename, "_test.go"):
   288  			testGoFiles = append(testGoFiles, file)
   289  		case strings.HasSuffix(filename, ".go"):
   290  			pkgName = file.Name.Name
   291  			goFiles[filename] = file
   292  		default:
   293  			return nil, fmt.Errorf("file files[%d] filename %q does not have a .go extension", i, filename)
   294  		}
   295  	}
   296  
   297  	// Compute package documentation.
   298  	//
   299  	// Since this package doesn't need Package.{Scope,Imports}, or
   300  	// handle errors, and ast.File's Scope field is unset in files
   301  	// parsed with parser.SkipObjectResolution, we construct the
   302  	// Package directly instead of calling [ast.NewPackage].
   303  	pkg := &ast.Package{Name: pkgName, Files: goFiles}
   304  	p := New(pkg, importPath, mode)
   305  	classifyExamples(p, Examples(testGoFiles...))
   306  	return p, nil
   307  }
   308  
   309  // lookupSym reports whether the package has a given symbol or method.
   310  //
   311  // If recv == "", HasSym reports whether the package has a top-level
   312  // const, func, type, or var named name.
   313  //
   314  // If recv != "", HasSym reports whether the package has a type
   315  // named recv with a method named name.
   316  func (p *Package) lookupSym(recv, name string) bool {
   317  	if recv != "" {
   318  		return p.syms[recv+"."+name]
   319  	}
   320  	return p.syms[name]
   321  }
   322  
   323  // lookupPackage returns the import path identified by name
   324  // in the given package. If name uniquely identifies a single import,
   325  // then lookupPackage returns that import.
   326  // If multiple packages are imported as name, importPath returns "", false.
   327  // Otherwise, if name is the name of p itself, importPath returns "", true,
   328  // to signal a reference to p.
   329  // Otherwise, importPath returns "", false.
   330  func (p *Package) lookupPackage(name string) (importPath string, ok bool) {
   331  	if path, ok := p.importByName[name]; ok {
   332  		if path == "" {
   333  			return "", false // multiple imports used the name
   334  		}
   335  		return path, true // found import
   336  	}
   337  	if p.Name == name {
   338  		return "", true // allow reference to this package
   339  	}
   340  	return "", false // unknown name
   341  }
   342  
   343  // Parser returns a doc comment parser configured
   344  // for parsing doc comments from package p.
   345  // Each call returns a new parser, so that the caller may
   346  // customize it before use.
   347  func (p *Package) Parser() *comment.Parser {
   348  	return &comment.Parser{
   349  		LookupPackage: p.lookupPackage,
   350  		LookupSym:     p.lookupSym,
   351  	}
   352  }
   353  
   354  // Printer returns a doc comment printer configured
   355  // for printing doc comments from package p.
   356  // Each call returns a new printer, so that the caller may
   357  // customize it before use.
   358  func (p *Package) Printer() *comment.Printer {
   359  	// No customization today, but having p.Printer()
   360  	// gives us flexibility in the future, and it is convenient for callers.
   361  	return &comment.Printer{}
   362  }
   363  
   364  // HTML returns formatted HTML for the doc comment text.
   365  //
   366  // To customize details of the HTML, use [Package.Printer]
   367  // to obtain a [comment.Printer], and configure it
   368  // before calling its HTML method.
   369  func (p *Package) HTML(text string) []byte {
   370  	return p.Printer().HTML(p.Parser().Parse(text))
   371  }
   372  
   373  // Markdown returns formatted Markdown for the doc comment text.
   374  //
   375  // To customize details of the Markdown, use [Package.Printer]
   376  // to obtain a [comment.Printer], and configure it
   377  // before calling its Markdown method.
   378  func (p *Package) Markdown(text string) []byte {
   379  	return p.Printer().Markdown(p.Parser().Parse(text))
   380  }
   381  
   382  // Text returns formatted text for the doc comment text,
   383  // wrapped to 80 Unicode code points and using tabs for
   384  // code block indentation.
   385  //
   386  // To customize details of the formatting, use [Package.Printer]
   387  // to obtain a [comment.Printer], and configure it
   388  // before calling its Text method.
   389  func (p *Package) Text(text string) []byte {
   390  	return p.Printer().Text(p.Parser().Parse(text))
   391  }
   392  

View as plain text