pkg.go 13.2 KB
Newer Older
Rob Pike's avatar
Rob Pike committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/build"
	"go/doc"
	"go/format"
	"go/parser"
	"go/token"
	"log"
	"os"
	"unicode"
	"unicode/utf8"
)

type Package struct {
Rob Pike's avatar
Rob Pike committed
23 24 25 26 27 28 29 30
	name     string       // Package name, json for encoding/json.
	userPath string       // String the user used to find this package.
	pkg      *ast.Package // Parsed package.
	file     *ast.File    // Merged from all files in the package
	doc      *doc.Package
	build    *build.Package
	fs       *token.FileSet // Needed for printing.
	buf      bytes.Buffer
Rob Pike's avatar
Rob Pike committed
31 32 33 34
}

// parsePackage turns the build package we found into a parsed package
// we can then use to generate documentation.
Rob Pike's avatar
Rob Pike committed
35
func parsePackage(pkg *build.Package, userPath string) *Package {
Rob Pike's avatar
Rob Pike committed
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
	fs := token.NewFileSet()
	// include tells parser.ParseDir which files to include.
	// That means the file must be in the build package's GoFiles or CgoFiles
	// list only (no tag-ignored files, tests, swig or other non-Go files).
	include := func(info os.FileInfo) bool {
		for _, name := range pkg.GoFiles {
			if name == info.Name() {
				return true
			}
		}
		for _, name := range pkg.CgoFiles {
			if name == info.Name() {
				return true
			}
		}
		return false
	}
	pkgs, err := parser.ParseDir(fs, pkg.Dir, include, parser.ParseComments)
	if err != nil {
		log.Fatal(err)
	}
	// Make sure they are all in one package.
	if len(pkgs) != 1 {
		log.Fatalf("multiple packages directory %s", pkg.Dir)
	}
	astPkg := pkgs[pkg.Name]

	// TODO: go/doc does not include typed constants in the constants
	// list, which is what we want. For instance, time.Sunday is of type
	// time.Weekday, so it is defined in the type but not in the
	// Consts list for the package. This prevents
	//	go doc time.Sunday
	// from finding the symbol. Work around this for now, but we
	// should fix it in go/doc.
	// A similar story applies to factory functions.
	docPkg := doc.New(astPkg, pkg.ImportPath, doc.AllDecls)
	for _, typ := range docPkg.Types {
		docPkg.Consts = append(docPkg.Consts, typ.Consts...)
74
		docPkg.Vars = append(docPkg.Vars, typ.Vars...)
Rob Pike's avatar
Rob Pike committed
75 76 77 78
		docPkg.Funcs = append(docPkg.Funcs, typ.Funcs...)
	}

	return &Package{
Rob Pike's avatar
Rob Pike committed
79 80 81 82 83 84 85
		name:     pkg.Name,
		userPath: userPath,
		pkg:      astPkg,
		file:     ast.MergePackageFiles(astPkg, 0),
		doc:      docPkg,
		build:    pkg,
		fs:       fs,
Rob Pike's avatar
Rob Pike committed
86 87 88
	}
}

Rob Pike's avatar
Rob Pike committed
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
func (pkg *Package) Printf(format string, args ...interface{}) {
	fmt.Fprintf(&pkg.buf, format, args...)
}

func (pkg *Package) flush() {
	_, err := os.Stdout.Write(pkg.buf.Bytes())
	if err != nil {
		log.Fatal(err)
	}
	pkg.buf.Reset() // Not needed, but it's a flush.
}

var newlineBytes = []byte("\n\n") // We never ask for more than 2.

// newlines guarantees there are n newlines at the end of the buffer.
func (pkg *Package) newlines(n int) {
	for !bytes.HasSuffix(pkg.buf.Bytes(), newlineBytes[:n]) {
		pkg.buf.WriteRune('\n')
	}
}
Rob Pike's avatar
Rob Pike committed
109 110 111 112

