// Copyright 2011 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. // Extract example functions from file ASTs. package doc import ( "go/ast" "go/token" "regexp" "sort" "strings" "unicode" "unicode/utf8" ) type Example struct { Name string // name of the item being exemplified Doc string // example function doc string Code ast.Node Comments []*ast.CommentGroup Output string // expected output } func Examples(files ...*ast.File) []*Example { var list []*Example for _, file := range files { hasTests := false // file contains tests or benchmarks numDecl := 0 // number of non-import declarations in the file var flist []*Example for _, decl := range file.Decls { if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT { numDecl++ continue } f, ok := decl.(*ast.FuncDecl) if !ok { continue } numDecl++ name := f.Name.Name if isTest(name, "Test") || isTest(name, "Benchmark") { hasTests = true continue } if !isTest(name, "Example") { continue } var doc string if f.Doc != nil { doc = f.Doc.Text() } flist = append(flist, &Example{ Name: name[len("Example"):], Doc: doc, Code: f.Body, Comments: file.Comments, Output: exampleOutput(f, file.Comments), }) } if !hasTests && numDecl > 1 && len(flist) == 1 { // If this file only has one example function, some // other top-level declarations, and no tests or // benchmarks, use the whole file as the example. flist[0].Code = file } list = append(list, flist...) } sort.Sort(exampleByName(list)) return list } var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*output:`) func exampleOutput(fun *ast.FuncDecl, comments []*ast.CommentGroup) string { // find the last comment in the function var last *ast.CommentGroup for _, cg := range comments { if cg.Pos() < fun.Pos() { continue } if cg.End() > fun.End() { break } last = cg } if last != nil { // test that it begins with the correct prefix text := last.Text() if loc := outputPrefix.FindStringIndex(text); loc != nil { return strings.TrimSpace(text[loc[1]:]) } } return "" // no suitable comment found } // isTest tells whether name looks like a test, example, or benchmark. // It is a Test (say) if there is a character after Test that is not a // lower-case letter. (We don't want Testiness.) func isTest(name, prefix string) bool { if !strings.HasPrefix(name, prefix) { return false } if len(name) == len(prefix) { // "Test" is ok return true } rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) return !unicode.IsLower(rune) } type exampleByName []*Example func (s exampleByName) Len() int { return len(s) } func (s exampleByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s exampleByName) Less(i, j int) bool { return s[i].Name < s[j].Name }