1// Copyright 2013 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//go:build go1.16 6// +build go1.16 7 8package doc_test 9 10import ( 11 "bytes" 12 "fmt" 13 "go/ast" 14 "go/doc" 15 "go/format" 16 "go/parser" 17 "go/token" 18 "reflect" 19 "strings" 20 "testing" 21) 22 23const exampleTestFile = ` 24package foo_test 25 26import ( 27 "flag" 28 "fmt" 29 "log" 30 "sort" 31 "os/exec" 32) 33 34func ExampleHello() { 35 fmt.Println("Hello, world!") 36 // Output: Hello, world! 37} 38 39func ExampleImport() { 40 out, err := exec.Command("date").Output() 41 if err != nil { 42 log.Fatal(err) 43 } 44 fmt.Printf("The date is %s\n", out) 45} 46 47func ExampleKeyValue() { 48 v := struct { 49 a string 50 b int 51 }{ 52 a: "A", 53 b: 1, 54 } 55 fmt.Print(v) 56 // Output: a: "A", b: 1 57} 58 59func ExampleKeyValueImport() { 60 f := flag.Flag{ 61 Name: "play", 62 } 63 fmt.Print(f) 64 // Output: Name: "play" 65} 66 67var keyValueTopDecl = struct { 68 a string 69 b int 70}{ 71 a: "B", 72 b: 2, 73} 74 75func ExampleKeyValueTopDecl() { 76 fmt.Print(keyValueTopDecl) 77 // Output: a: "B", b: 2 78} 79 80// Person represents a person by name and age. 81type Person struct { 82 Name string 83 Age int 84} 85 86// String returns a string representation of the Person. 87func (p Person) String() string { 88 return fmt.Sprintf("%s: %d", p.Name, p.Age) 89} 90 91// ByAge implements sort.Interface for []Person based on 92// the Age field. 93type ByAge []Person 94 95// Len returns the number of elements in ByAge. 96func (a (ByAge)) Len() int { return len(a) } 97 98// Swap swaps the elements in ByAge. 99func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 100func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age } 101 102// people is the array of Person 103var people = []Person{ 104 {"Bob", 31}, 105 {"John", 42}, 106 {"Michael", 17}, 107 {"Jenny", 26}, 108} 109 110func ExampleSort() { 111 fmt.Println(people) 112 sort.Sort(ByAge(people)) 113 fmt.Println(people) 114 // Output: 115 // [Bob: 31 John: 42 Michael: 17 Jenny: 26] 116 // [Michael: 17 Jenny: 26 Bob: 31 John: 42] 117} 118` 119 120var exampleTestCases = []struct { 121 Name, Play, Output string 122}{ 123 { 124 Name: "Hello", 125 Play: exampleHelloPlay, 126 Output: "Hello, world!\n", 127 }, 128 { 129 Name: "Import", 130 Play: exampleImportPlay, 131 }, 132 { 133 Name: "KeyValue", 134 Play: exampleKeyValuePlay, 135 Output: "a: \"A\", b: 1\n", 136 }, 137 { 138 Name: "KeyValueImport", 139 Play: exampleKeyValueImportPlay, 140 Output: "Name: \"play\"\n", 141 }, 142 { 143 Name: "KeyValueTopDecl", 144 Play: exampleKeyValueTopDeclPlay, 145 Output: "a: \"B\", b: 2\n", 146 }, 147 { 148 Name: "Sort", 149 Play: exampleSortPlay, 150 Output: "[Bob: 31 John: 42 Michael: 17 Jenny: 26]\n[Michael: 17 Jenny: 26 Bob: 31 John: 42]\n", 151 }, 152} 153 154const exampleHelloPlay = `package main 155 156import ( 157 "fmt" 158) 159 160func main() { 161 fmt.Println("Hello, world!") 162} 163` 164const exampleImportPlay = `package main 165 166import ( 167 "fmt" 168 "log" 169 "os/exec" 170) 171 172func main() { 173 out, err := exec.Command("date").Output() 174 if err != nil { 175 log.Fatal(err) 176 } 177 fmt.Printf("The date is %s\n", out) 178} 179` 180 181const exampleKeyValuePlay = `package main 182 183import ( 184 "fmt" 185) 186 187func main() { 188 v := struct { 189 a string 190 b int 191 }{ 192 a: "A", 193 b: 1, 194 } 195 fmt.Print(v) 196} 197` 198 199const exampleKeyValueImportPlay = `package main 200 201import ( 202 "flag" 203 "fmt" 204) 205 206func main() { 207 f := flag.Flag{ 208 Name: "play", 209 } 210 fmt.Print(f) 211} 212` 213 214const exampleKeyValueTopDeclPlay = `package main 215 216import ( 217 "fmt" 218) 219 220var keyValueTopDecl = struct { 221 a string 222 b int 223}{ 224 a: "B", 225 b: 2, 226} 227 228func main() { 229 fmt.Print(keyValueTopDecl) 230} 231` 232 233const exampleSortPlay = `package main 234 235import ( 236 "fmt" 237 "sort" 238) 239 240// Person represents a person by name and age. 241type Person struct { 242 Name string 243 Age int 244} 245 246// String returns a string representation of the Person. 247func (p Person) String() string { 248 return fmt.Sprintf("%s: %d", p.Name, p.Age) 249} 250 251// ByAge implements sort.Interface for []Person based on 252// the Age field. 253type ByAge []Person 254 255// Len returns the number of elements in ByAge. 256func (a ByAge) Len() int { return len(a) } 257 258// Swap swaps the elements in ByAge. 259func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 260func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age } 261 262// people is the array of Person 263var people = []Person{ 264 {"Bob", 31}, 265 {"John", 42}, 266 {"Michael", 17}, 267 {"Jenny", 26}, 268} 269 270func main() { 271 fmt.Println(people) 272 sort.Sort(ByAge(people)) 273 fmt.Println(people) 274} 275` 276 277func TestExamples(t *testing.T) { 278 fset := token.NewFileSet() 279 file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleTestFile), parser.ParseComments) 280 if err != nil { 281 t.Fatal(err) 282 } 283 for i, e := range doc.Examples(file) { 284 c := exampleTestCases[i] 285 if e.Name != c.Name { 286 t.Errorf("got Name == %q, want %q", e.Name, c.Name) 287 } 288 if w := c.Play; w != "" { 289 g := formatFile(t, fset, e.Play) 290 if g != w { 291 t.Errorf("%s: got Play == %q, want %q", c.Name, g, w) 292 } 293 } 294 if g, w := e.Output, c.Output; g != w { 295 t.Errorf("%s: got Output == %q, want %q", c.Name, g, w) 296 } 297 } 298} 299 300const exampleWholeFile = `package foo_test 301 302type X int 303 304func (X) Foo() { 305} 306 307func (X) TestBlah() { 308} 309 310func (X) BenchmarkFoo() { 311} 312 313func Example() { 314 fmt.Println("Hello, world!") 315 // Output: Hello, world! 316} 317` 318 319const exampleWholeFileOutput = `package main 320 321type X int 322 323func (X) Foo() { 324} 325 326func (X) TestBlah() { 327} 328 329func (X) BenchmarkFoo() { 330} 331 332func main() { 333 fmt.Println("Hello, world!") 334} 335` 336 337const exampleWholeFileFunction = `package foo_test 338 339func Foo(x int) { 340} 341 342func Example() { 343 fmt.Println("Hello, world!") 344 // Output: Hello, world! 345} 346` 347 348const exampleWholeFileFunctionOutput = `package main 349 350func Foo(x int) { 351} 352 353func main() { 354 fmt.Println("Hello, world!") 355} 356` 357 358var exampleWholeFileTestCases = []struct { 359 Title, Source, Play, Output string 360}{ 361 { 362 "Methods", 363 exampleWholeFile, 364 exampleWholeFileOutput, 365 "Hello, world!\n", 366 }, 367 { 368 "Function", 369 exampleWholeFileFunction, 370 exampleWholeFileFunctionOutput, 371 "Hello, world!\n", 372 }, 373} 374 375func TestExamplesWholeFile(t *testing.T) { 376 for _, c := range exampleWholeFileTestCases { 377 fset := token.NewFileSet() 378 file, err := parser.ParseFile(fset, "test.go", strings.NewReader(c.Source), parser.ParseComments) 379 if err != nil { 380 t.Fatal(err) 381 } 382 es := doc.Examples(file) 383 if len(es) != 1 { 384 t.Fatalf("%s: wrong number of examples; got %d want 1", c.Title, len(es)) 385 } 386 e := es[0] 387 if e.Name != "" { 388 t.Errorf("%s: got Name == %q, want %q", c.Title, e.Name, "") 389 } 390 if g, w := formatFile(t, fset, e.Play), c.Play; g != w { 391 t.Errorf("%s: got Play == %q, want %q", c.Title, g, w) 392 } 393 if g, w := e.Output, c.Output; g != w { 394 t.Errorf("%s: got Output == %q, want %q", c.Title, g, w) 395 } 396 } 397} 398 399const exampleInspectSignature = `package foo_test 400 401import ( 402 "bytes" 403 "io" 404) 405 406func getReader() io.Reader { return nil } 407 408func do(b bytes.Reader) {} 409 410func Example() { 411 getReader() 412 do() 413 // Output: 414} 415 416func ExampleIgnored() { 417} 418` 419 420const exampleInspectSignatureOutput = `package main 421 422import ( 423 "bytes" 424 "io" 425) 426 427func getReader() io.Reader { return nil } 428 429func do(b bytes.Reader) {} 430 431func main() { 432 getReader() 433 do() 434} 435` 436 437func TestExampleInspectSignature(t *testing.T) { 438 // Verify that "bytes" and "io" are imported. See issue #28492. 439 fset := token.NewFileSet() 440 file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleInspectSignature), parser.ParseComments) 441 if err != nil { 442 t.Fatal(err) 443 } 444 es := doc.Examples(file) 445 if len(es) != 2 { 446 t.Fatalf("wrong number of examples; got %d want 2", len(es)) 447 } 448 // We are interested in the first example only. 449 e := es[0] 450 if e.Name != "" { 451 t.Errorf("got Name == %q, want %q", e.Name, "") 452 } 453 if g, w := formatFile(t, fset, e.Play), exampleInspectSignatureOutput; g != w { 454 t.Errorf("got Play == %q, want %q", g, w) 455 } 456 if g, w := e.Output, ""; g != w { 457 t.Errorf("got Output == %q, want %q", g, w) 458 } 459} 460 461const exampleEmpty = ` 462package p 463func Example() {} 464func Example_a() 465` 466 467const exampleEmptyOutput = `package main 468 469func main() {} 470func main() 471` 472 473func TestExampleEmpty(t *testing.T) { 474 fset := token.NewFileSet() 475 file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleEmpty), parser.ParseComments) 476 if err != nil { 477 t.Fatal(err) 478 } 479 480 es := doc.Examples(file) 481 if len(es) != 1 { 482 t.Fatalf("wrong number of examples; got %d want 1", len(es)) 483 } 484 e := es[0] 485 if e.Name != "" { 486 t.Errorf("got Name == %q, want %q", e.Name, "") 487 } 488 if g, w := formatFile(t, fset, e.Play), exampleEmptyOutput; g != w { 489 t.Errorf("got Play == %q, want %q", g, w) 490 } 491 if g, w := e.Output, ""; g != w { 492 t.Errorf("got Output == %q, want %q", g, w) 493 } 494} 495 496func formatFile(t *testing.T, fset *token.FileSet, n *ast.File) string { 497 if n == nil { 498 return "<nil>" 499 } 500 var buf bytes.Buffer 501 if err := format.Node(&buf, fset, n); err != nil { 502 t.Fatal(err) 503 } 504 return buf.String() 505} 506 507// This example illustrates how to use NewFromFiles 508// to compute package documentation with examples. 509func ExampleNewFromFiles() { 510 // src and test are two source files that make up 511 // a package whose documentation will be computed. 512 const src = ` 513// This is the package comment. 514package p 515 516import "fmt" 517 518// This comment is associated with the Greet function. 519func Greet(who string) { 520 fmt.Printf("Hello, %s!\n", who) 521} 522` 523 const test = ` 524package p_test 525 526// This comment is associated with the ExampleGreet_world example. 527func ExampleGreet_world() { 528 Greet("world") 529} 530` 531 532 // Create the AST by parsing src and test. 533 fset := token.NewFileSet() 534 files := []*ast.File{ 535 mustParse(fset, "src.go", src), 536 mustParse(fset, "src_test.go", test), 537 } 538 539 // Compute package documentation with examples. 540 p, err := doc.NewFromFiles(fset, files, "example.com/p") 541 if err != nil { 542 panic(err) 543 } 544 545 fmt.Printf("package %s - %s", p.Name, p.Doc) 546 fmt.Printf("func %s - %s", p.Funcs[0].Name, p.Funcs[0].Doc) 547 fmt.Printf(" ⤷ example with suffix %q - %s", p.Funcs[0].Examples[0].Suffix, p.Funcs[0].Examples[0].Doc) 548 549 // Output: 550 // package p - This is the package comment. 551 // func Greet - This comment is associated with the Greet function. 552 // ⤷ example with suffix "world" - This comment is associated with the ExampleGreet_world example. 553} 554 555func TestClassifyExamples(t *testing.T) { 556 const src = ` 557package p 558 559const Const1 = 0 560var Var1 = 0 561 562type ( 563 Type1 int 564 Type1_Foo int 565 Type1_foo int 566 type2 int 567 568 Embed struct { Type1 } 569 Uembed struct { type2 } 570) 571 572func Func1() {} 573func Func1_Foo() {} 574func Func1_foo() {} 575func func2() {} 576 577func (Type1) Func1() {} 578func (Type1) Func1_Foo() {} 579func (Type1) Func1_foo() {} 580func (Type1) func2() {} 581 582func (type2) Func1() {} 583 584type ( 585 Conflict int 586 Conflict_Conflict int 587 Conflict_conflict int 588) 589 590func (Conflict) Conflict() {} 591` 592 const test = ` 593package p_test 594 595func ExampleConst1() {} // invalid - no support for consts and vars 596func ExampleVar1() {} // invalid - no support for consts and vars 597 598func Example() {} 599func Example_() {} // invalid - suffix must start with a lower-case letter 600func Example_suffix() {} 601func Example_suffix_xX_X_x() {} 602func Example_世界() {} // invalid - suffix must start with a lower-case letter 603func Example_123() {} // invalid - suffix must start with a lower-case letter 604func Example_BadSuffix() {} // invalid - suffix must start with a lower-case letter 605 606func ExampleType1() {} 607func ExampleType1_() {} // invalid - suffix must start with a lower-case letter 608func ExampleType1_suffix() {} 609func ExampleType1_BadSuffix() {} // invalid - suffix must start with a lower-case letter 610func ExampleType1_Foo() {} 611func ExampleType1_Foo_suffix() {} 612func ExampleType1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter 613func ExampleType1_foo() {} 614func ExampleType1_foo_suffix() {} 615func ExampleType1_foo_Suffix() {} // matches Type1, instead of Type1_foo 616func Exampletype2() {} // invalid - cannot match unexported 617 618func ExampleFunc1() {} 619func ExampleFunc1_() {} // invalid - suffix must start with a lower-case letter 620func ExampleFunc1_suffix() {} 621func ExampleFunc1_BadSuffix() {} // invalid - suffix must start with a lower-case letter 622func ExampleFunc1_Foo() {} 623func ExampleFunc1_Foo_suffix() {} 624func ExampleFunc1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter 625func ExampleFunc1_foo() {} 626func ExampleFunc1_foo_suffix() {} 627func ExampleFunc1_foo_Suffix() {} // matches Func1, instead of Func1_foo 628func Examplefunc1() {} // invalid - cannot match unexported 629 630func ExampleType1_Func1() {} 631func ExampleType1_Func1_() {} // invalid - suffix must start with a lower-case letter 632func ExampleType1_Func1_suffix() {} 633func ExampleType1_Func1_BadSuffix() {} // invalid - suffix must start with a lower-case letter 634func ExampleType1_Func1_Foo() {} 635func ExampleType1_Func1_Foo_suffix() {} 636func ExampleType1_Func1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter 637func ExampleType1_Func1_foo() {} 638func ExampleType1_Func1_foo_suffix() {} 639func ExampleType1_Func1_foo_Suffix() {} // matches Type1.Func1, instead of Type1.Func1_foo 640func ExampleType1_func2() {} // matches Type1, instead of Type1.func2 641 642func ExampleEmbed_Func1() {} // invalid - no support for forwarded methods from embedding exported type 643func ExampleUembed_Func1() {} // methods from embedding unexported types are OK 644func ExampleUembed_Func1_suffix() {} 645 646func ExampleConflict_Conflict() {} // ambiguous with either Conflict or Conflict_Conflict type 647func ExampleConflict_conflict() {} // ambiguous with either Conflict or Conflict_conflict type 648func ExampleConflict_Conflict_suffix() {} // ambiguous with either Conflict or Conflict_Conflict type 649func ExampleConflict_conflict_suffix() {} // ambiguous with either Conflict or Conflict_conflict type 650` 651 652 // Parse literal source code as a *doc.Package. 653 fset := token.NewFileSet() 654 files := []*ast.File{ 655 mustParse(fset, "src.go", src), 656 mustParse(fset, "src_test.go", test), 657 } 658 p, err := doc.NewFromFiles(fset, files, "example.com/p") 659 if err != nil { 660 t.Fatalf("doc.NewFromFiles: %v", err) 661 } 662 663 // Collect the association of examples to top-level identifiers. 664 got := map[string][]string{} 665 got[""] = exampleNames(p.Examples) 666 for _, f := range p.Funcs { 667 got[f.Name] = exampleNames(f.Examples) 668 } 669 for _, t := range p.Types { 670 got[t.Name] = exampleNames(t.Examples) 671 for _, f := range t.Funcs { 672 got[f.Name] = exampleNames(f.Examples) 673 } 674 for _, m := range t.Methods { 675 got[t.Name+"."+m.Name] = exampleNames(m.Examples) 676 } 677 } 678 679 want := map[string][]string{ 680 "": {"", "suffix", "suffix_xX_X_x"}, // Package-level examples. 681 682 "Type1": {"", "foo_Suffix", "func2", "suffix"}, 683 "Type1_Foo": {"", "suffix"}, 684 "Type1_foo": {"", "suffix"}, 685 686 "Func1": {"", "foo_Suffix", "suffix"}, 687 "Func1_Foo": {"", "suffix"}, 688 "Func1_foo": {"", "suffix"}, 689 690 "Type1.Func1": {"", "foo_Suffix", "suffix"}, 691 "Type1.Func1_Foo": {"", "suffix"}, 692 "Type1.Func1_foo": {"", "suffix"}, 693 694 "Uembed.Func1": {"", "suffix"}, 695 696 // These are implementation dependent due to the ambiguous parsing. 697 "Conflict_Conflict": {"", "suffix"}, 698 "Conflict_conflict": {"", "suffix"}, 699 } 700 701 for id := range got { 702 if !reflect.DeepEqual(got[id], want[id]) { 703 t.Errorf("classification mismatch for %q:\ngot %q\nwant %q", id, got[id], want[id]) 704 } 705 } 706} 707 708func exampleNames(exs []*doc.Example) (out []string) { 709 for _, ex := range exs { 710 out = append(out, ex.Suffix) 711 } 712 return out 713} 714 715func mustParse(fset *token.FileSet, filename, src string) *ast.File { 716 f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) 717 if err != nil { 718 panic(err) 719 } 720 return f 721} 722