// emit prints the node.
func (pkg *Package) emit(comment string, node ast.Node) {
	if node != nil {
Rob Pike's avatar
Rob Pike committed
113
		err := format.Node(&pkg.buf, pkg.fs, node)
Rob Pike's avatar
Rob Pike committed
114 115 116
		if err != nil {
			log.Fatal(err)
		}
Rob Pike's avatar
Rob Pike committed
117
		if comment != "" {
118
			pkg.newlines(2) // Guarantee blank line before comment.
Rob Pike's avatar
Rob Pike committed
119
			doc.ToText(&pkg.buf, comment, "    ", "\t", 80)
Rob Pike's avatar
Rob Pike committed
120
		}
Rob Pike's avatar
Rob Pike committed
121
		pkg.newlines(1)
Rob Pike's avatar
Rob Pike committed
122 123 124
	}
}

Rob Pike's avatar
Rob Pike committed
125 126
var formatBuf bytes.Buffer // Reusable to avoid allocation.

Rob Pike's avatar
Rob Pike committed
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
// formatNode is a helper function for printing.
func (pkg *Package) formatNode(node ast.Node) []byte {
	formatBuf.Reset()
	format.Node(&formatBuf, pkg.fs, node)
	return formatBuf.Bytes()
}

// oneLineFunc prints a function declaration as a single line.
func (pkg *Package) oneLineFunc(decl *ast.FuncDecl) {
	decl.Doc = nil
	decl.Body = nil
	pkg.emit("", decl)
}

// oneLineValueGenDecl prints a var or const declaration as a single line.
func (pkg *Package) oneLineValueGenDecl(decl *ast.GenDecl) {
	decl.Doc = nil
	dotDotDot := ""
	if len(decl.Specs) > 1 {
		dotDotDot = " ..."
	}
	// Find the first relevant spec.
	for i, spec := range decl.Specs {
		valueSpec := spec.(*ast.ValueSpec) // Must succeed; we can't mix types in one genDecl.
		if !isExported(valueSpec.Names[0].Name) {
			continue
		}
		typ := ""
		if valueSpec.Type != nil {
			typ = fmt.Sprintf(" %s", pkg.formatNode(valueSpec.Type))
		}
		val := ""
		if i < len(valueSpec.Values) && valueSpec.Values[i] != nil {
			val = fmt.Sprintf(" = %s", pkg.formatNode(valueSpec.Values[i]))
		}
Rob Pike's avatar
Rob Pike committed
162
		pkg.Printf("%s %s%s%s%s\n", decl.Tok, valueSpec.Names[0], typ, val, dotDotDot)
Rob Pike's avatar
Rob Pike committed
163 164 165 166 167 168 169 170 171 172
		break
	}
}

// oneLineTypeDecl prints a type declaration as a single line.
func (pkg *Package) oneLineTypeDecl(spec *ast.TypeSpec) {
	spec.Doc = nil
	spec.Comment = nil
	switch spec.Type.(type) {
	case *ast.InterfaceType:
Rob Pike's avatar
Rob Pike committed
173
		pkg.Printf("type %s interface { ... }\n", spec.Name)
Rob Pike's avatar
Rob Pike committed
174
	case *ast.StructType:
Rob Pike's avatar
Rob Pike committed
175
		pkg.Printf("type %s struct { ... }\n", spec.Name)
Rob Pike's avatar
Rob Pike committed
176
	default:
Rob Pike's avatar
Rob Pike committed
177
		pkg.Printf("type %s %s\n", spec.Name, pkg.formatNode(spec.Type))
Rob Pike's avatar
Rob Pike committed
178 179 180 181 182
	}
}

// packageDoc prints the docs for the package (package doc plus one-liners of the rest).
func (pkg *Package) packageDoc() {
Rob Pike's avatar
Rob Pike committed
183 184 185 186 187 188 189 190 191 192
	defer pkg.flush()
	pkg.packageClause(false)

	doc.ToText(&pkg.buf, pkg.doc.Doc, "", "\t", 80)
	pkg.newlines(2)

	pkg.valueSummary(pkg.doc.Consts)
	pkg.valueSummary(pkg.doc.Vars)
	pkg.funcSummary(pkg.doc.Funcs)
	pkg.typeSummary()
193
	pkg.bugs()
Rob Pike's avatar
Rob Pike committed
194 195 196 197 198 199 200 201 202 203 204 205
}

