• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
6package doc
7
8import (
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.
17type 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.
45type 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.
54type 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.
72type 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.
93type 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].
100type Mode int
101
102const (
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.
121func 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
151func (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
159func (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
173func (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.
208func 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.
261func 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.
279func (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.
293func (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.
310func (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.
321func (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.
332func (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.
341func (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.
352func (p *Package) Text(text string) []byte {
353	return p.Printer().Text(p.Parser().Parse(text))
354}
355