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// +build go1.16
6
7// Package doc extracts source code documentation from a Go AST.
8package doc
9
10import (
11	"fmt"
12	"go/ast"
13	"go/token"
14	"strings"
15)
16
17// Package is the documentation for an entire package.
18type Package struct {
19	Doc        string
20	Name       string
21	ImportPath string
22	Imports    []string
23	Filenames  []string
24	Notes      map[string][]*Note
25
26	// Deprecated: For backward compatibility Bugs is still populated,
27	// but all new code should use Notes instead.
28	Bugs []string
29
30	// declarations
31	Consts []*Value
32	Types  []*Type
33	Vars   []*Value
34	Funcs  []*Func
35
36	// Examples is a sorted list of examples associated with
37	// the package. Examples are extracted from _test.go files
38	// provided to NewFromFiles.
39	Examples []*Example
40}
41
42// Value is the documentation for a (possibly grouped) var or const declaration.
43type Value struct {
44	Doc   string
45	Names []string // var or const names in declaration order
46	Decl  *ast.GenDecl
47
48	order int
49}
50
51// Type is the documentation for a type declaration.
52type Type struct {
53	Doc  string
54	Name string
55	Decl *ast.GenDecl
56
57	// associated declarations
58	Consts  []*Value // sorted list of constants of (mostly) this type
59	Vars    []*Value // sorted list of variables of (mostly) this type
60	Funcs   []*Func  // sorted list of functions returning this type
61	Methods []*Func  // sorted list of methods (including embedded ones) of this type
62
63	// Examples is a sorted list of examples associated with
64	// this type. Examples are extracted from _test.go files
65	// provided to NewFromFiles.
66	Examples []*Example
67}
68
69// Func is the documentation for a func declaration.
70type Func struct {
71	Doc  string
72	Name string
73	Decl *ast.FuncDecl
74
75	// methods
76	// (for functions, these fields have the respective zero value)
77	Recv  string // actual   receiver "T" or "*T"
78	Orig  string // original receiver "T" or "*T"
79	Level int    // embedding level; 0 means not embedded
80
81	// Examples is a sorted list of examples associated with this
82	// function or method. Examples are extracted from _test.go files
83	// provided to NewFromFiles.
84	Examples []*Example
85}
86
87// A Note represents a marked comment starting with "MARKER(uid): note body".
88// Any note with a marker of 2 or more upper case [A-Z] letters and a uid of
89// at least one character is recognized. The ":" following the uid is optional.
90// Notes are collected in the Package.Notes map indexed by the notes marker.
91type Note struct {
92	Pos, End token.Pos // position range of the comment containing the marker
93	UID      string    // uid found with the marker
94	Body     string    // note body text
95}
96
97// Mode values control the operation of New and NewFromFiles.
98type Mode int
99
100const (
101	// AllDecls says to extract documentation for all package-level
102	// declarations, not just exported ones.
103	AllDecls Mode = 1 << iota
104
105	// AllMethods says to show all embedded methods, not just the ones of
106	// invisible (unexported) anonymous fields.
107	AllMethods
108
109	// PreserveAST says to leave the AST unmodified. Originally, pieces of
110	// the AST such as function bodies were nil-ed out to save memory in
111	// godoc, but not all programs want that behavior.
112	PreserveAST
113)
114
115// New computes the package documentation for the given package AST.
116// New takes ownership of the AST pkg and may edit or overwrite it.
117// To have the Examples fields populated, use NewFromFiles and include
118// the package's _test.go files.
119//
120func New(pkg *ast.Package, importPath string, mode Mode) *Package {
121	var r reader
122	r.readPackage(pkg, mode)
123	r.computeMethodSets()
124	r.cleanupTypes()
125	return &Package{
126		Doc:        r.doc,
127		Name:       pkg.Name,
128		ImportPath: importPath,
129		Imports:    sortedKeys(r.imports),
130		Filenames:  r.filenames,
131		Notes:      r.notes,
132		Bugs:       noteBodies(r.notes["BUG"]),
133		Consts:     sortedValues(r.values, token.CONST),
134		Types:      sortedTypes(r.types, mode&AllMethods != 0),
135		Vars:       sortedValues(r.values, token.VAR),
136		Funcs:      sortedFuncs(r.funcs, true),
137	}
138}
139
140// NewFromFiles computes documentation for a package.
141//
142// The package is specified by a list of *ast.Files and corresponding
143// file set, which must not be nil.
144// NewFromFiles uses all provided files when computing documentation,
145// so it is the caller's responsibility to provide only the files that
146// match the desired build context. "go/build".Context.MatchFile can
147// be used for determining whether a file matches a build context with
148// the desired GOOS and GOARCH values, and other build constraints.
149// The import path of the package is specified by importPath.
150//
151// Examples found in _test.go files are associated with the corresponding
152// type, function, method, or the package, based on their name.
153// If the example has a suffix in its name, it is set in the
154// Example.Suffix field. Examples with malformed names are skipped.
155//
156// Optionally, a single extra argument of type Mode can be provided to
157// control low-level aspects of the documentation extraction behavior.
158//
159// NewFromFiles takes ownership of the AST files and may edit them,
160// unless the PreserveAST Mode bit is on.
161//
162func NewFromFiles(fset *token.FileSet, files []*ast.File, importPath string, opts ...interface{}) (*Package, error) {
163	// Check for invalid API usage.
164	if fset == nil {
165		panic(fmt.Errorf("doc.NewFromFiles: no token.FileSet provided (fset == nil)"))
166	}
167	var mode Mode
168	switch len(opts) { // There can only be 0 or 1 options, so a simple switch works for now.
169	case 0:
170		// Nothing to do.
171	case 1:
172		m, ok := opts[0].(Mode)
173		if !ok {
174			panic(fmt.Errorf("doc.NewFromFiles: option argument type must be doc.Mode"))
175		}
176		mode = m
177	default:
178		panic(fmt.Errorf("doc.NewFromFiles: there must not be more than 1 option argument"))
179	}
180
181	// Collect .go and _test.go files.
182	var (
183		goFiles     = make(map[string]*ast.File)
184		testGoFiles []*ast.File
185	)
186	for i := range files {
187		f := fset.File(files[i].Pos())
188		if f == nil {
189			return nil, fmt.Errorf("file files[%d] is not found in the provided file set", i)
190		}
191		switch name := f.Name(); {
192		case strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go"):
193			goFiles[name] = files[i]
194		case strings.HasSuffix(name, "_test.go"):
195			testGoFiles = append(testGoFiles, files[i])
196		default:
197			return nil, fmt.Errorf("file files[%d] filename %q does not have a .go extension", i, name)
198		}
199	}
200
201	// TODO(dmitshur,gri): A relatively high level call to ast.NewPackage with a simpleImporter
202	// ast.Importer implementation is made below. It might be possible to short-circuit and simplify.
203
204	// Compute package documentation.
205	pkg, _ := ast.NewPackage(fset, goFiles, simpleImporter, nil) // Ignore errors that can happen due to unresolved identifiers.
206	p := New(pkg, importPath, mode)
207	classifyExamples(p, Examples(testGoFiles...))
208	return p, nil
209}
210
211// simpleImporter returns a (dummy) package object named by the last path
212// component of the provided package path (as is the convention for packages).
213// This is sufficient to resolve package identifiers without doing an actual
214// import. It never returns an error.
215func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
216	pkg := imports[path]
217	if pkg == nil {
218		// note that strings.LastIndex returns -1 if there is no "/"
219		pkg = ast.NewObj(ast.Pkg, path[strings.LastIndex(path, "/")+1:])
220		pkg.Data = ast.NewScope(nil) // required by ast.NewPackage for dot-import
221		imports[path] = pkg
222	}
223	return pkg, nil
224}
225