// packageClause prints the package clause.
// The argument boolean, if true, suppresses the output if the
// user's argument is identical to the actual package path or
// is empty, meaning it's the current directory.
func (pkg *Package) packageClause(checkUserPath bool) {
	if checkUserPath {
		if pkg.userPath == "" || pkg.userPath == pkg.build.ImportPath {
			return
		}
	}
Rob Pike's avatar
Rob Pike committed
206 207 208 209
	importPath := pkg.build.ImportComment
	if importPath == "" {
		importPath = pkg.build.ImportPath
	}
Rob Pike's avatar
Rob Pike committed
210
	pkg.Printf("package %s // import %q\n\n", pkg.name, importPath)
Rob Pike's avatar
Rob Pike committed
211
	if importPath != pkg.build.ImportPath {
Rob Pike's avatar
Rob Pike committed
212
		pkg.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath)
Rob Pike's avatar
Rob Pike committed
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
	}
}

// valueSummary prints a one-line summary for each set of values and constants.
func (pkg *Package) valueSummary(values []*doc.Value) {
	for _, value := range values {
		// Only print first item in spec, show ... to stand for the rest.
		spec := value.Decl.Specs[0].(*ast.ValueSpec) // Must succeed.
		exported := true
		for _, name := range spec.Names {
			if !isExported(name.Name) {
				exported = false
				break
			}
		}
		if exported {
			pkg.oneLineValueGenDecl(value.Decl)
		}
	}
}

// funcSummary prints a one-line summary for each function.
func (pkg *Package) funcSummary(funcs []*doc.Func) {
	for _, fun := range funcs {
		decl := fun.Decl
		// Exported functions only. The go/doc package does not include methods here.
		if isExported(fun.Name) {
			pkg.oneLineFunc(decl)
		}
	}
}

// typeSummary prints a one-line summary for each type.
func (pkg *Package) typeSummary() {
	for _, typ := range pkg.doc.Types {
		for _, spec := range typ.Decl.Specs {
			typeSpec := spec.(*ast.TypeSpec) // Must succeed.
			if isExported(typeSpec.Name.Name) {
				pkg.oneLineTypeDecl(typeSpec)
			}
		}
	}
}

257 258 259 260 261 262 263 264 265 266 267 268
// bugs prints the BUGS information for the package.
// TODO: Provide access to TODOs and NOTEs as well (very noisy so off by default)?
func (pkg *Package) bugs() {
	if pkg.doc.Notes["BUG"] == nil {
		return
	}
	pkg.Printf("\n")
	for _, note := range pkg.doc.Notes["BUG"] {
		pkg.Printf("%s: %v\n", "BUG", note.Body)
	}
}

269 270 271
// findValues finds the doc.Values that describe the symbol.
func (pkg *Package) findValues(symbol string, docValues []*doc.Value) (values []*doc.Value) {
	for _, value := range docValues {
Rob Pike's avatar
Rob Pike committed
272 273
		for _, name := range value.Names {
			if match(symbol, name) {
274
				values = append(values, value)
Rob Pike's avatar
Rob Pike committed
275 276 277
			}
		}
	}
278
	return
Rob Pike's avatar
Rob Pike committed
279 280
}

281 282
// findFuncs finds the doc.Funcs that describes the symbol.
func (pkg *Package) findFuncs(symbol string) (funcs []*doc.Func) {
Rob Pike's avatar
Rob Pike committed
283 284
	for _, fun := range pkg.doc.Funcs {
		if match(symbol, fun.Name) {
285
			funcs = append(funcs, fun)
Rob Pike's avatar
Rob Pike committed
286 287
		}
	}
288
	return
Rob Pike's avatar
Rob Pike committed
289 290
}

