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