Name: name[len("Example"):],
Doc: doc,
Code: f.Body,
- Play: playExample(file, f.Body),
+ Play: playExample(file, f),
Comments: file.Comments,
Output: output,
Unordered: unordered,
// playExample synthesizes a new *ast.File based on the provided
// file with the provided function body as the body of main.
-func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
+func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
+ body := f.Body
+
if !strings.HasSuffix(file.Name.Name, "_test") {
// We don't support examples that are part of the
// greater package (yet).
return nil
}
- // Find top-level declarations in the file.
- topDecls := make(map[*ast.Object]bool)
+ // Collect top-level declarations in the file.
+ topDecls := make(map[*ast.Object]ast.Decl)
+ typMethods := make(map[string][]ast.Decl)
+
for _, decl := range file.Decls {
switch d := decl.(type) {
case *ast.FuncDecl:
- topDecls[d.Name.Obj] = true
+ if d.Recv == nil {
+ topDecls[d.Name.Obj] = d
+ } else {
+ if len(d.Recv.List) == 1 {
+ t := d.Recv.List[0].Type
+ tname, _ := baseTypeName(t)
+ typMethods[tname] = append(typMethods[tname], d)
+ }
+ }
case *ast.GenDecl:
for _, spec := range d.Specs {
switch s := spec.(type) {
case *ast.TypeSpec:
- topDecls[s.Name.Obj] = true
+ topDecls[s.Name.Obj] = d
case *ast.ValueSpec:
- for _, id := range s.Names {
- topDecls[id.Obj] = true
+ for _, name := range s.Names {
+ topDecls[name.Obj] = d
}
}
}
// Find unresolved identifiers and uses of top-level declarations.
unresolved := make(map[string]bool)
- usesTopDecl := false
+ var depDecls []ast.Decl
+ hasDepDecls := make(map[ast.Decl]bool)
+
var inspectFunc func(ast.Node) bool
inspectFunc = func(n ast.Node) bool {
- // For selector expressions, only inspect the left hand side.
- // (For an expression like fmt.Println, only add "fmt" to the
- // set of unresolved names, not "Println".)
- if e, ok := n.(*ast.SelectorExpr); ok {
+ switch e := n.(type) {
+ case *ast.Ident:
+ if e.Obj == nil {
+ unresolved[e.Name] = true
+ } else if d := topDecls[e.Obj]; d != nil {
+ if !hasDepDecls[d] {
+ hasDepDecls[d] = true
+ depDecls = append(depDecls, d)
+ }
+ }
+ return true
+ case *ast.SelectorExpr:
+ // For selector expressions, only inspect the left hand side.
+ // (For an expression like fmt.Println, only add "fmt" to the
+ // set of unresolved names, not "Println".)
ast.Inspect(e.X, inspectFunc)
return false
- }
- // For key value expressions, only inspect the value
- // as the key should be resolved by the type of the
- // composite literal.
- if e, ok := n.(*ast.KeyValueExpr); ok {
+ case *ast.KeyValueExpr:
+ // For key value expressions, only inspect the value
+ // as the key should be resolved by the type of the
+ // composite literal.
ast.Inspect(e.Value, inspectFunc)
return false
}
- if id, ok := n.(*ast.Ident); ok {
- if id.Obj == nil {
- unresolved[id.Name] = true
- } else if topDecls[id.Obj] {
- usesTopDecl = true
- }
- }
return true
}
ast.Inspect(body, inspectFunc)
- if usesTopDecl {
- // We don't support examples that are not self-contained (yet).
- return nil
+ for i := 0; i < len(depDecls); i++ {
+ switch d := depDecls[i].(type) {
+ case *ast.FuncDecl:
+ ast.Inspect(d.Body, inspectFunc)
+ case *ast.GenDecl:
+ for _, spec := range d.Specs {
+ switch s := spec.(type) {
+ case *ast.TypeSpec:
+ ast.Inspect(s.Type, inspectFunc)
+
+ depDecls = append(depDecls, typMethods[s.Name.Name]...)
+ case *ast.ValueSpec:
+ if s.Type != nil {
+ ast.Inspect(s.Type, inspectFunc)
+ }
+ for _, val := range s.Values {
+ ast.Inspect(val, inspectFunc)
+ }
+ }
+ }
+ }
}
// Remove predeclared identifiers from unresolved list.
// end position.
body, comments = stripOutputComment(body, comments)
+ // Include documentation belonging to dependent declarations.
+ for _, d := range depDecls {
+ switch d := d.(type) {
+ case *ast.GenDecl:
+ if d.Doc != nil {
+ comments = append(comments, d.Doc)
+ }
+ case *ast.FuncDecl:
+ if d.Doc != nil {
+ comments = append(comments, d.Doc)
+ }
+ }
+ }
+
// Synthesize import declaration.
importDecl := &ast.GenDecl{
Tok: token.IMPORT,
// Synthesize main function.
funcDecl := &ast.FuncDecl{
Name: ast.NewIdent("main"),
- Type: &ast.FuncType{Params: &ast.FieldList{}}, // FuncType.Params must be non-nil
+ Type: f.Type,
Body: body,
}
+ decls := make([]ast.Decl, 0, 2+len(depDecls))
+ decls = append(decls, importDecl)
+ decls = append(decls, depDecls...)
+ decls = append(decls, funcDecl)
+
+ sort.Slice(decls, func(i, j int) bool {
+ return decls[i].Pos() < decls[j].Pos()
+ })
+
+ sort.Slice(comments, func(i, j int) bool {
+ return comments[i].Pos() < comments[j].Pos()
+ })
+
// Synthesize file.
return &ast.File{
Name: ast.NewIdent("main"),
- Decls: []ast.Decl{importDecl, funcDecl},
+ Decls: decls,
Comments: comments,
}
}
"flag"
"fmt"
"log"
+ "sort"
"os/exec"
)
func ExampleKeyValueTopDecl() {
fmt.Print(keyValueTopDecl)
+ // Output: a: "B", b: 2
+}
+
+// Person represents a person by name and age.
+type Person struct {
+ Name string
+ Age int
+}
+
+// String returns a string representation of the Person.
+func (p Person) String() string {
+ return fmt.Sprintf("%s: %d", p.Name, p.Age)
+}
+
+// ByAge implements sort.Interface for []Person based on
+// the Age field.
+type ByAge []Person
+
+// Len returns the number of elements in ByAge.
+func (a (ByAge)) Len() int { return len(a) }
+
+// Swap swaps the elements in ByAge.
+func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
+
+// people is the array of Person
+var people = []Person{
+ {"Bob", 31},
+ {"John", 42},
+ {"Michael", 17},
+ {"Jenny", 26},
+}
+
+func ExampleSort() {
+ fmt.Println(people)
+ sort.Sort(ByAge(people))
+ fmt.Println(people)
+ // Output:
+ // [Bob: 31 John: 42 Michael: 17 Jenny: 26]
+ // [Michael: 17 Jenny: 26 Bob: 31 John: 42]
}
`
Output: "Name: \"play\"\n",
},
{
- Name: "KeyValueTopDecl",
- Play: "<nil>",
+ Name: "KeyValueTopDecl",
+ Play: exampleKeyValueTopDeclPlay,
+ Output: "a: \"B\", b: 2\n",
+ },
+ {
+ Name: "Sort",
+ Play: exampleSortPlay,
+ Output: "[Bob: 31 John: 42 Michael: 17 Jenny: 26]\n[Michael: 17 Jenny: 26 Bob: 31 John: 42]\n",
},
}
}
`
+const exampleKeyValueTopDeclPlay = `package main
+
+import (
+ "fmt"
+)
+
+var keyValueTopDecl = struct {
+ a string
+ b int
+}{
+ a: "B",
+ b: 2,
+}
+
+func main() {
+ fmt.Print(keyValueTopDecl)
+}
+`
+
+const exampleSortPlay = `package main
+
+import (
+ "fmt"
+ "sort"
+)
+
+// Person represents a person by name and age.
+type Person struct {
+ Name string
+ Age int
+}
+
+// String returns a string representation of the Person.
+func (p Person) String() string {
+ return fmt.Sprintf("%s: %d", p.Name, p.Age)
+}
+
+// ByAge implements sort.Interface for []Person based on
+// the Age field.
+type ByAge []Person
+
+// Len returns the number of elements in ByAge.
+func (a ByAge) Len() int { return len(a) }
+
+// Swap swaps the elements in ByAge.
+func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
+
+// people is the array of Person
+var people = []Person{
+ {"Bob", 31},
+ {"John", 42},
+ {"Michael", 17},
+ {"Jenny", 26},
+}
+
+func main() {
+ fmt.Println(people)
+ sort.Sort(ByAge(people))
+ fmt.Println(people)
+}
+`
+
func TestExamples(t *testing.T) {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleTestFile), parser.ParseComments)