291
// findTypes finds the doc.Types that describes the symbol.
292
// If symbol is empty, it finds all exported types.
293
func (pkg *Package) findTypes(symbol string) (types []*doc.Type) {
Rob Pike's avatar
Rob Pike committed
294
	for _, typ := range pkg.doc.Types {
295
		if symbol == "" && isExported(typ.Name) || match(symbol, typ.Name) {
296
			types = append(types, typ)
Rob Pike's avatar
Rob Pike committed
297 298
		}
	}
299
	return
Rob Pike's avatar
Rob Pike committed
300 301 302
}

// findTypeSpec returns the ast.TypeSpec within the declaration that defines the symbol.
303
// The name must match exactly.
Rob Pike's avatar
Rob Pike committed
304 305 306
func (pkg *Package) findTypeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec {
	for _, spec := range decl.Specs {
		typeSpec := spec.(*ast.TypeSpec) // Must succeed.
307
		if symbol == typeSpec.Name.Name {
Rob Pike's avatar
Rob Pike committed
308 309 310 311 312 313
			return typeSpec
		}
	}
	return nil
}

314 315
// symbolDoc prints the docs for symbol. There may be multiple matches.
// If symbol matches a type, output includes its methods factories and associated constants.
316
// If there is no top-level symbol, symbolDoc looks for methods that match.
Rob Pike's avatar
Rob Pike committed
317
func (pkg *Package) symbolDoc(symbol string) {
Rob Pike's avatar
Rob Pike committed
318
	defer pkg.flush()
319
	found := false
Rob Pike's avatar
Rob Pike committed
320
	// Functions.
321
	for _, fun := range pkg.findFuncs(symbol) {
Rob Pike's avatar
Rob Pike committed
322 323 324
		if !found {
			pkg.packageClause(true)
		}
Rob Pike's avatar
Rob Pike committed
325 326 327 328
		// Symbol is a function.
		decl := fun.Decl
		decl.Body = nil
		pkg.emit(fun.Doc, decl)
329
		found = true
Rob Pike's avatar
Rob Pike committed
330 331
	}
	// Constants and variables behave the same.
332 333 334
	values := pkg.findValues(symbol, pkg.doc.Consts)
	values = append(values, pkg.findValues(symbol, pkg.doc.Vars)...)
	for _, value := range values {
Rob Pike's avatar
Rob Pike committed
335 336 337
		if !found {
			pkg.packageClause(true)
		}
Rob Pike's avatar
Rob Pike committed
338
		pkg.emit(value.Doc, value.Decl)
339
		found = true
Rob Pike's avatar
Rob Pike committed
340 341
	}
	// Types.
342
	for _, typ := range pkg.findTypes(symbol) {
Rob Pike's avatar
Rob Pike committed
343 344 345
		if !found {
			pkg.packageClause(true)
		}
346 347
		decl := typ.Decl
		spec := pkg.findTypeSpec(decl, typ.Name)
348
		trimUnexportedElems(spec)
349 350 351 352 353 354
		// If there are multiple types defined, reduce to just this one.
		if len(decl.Specs) > 1 {
			decl.Specs = []ast.Spec{spec}
		}
		pkg.emit(typ.Doc, decl)
		// Show associated methods, constants, etc.
355 356 357
		if len(typ.Consts) > 0 || len(typ.Vars) > 0 || len(typ.Funcs) > 0 || len(typ.Methods) > 0 {
			pkg.Printf("\n")
		}
358 359 360 361 362 363 364
		pkg.valueSummary(typ.Consts)
		pkg.valueSummary(typ.Vars)
		pkg.funcSummary(typ.Funcs)
		pkg.funcSummary(typ.Methods)
		found = true
	}
	if !found {
365 366 367 368
		// See if there are methods.
		if !pkg.printMethodDoc("", symbol) {
			log.Printf("symbol %s not present in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)
		}
Rob Pike's avatar
Rob Pike committed
369 370 371
	}
}

372 373 374
// trimUnexportedElems modifies spec in place to elide unexported fields from
// structs and methods from interfaces (unless the unexported flag is set).
func trimUnexportedElems(spec *ast.TypeSpec) {
Rob Pike's avatar
Rob Pike committed
375
	if *unexported {
Mikio Hara's avatar
Mikio Hara committed
376
		return
Rob Pike's avatar
Rob Pike committed
377
	}
378 379 380 381 382
	switch typ := spec.Type.(type) {
	case *ast.StructType:
		typ.Fields = trimUnexportedFields(typ.Fields, "fields")
	case *ast.InterfaceType:
		typ.Methods = trimUnexportedFields(typ.Methods, "methods")
Rob Pike's avatar
Rob Pike committed
383
	}
384 385 386 387
}

// trimUnexportedFields returns the field list trimmed of unexported fields.
func trimUnexportedFields(fields *ast.FieldList, what string) *ast.FieldList {
Rob Pike's avatar
Rob Pike committed
388
	trimmed := false
389 390 391
	list := make([]*ast.Field, 0, len(fields.List))
	for _, field := range fields.List {
		// Trims if any is unexported. Good enough in practice.
Rob Pike's avatar
Rob Pike committed
392 393 394 395 396 397 398 399 400 401 402 403
		ok := true
		for _, name := range field.Names {
			if !isExported(name.Name) {
				trimmed = true
				ok = false
				break
			}
		}
		if ok {
			list = append(list, field)
		}
	}
404 405 406 407 408 409 410 411 412
	if !trimmed {
		return fields
	}
	unexportedField := &ast.Field{
		Type: ast.NewIdent(""), // Hack: printer will treat this as a field with a named type.
		Comment: &ast.CommentGroup{
			List: []*ast.Comment{
				&ast.Comment{
					Text: fmt.Sprintf("// Has unexported %s.\n", what),
Rob Pike's avatar
Rob Pike committed
413 414
				},
			},
415 416 417 418 419 420
		},
	}
	return &ast.FieldList{
		Opening: fields.Opening,
		List:    append(list, unexportedField),
		Closing: fields.Closing,
Rob Pike's avatar
Rob Pike committed
421 422 423
	}
}

424 425 426 427
// printMethodDoc prints the docs for matches of symbol.method.
// If symbol is empty, it prints all methods that match the name.
// It reports whether it found any methods.
func (pkg *Package) printMethodDoc(symbol, method string) bool {
Rob Pike's avatar
Rob Pike committed
428
	defer pkg.flush()
429 430
	types := pkg.findTypes(symbol)
	if types == nil {
431 432 433
		if symbol == "" {
			return false
		}
Rob Pike's avatar
Rob Pike committed
434 435
		log.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)
	}
436 437 438 439 440 441 442 443 444
	found := false
	for _, typ := range types {
		for _, meth := range typ.Methods {
			if match(method, meth.Name) {
				decl := meth.Decl
				decl.Body = nil
				pkg.emit(meth.Doc, decl)
				found = true
			}
Rob Pike's avatar
Rob Pike committed
445 446
		}
	}
447 448 449 450 451 452 453
	return found
}

