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