Source file src/pkg/go/doc/example.go
1 // Copyright 2011 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 // Extract example functions from file ASTs. 6 7 package doc 8 9 import ( 10 "go/ast" 11 "go/token" 12 "regexp" 13 "sort" 14 "strings" 15 "unicode" 16 "unicode/utf8" 17 ) 18 19 type Example struct { 20 Name string // name of the item being exemplified 21 Doc string // example function doc string 22 Code ast.Node 23 Comments []*ast.CommentGroup 24 Output string // expected output 25 } 26 27 func Examples(files ...*ast.File) []*Example { 28 var list []*Example 29 for _, file := range files { 30 hasTests := false // file contains tests or benchmarks 31 numDecl := 0 // number of non-import declarations in the file 32 var flist []*Example 33 for _, decl := range file.Decls { 34 if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT { 35 numDecl++ 36 continue 37 } 38 f, ok := decl.(*ast.FuncDecl) 39 if !ok { 40 continue 41 } 42 numDecl++ 43 name := f.Name.Name 44 if isTest(name, "Test") || isTest(name, "Benchmark") { 45 hasTests = true 46 continue 47 } 48 if !isTest(name, "Example") { 49 continue 50 } 51 var doc string 52 if f.Doc != nil { 53 doc = f.Doc.Text() 54 } 55 flist = append(flist, &Example{ 56 Name: name[len("Example"):], 57 Doc: doc, 58 Code: f.Body, 59 Comments: file.Comments, 60 Output: exampleOutput(f, file.Comments), 61 }) 62 } 63 if !hasTests && numDecl > 1 && len(flist) == 1 { 64 // If this file only has one example function, some 65 // other top-level declarations, and no tests or 66 // benchmarks, use the whole file as the example. 67 flist[0].Code = file 68 } 69 list = append(list, flist...) 70 } 71 sort.Sort(exampleByName(list)) 72 return list 73 } 74 75 var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*output:`) 76 77 func exampleOutput(fun *ast.FuncDecl, comments []*ast.CommentGroup) string { 78 // find the last comment in the function 79 var last *ast.CommentGroup 80 for _, cg := range comments { 81 if cg.Pos() < fun.Pos() { 82 continue 83 } 84 if cg.End() > fun.End() { 85 break 86 } 87 last = cg 88 } 89 if last != nil { 90 // test that it begins with the correct prefix 91 text := last.Text() 92 if loc := outputPrefix.FindStringIndex(text); loc != nil { 93 return strings.TrimSpace(text[loc[1]:]) 94 } 95 } 96 return "" // no suitable comment found 97 } 98 99 // isTest tells whether name looks like a test, example, or benchmark. 100 // It is a Test (say) if there is a character after Test that is not a 101 // lower-case letter. (We don't want Testiness.) 102 func isTest(name, prefix string) bool { 103 if !strings.HasPrefix(name, prefix) { 104 return false 105 } 106 if len(name) == len(prefix) { // "Test" is ok 107 return true 108 } 109 rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) 110 return !unicode.IsLower(rune) 111 } 112 113 type exampleByName []*Example 114 115 func (s exampleByName) Len() int { return len(s) } 116 func (s exampleByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 117 func (s exampleByName) Less(i, j int) bool { return s[i].Name < s[j].Name }