// methodDoc prints the docs for matches of symbol.method.
func (pkg *Package) methodDoc(symbol, method string) {
	defer pkg.flush()
	if !pkg.printMethodDoc(symbol, method) {
454 455
		log.Fatalf("no method %s.%s in package %s installed in %q", symbol, method, pkg.name, pkg.build.ImportPath)
	}
Rob Pike's avatar
Rob Pike committed
456 457 458 459 460 461 462 463 464
}

// match reports whether the user's symbol matches the program's.
// A lower-case character in the user's string matches either case in the program's.
// The program string must be exported.
func match(user, program string) bool {
	if !isExported(program) {
		return false
	}
Rob Pike's avatar
Rob Pike committed
465 466 467
	if *matchCase {
		return user == program
	}
Rob Pike's avatar
Rob Pike committed
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
	for _, u := range user {
		p, w := utf8.DecodeRuneInString(program)
		program = program[w:]
		if u == p {
			continue
		}
		if unicode.IsLower(u) && simpleFold(u) == simpleFold(p) {
			continue
		}
		return false
	}
	return program == ""
}

// simpleFold returns the minimum rune equivalent to r
// under Unicode-defined simple case folding.
func simpleFold(r rune) rune {
	for {
		r1 := unicode.SimpleFold(r)
		if r1 <= r {
			return r1 // wrapped around, found min
		}
		r = r1
	}
}