Source file src/go/doc/doc.go
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" 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. 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 p.collectInterfaceMethods(t) 171 p.collectStructFields(t) 172 } 173 } 174 175 func (p *Package) collectFuncs(funcs []*Func) { 176 for _, f := range funcs { 177 if f.Recv != "" { 178 r := strings.TrimPrefix(f.Recv, "*") 179 if i := strings.IndexByte(r, '['); i >= 0 { 180 r = r[:i] // remove type parameters 181 } 182 p.syms[r+"."+f.Name] = true 183 } else { 184 p.syms[f.Name] = true 185 } 186 } 187 } 188 189 // collectInterfaceMethods adds methods of interface types within t to p.syms. 190 // Note that t.Methods will contain methods of non-interface types, but not interface types. 191 // Adding interface methods to t.Methods might make sense, but would cause us to 192 // include those methods in the documentation index. Adding interface methods to p.syms 193 // here allows us to linkify references like [io.Reader.Read] without making any other 194 // changes to the documentation formatting at this time. 195 // 196 // If we do start adding interface methods to t.Methods in the future, 197 // collectInterfaceMethods can be dropped as redundant with collectFuncs(t.Methods). 198 func (p *Package) collectInterfaceMethods(t *Type) { 199 for _, s := range t.Decl.Specs { 200 spec, ok := s.(*ast.TypeSpec) 201 if !ok { 202 continue 203 } 204 list, isStruct := fields(spec.Type) 205 if isStruct { 206 continue 207 } 208 for _, field := range list { 209 for _, name := range field.Names { 210 p.syms[t.Name+"."+name.Name] = true 211 } 212 } 213 } 214 } 215 216 func (p *Package) collectStructFields(t *Type) { 217 for _, s := range t.Decl.Specs { 218 spec, ok := s.(*ast.TypeSpec) 219 if !ok { 220 continue 221 } 222 list, isStruct := fields(spec.Type) 223 if !isStruct { 224 continue 225 } 226 for _, field := range list { 227 for _, name := range field.Names { 228 p.syms[t.Name+"."+name.Name] = true 229 } 230 } 231 } 232 } 233 234 // NewFromFiles computes documentation for a package. 235 // 236 // The package is specified by a list of *ast.Files and corresponding 237 // file set, which must not be nil. 238 // 239 // NewFromFiles uses all provided files when computing documentation, 240 // so it is the caller's responsibility to provide only the files that 241 // match the desired build context. "go/build".Context.MatchFile can 242 // be used for determining whether a file matches a build context with 243 // the desired GOOS and GOARCH values, and other build constraints. 244 // The import path of the package is specified by importPath. 245 // 246 // Examples found in _test.go files are associated with the corresponding 247 // type, function, method, or the package, based on their name. 248 // If the example has a suffix in its name, it is set in the 249 // [Example.Suffix] field. [Examples] with malformed names are skipped. 250 // 251 // Optionally, a single extra argument of type [Mode] can be provided to 252 // control low-level aspects of the documentation extraction behavior. 253 // 254 // NewFromFiles takes ownership of the AST files and may edit them, 255 // unless the PreserveAST Mode bit is on. 256 func NewFromFiles(fset *token.FileSet, files []*ast.File, importPath string, opts ...any) (*Package, error) { 257 // Check for invalid API usage. 258 if fset == nil { 259 panic(fmt.Errorf("doc.NewFromFiles: no token.FileSet provided (fset == nil)")) 260 } 261 var mode Mode 262 switch len(opts) { // There can only be 0 or 1 options, so a simple switch works for now. 263 case 0: 264 // Nothing to do. 265 case 1: 266 m, ok := opts[0].(Mode) 267 if !ok { 268 panic(fmt.Errorf("doc.NewFromFiles: option argument type must be doc.Mode")) 269 } 270 mode = m 271 default: 272 panic(fmt.Errorf("doc.NewFromFiles: there must not be more than 1 option argument")) 273 } 274 275 // Collect .go and _test.go files. 276 var ( 277 pkgName string 278 goFiles = make(map[string]*ast.File) 279 testGoFiles []*ast.File 280 ) 281 for i, file := range files { 282 f := fset.File(file.Pos()) 283 if f == nil { 284 return nil, fmt.Errorf("file files[%d] is not found in the provided file set", i) 285 } 286 switch filename := f.Name(); { 287 case strings.HasSuffix(filename, "_test.go"): 288 testGoFiles = append(testGoFiles, file) 289 case strings.HasSuffix(filename, ".go"): 290 pkgName = file.Name.Name 291 goFiles[filename] = file 292 default: 293 return nil, fmt.Errorf("file files[%d] filename %q does not have a .go extension", i, filename) 294 } 295 } 296 297 // Compute package documentation. 298 // 299 // Since this package doesn't need Package.{Scope,Imports}, or 300 // handle errors, and ast.File's Scope field is unset in files 301 // parsed with parser.SkipObjectResolution, we construct the 302 // Package directly instead of calling [ast.NewPackage]. 303 pkg := &ast.Package{Name: pkgName, Files: goFiles} 304 p := New(pkg, importPath, mode) 305 classifyExamples(p, Examples(testGoFiles...)) 306 return p, nil 307 } 308 309 // lookupSym reports whether the package has a given symbol or method. 310 // 311 // If recv == "", HasSym reports whether the package has a top-level 312 // const, func, type, or var named name. 313 // 314 // If recv != "", HasSym reports whether the package has a type 315 // named recv with a method named name. 316 func (p *Package) lookupSym(recv, name string) bool { 317 if recv != "" { 318 return p.syms[recv+"."+name] 319 } 320 return p.syms[name] 321 } 322 323 // lookupPackage returns the import path identified by name 324 // in the given package. If name uniquely identifies a single import, 325 // then lookupPackage returns that import. 326 // If multiple packages are imported as name, importPath returns "", false. 327 // Otherwise, if name is the name of p itself, importPath returns "", true, 328 // to signal a reference to p. 329 // Otherwise, importPath returns "", false. 330 func (p *Package) lookupPackage(name string) (importPath string, ok bool) { 331 if path, ok := p.importByName[name]; ok { 332 if path == "" { 333 return "", false // multiple imports used the name 334 } 335 return path, true // found import 336 } 337 if p.Name == name { 338 return "", true // allow reference to this package 339 } 340 return "", false // unknown name 341 } 342 343 // Parser returns a doc comment parser configured 344 // for parsing doc comments from package p. 345 // Each call returns a new parser, so that the caller may 346 // customize it before use. 347 func (p *Package) Parser() *comment.Parser { 348 return &comment.Parser{ 349 LookupPackage: p.lookupPackage, 350 LookupSym: p.lookupSym, 351 } 352 } 353 354 // Printer returns a doc comment printer configured 355 // for printing doc comments from package p. 356 // Each call returns a new printer, so that the caller may 357 // customize it before use. 358 func (p *Package) Printer() *comment.Printer { 359 // No customization today, but having p.Printer() 360 // gives us flexibility in the future, and it is convenient for callers. 361 return &comment.Printer{} 362 } 363 364 // HTML returns formatted HTML for the doc comment text. 365 // 366 // To customize details of the HTML, use [Package.Printer] 367 // to obtain a [comment.Printer], and configure it 368 // before calling its HTML method. 369 func (p *Package) HTML(text string) []byte { 370 return p.Printer().HTML(p.Parser().Parse(text)) 371 } 372 373 // Markdown returns formatted Markdown for the doc comment text. 374 // 375 // To customize details of the Markdown, use [Package.Printer] 376 // to obtain a [comment.Printer], and configure it 377 // before calling its Markdown method. 378 func (p *Package) Markdown(text string) []byte { 379 return p.Printer().Markdown(p.Parser().Parse(text)) 380 } 381 382 // Text returns formatted text for the doc comment text, 383 // wrapped to 80 Unicode code points and using tabs for 384 // code block indentation. 385 // 386 // To customize details of the formatting, use [Package.Printer] 387 // to obtain a [comment.Printer], and configure it 388 // before calling its Text method. 389 func (p *Package) Text(text string) []byte { 390 return p.Printer().Text(p.Parser().Parse(text)) 391 } 392