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  	}
   171  }
   172  
   173  func (p *Package) collectFuncs(funcs []*Func) {
   174  	for _, f := range funcs {
   175  		if f.Recv != "" {
   176  			r := strings.TrimPrefix(f.Recv, "*")
   177  			if i := strings.IndexByte(r, '['); i >= 0 {
   178  				r = r[:i] // remove type parameters
   179  			}
   180  			p.syms[r+"."+f.Name] = true
   181  		} else {
   182  			p.syms[f.Name] = true
   183  		}
   184  	}
   185  }
   186  
   187  // NewFromFiles computes documentation for a package.
   188  //
   189  // The package is specified by a list of *ast.Files and corresponding
   190  // file set, which must not be nil.
   191  // NewFromFiles uses all provided files when computing documentation,
   192  // so it is the caller's responsibility to provide only the files that
   193  // match the desired build context. "go/build".Context.MatchFile can
   194  // be used for determining whether a file matches a build context with
   195  // the desired GOOS and GOARCH values, and other build constraints.
   196  // The import path of the package is specified by importPath.
   197  //
   198  // Examples found in _test.go files are associated with the corresponding
   199  // type, function, method, or the package, based on their name.
   200  // If the example has a suffix in its name, it is set in the
   201  // [Example.Suffix] field. [Examples] with malformed names are skipped.
   202  //
   203  // Optionally, a single extra argument of type [Mode] can be provided to
   204  // control low-level aspects of the documentation extraction behavior.
   205  //
   206  // NewFromFiles takes ownership of the AST files and may edit them,
   207  // unless the PreserveAST Mode bit is on.
   208  func NewFromFiles(fset *token.FileSet, files []*ast.File, importPath string, opts ...any) (*Package, error) {
   209  	// Check for invalid API usage.
   210  	if fset == nil {
   211  		panic(fmt.Errorf("doc.NewFromFiles: no token.FileSet provided (fset == nil)"))
   212  	}
   213  	var mode Mode
   214  	switch len(opts) { // There can only be 0 or 1 options, so a simple switch works for now.
   215  	case 0:
   216  		// Nothing to do.
   217  	case 1:
   218  		m, ok := opts[0].(Mode)
   219  		if !ok {
   220  			panic(fmt.Errorf("doc.NewFromFiles: option argument type must be doc.Mode"))
   221  		}
   222  		mode = m
   223  	default:
   224  		panic(fmt.Errorf("doc.NewFromFiles: there must not be more than 1 option argument"))
   225  	}
   226  
   227  	// Collect .go and _test.go files.
   228  	var (
   229  		goFiles     = make(map[string]*ast.File)
   230  		testGoFiles []*ast.File
   231  	)
   232  	for i := range files {
   233  		f := fset.File(files[i].Pos())
   234  		if f == nil {
   235  			return nil, fmt.Errorf("file files[%d] is not found in the provided file set", i)
   236  		}
   237  		switch name := f.Name(); {
   238  		case strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go"):
   239  			goFiles[name] = files[i]
   240  		case strings.HasSuffix(name, "_test.go"):
   241  			testGoFiles = append(testGoFiles, files[i])
   242  		default:
   243  			return nil, fmt.Errorf("file files[%d] filename %q does not have a .go extension", i, name)
   244  		}
   245  	}
   246  
   247  	// TODO(dmitshur,gri): A relatively high level call to ast.NewPackage with a simpleImporter
   248  	// ast.Importer implementation is made below. It might be possible to short-circuit and simplify.
   249  
   250  	// Compute package documentation.
   251  	pkg, _ := ast.NewPackage(fset, goFiles, simpleImporter, nil) // Ignore errors that can happen due to unresolved identifiers.
   252  	p := New(pkg, importPath, mode)
   253  	classifyExamples(p, Examples(testGoFiles...))
   254  	return p, nil
   255  }
   256  
   257  // simpleImporter returns a (dummy) package object named by the last path
   258  // component of the provided package path (as is the convention for packages).
   259  // This is sufficient to resolve package identifiers without doing an actual
   260  // import. It never returns an error.
   261  func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
   262  	pkg := imports[path]
   263  	if pkg == nil {
   264  		// note that strings.LastIndex returns -1 if there is no "/"
   265  		pkg = ast.NewObj(ast.Pkg, path[strings.LastIndex(path, "/")+1:])
   266  		pkg.Data = ast.NewScope(nil) // required by ast.NewPackage for dot-import
   267  		imports[path] = pkg
   268  	}
   269  	return pkg, nil
   270  }
   271  
   272  // lookupSym reports whether the package has a given symbol or method.
   273  //
   274  // If recv == "", HasSym reports whether the package has a top-level
   275  // const, func, type, or var named name.
   276  //
   277  // If recv != "", HasSym reports whether the package has a type
   278  // named recv with a method named name.
   279  func (p *Package) lookupSym(recv, name string) bool {
   280  	if recv != "" {
   281  		return p.syms[recv+"."+name]
   282  	}
   283  	return p.syms[name]
   284  }
   285  
   286  // lookupPackage returns the import path identified by name
   287  // in the given package. If name uniquely identifies a single import,
   288  // then lookupPackage returns that import.
   289  // If multiple packages are imported as name, importPath returns "", false.
   290  // Otherwise, if name is the name of p itself, importPath returns "", true,
   291  // to signal a reference to p.
   292  // Otherwise, importPath returns "", false.
   293  func (p *Package) lookupPackage(name string) (importPath string, ok bool) {
   294  	if path, ok := p.importByName[name]; ok {
   295  		if path == "" {
   296  			return "", false // multiple imports used the name
   297  		}
   298  		return path, true // found import
   299  	}
   300  	if p.Name == name {
   301  		return "", true // allow reference to this package
   302  	}
   303  	return "", false // unknown name
   304  }
   305  
   306  // Parser returns a doc comment parser configured
   307  // for parsing doc comments from package p.
   308  // Each call returns a new parser, so that the caller may
   309  // customize it before use.
   310  func (p *Package) Parser() *comment.Parser {
   311  	return &comment.Parser{
   312  		LookupPackage: p.lookupPackage,
   313  		LookupSym:     p.lookupSym,
   314  	}
   315  }
   316  
   317  // Printer returns a doc comment printer configured
   318  // for printing doc comments from package p.
   319  // Each call returns a new printer, so that the caller may
   320  // customize it before use.
   321  func (p *Package) Printer() *comment.Printer {
   322  	// No customization today, but having p.Printer()
   323  	// gives us flexibility in the future, and it is convenient for callers.
   324  	return &comment.Printer{}
   325  }
   326  
   327  // HTML returns formatted HTML for the doc comment text.
   328  //
   329  // To customize details of the HTML, use [Package.Printer]
   330  // to obtain a [comment.Printer], and configure it
   331  // before calling its HTML method.
   332  func (p *Package) HTML(text string) []byte {
   333  	return p.Printer().HTML(p.Parser().Parse(text))
   334  }
   335  
   336  // Markdown returns formatted Markdown for the doc comment text.
   337  //
   338  // To customize details of the Markdown, use [Package.Printer]
   339  // to obtain a [comment.Printer], and configure it
   340  // before calling its Markdown method.
   341  func (p *Package) Markdown(text string) []byte {
   342  	return p.Printer().Markdown(p.Parser().Parse(text))
   343  }
   344  
   345  // Text returns formatted text for the doc comment text,
   346  // wrapped to 80 Unicode code points and using tabs for
   347  // code block indentation.
   348  //
   349  // To customize details of the formatting, use [Package.Printer]
   350  // to obtain a [comment.Printer], and configure it
   351  // before calling its Text method.
   352  func (p *Package) Text(text string) []byte {
   353  	return p.Printer().Text(p.Parser().Parse(text))
   354  }
   355  

View as plain text