...

Source file src/go/doc/doc.go

Documentation: go/doc

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

View as plain text