Source file
src/go/doc/example.go
Documentation: go/doc
1
2
3
4
5
6
7 package doc
8
9 import (
10 "go/ast"
11 "go/token"
12 "internal/lazyregexp"
13 "path"
14 "sort"
15 "strconv"
16 "strings"
17 "unicode"
18 "unicode/utf8"
19 )
20
21
22 type Example struct {
23 Name string
24 Suffix string
25 Doc string
26 Code ast.Node
27 Play *ast.File
28 Comments []*ast.CommentGroup
29 Output string
30 Unordered bool
31 EmptyOutput bool
32 Order int
33 }
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 func Examples(testFiles ...*ast.File) []*Example {
51 var list []*Example
52 for _, file := range testFiles {
53 hasTests := false
54 numDecl := 0
55 var flist []*Example
56 for _, decl := range file.Decls {
57 if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT {
58 numDecl++
59 continue
60 }
61 f, ok := decl.(*ast.FuncDecl)
62 if !ok || f.Recv != nil {
63 continue
64 }
65 numDecl++
66 name := f.Name.Name
67 if isTest(name, "Test") || isTest(name, "Benchmark") {
68 hasTests = true
69 continue
70 }
71 if !isTest(name, "Example") {
72 continue
73 }
74 if params := f.Type.Params; len(params.List) != 0 {
75 continue
76 }
77 if f.Body == nil {
78 continue
79 }
80 var doc string
81 if f.Doc != nil {
82 doc = f.Doc.Text()
83 }
84 output, unordered, hasOutput := exampleOutput(f.Body, file.Comments)
85 flist = append(flist, &Example{
86 Name: name[len("Example"):],
87 Doc: doc,
88 Code: f.Body,
89 Play: playExample(file, f),
90 Comments: file.Comments,
91 Output: output,
92 Unordered: unordered,
93 EmptyOutput: output == "" && hasOutput,
94 Order: len(flist),
95 })
96 }
97 if !hasTests && numDecl > 1 && len(flist) == 1 {
98
99
100
101 flist[0].Code = file
102 flist[0].Play = playExampleFile(file)
103 }
104 list = append(list, flist...)
105 }
106
107 sort.Slice(list, func(i, j int) bool {
108 return list[i].Name < list[j].Name
109 })
110 return list
111 }
112
113 var outputPrefix = lazyregexp.New(`(?i)^[[:space:]]*(unordered )?output:`)
114
115
116 func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, unordered, ok bool) {
117 if _, last := lastComment(b, comments); last != nil {
118
119 text := last.Text()
120 if loc := outputPrefix.FindStringSubmatchIndex(text); loc != nil {
121 if loc[2] != -1 {
122 unordered = true
123 }
124 text = text[loc[1]:]
125
126 text = strings.TrimLeft(text, " ")
127 if len(text) > 0 && text[0] == '\n' {
128 text = text[1:]
129 }
130 return text, unordered, true
131 }
132 }
133 return "", false, false
134 }
135
136
137
138
139 func isTest(name, prefix string) bool {
140 if !strings.HasPrefix(name, prefix) {
141 return false
142 }
143 if len(name) == len(prefix) {
144 return true
145 }
146 rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
147 return !unicode.IsLower(rune)
148 }
149
150
151
152 func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
153 body := f.Body
154
155 if !strings.HasSuffix(file.Name.Name, "_test") {
156
157
158 return nil
159 }
160
161
162 topDecls := make(map[*ast.Object]ast.Decl)
163 typMethods := make(map[string][]ast.Decl)
164
165 for _, decl := range file.Decls {
166 switch d := decl.(type) {
167 case *ast.FuncDecl:
168 if d.Recv == nil {
169 topDecls[d.Name.Obj] = d
170 } else {
171 if len(d.Recv.List) == 1 {
172 t := d.Recv.List[0].Type
173 tname, _ := baseTypeName(t)
174 typMethods[tname] = append(typMethods[tname], d)
175 }
176 }
177 case *ast.GenDecl:
178 for _, spec := range d.Specs {
179 switch s := spec.(type) {
180 case *ast.TypeSpec:
181 topDecls[s.Name.Obj] = d
182 case *ast.ValueSpec:
183 for _, name := range s.Names {
184 topDecls[name.Obj] = d
185 }
186 }
187 }
188 }
189 }
190
191
192 unresolved := make(map[string]bool)
193 var depDecls []ast.Decl
194 hasDepDecls := make(map[ast.Decl]bool)
195
196 var inspectFunc func(ast.Node) bool
197 inspectFunc = func(n ast.Node) bool {
198 switch e := n.(type) {
199 case *ast.Ident:
200 if e.Obj == nil && e.Name != "_" {
201 unresolved[e.Name] = true
202 } else if d := topDecls[e.Obj]; d != nil {
203 if !hasDepDecls[d] {
204 hasDepDecls[d] = true
205 depDecls = append(depDecls, d)
206 }
207 }
208 return true
209 case *ast.SelectorExpr:
210
211
212
213 ast.Inspect(e.X, inspectFunc)
214 return false
215 case *ast.KeyValueExpr:
216
217
218
219 ast.Inspect(e.Value, inspectFunc)
220 return false
221 }
222 return true
223 }
224 ast.Inspect(body, inspectFunc)
225 for i := 0; i < len(depDecls); i++ {
226 switch d := depDecls[i].(type) {
227 case *ast.FuncDecl:
228
229 if d.Type.Params != nil {
230 for _, p := range d.Type.Params.List {
231 ast.Inspect(p.Type, inspectFunc)
232 }
233 }
234 if d.Type.Results != nil {
235 for _, r := range d.Type.Results.List {
236 ast.Inspect(r.Type, inspectFunc)
237 }
238 }
239
240 ast.Inspect(d.Body, inspectFunc)
241 case *ast.GenDecl:
242 for _, spec := range d.Specs {
243 switch s := spec.(type) {
244 case *ast.TypeSpec:
245 ast.Inspect(s.Type, inspectFunc)
246
247 depDecls = append(depDecls, typMethods[s.Name.Name]...)
248 case *ast.ValueSpec:
249 if s.Type != nil {
250 ast.Inspect(s.Type, inspectFunc)
251 }
252 for _, val := range s.Values {
253 ast.Inspect(val, inspectFunc)
254 }
255 }
256 }
257 }
258 }
259
260
261 for n := range unresolved {
262 if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] {
263 delete(unresolved, n)
264 }
265 }
266
267
268
269
270 namedImports := make(map[string]string)
271 var blankImports []ast.Spec
272 for _, s := range file.Imports {
273 p, err := strconv.Unquote(s.Path.Value)
274 if err != nil {
275 continue
276 }
277 if p == "syscall/js" {
278
279
280 return nil
281 }
282 n := path.Base(p)
283 if s.Name != nil {
284 n = s.Name.Name
285 switch n {
286 case "_":
287 blankImports = append(blankImports, s)
288 continue
289 case ".":
290
291 return nil
292 }
293 }
294 if unresolved[n] {
295 namedImports[n] = p
296 delete(unresolved, n)
297 }
298 }
299
300
301
302 if len(unresolved) > 0 {
303 return nil
304 }
305
306
307 var comments []*ast.CommentGroup
308 for _, s := range blankImports {
309 if c := s.(*ast.ImportSpec).Doc; c != nil {
310 comments = append(comments, c)
311 }
312 }
313
314
315 for _, c := range file.Comments {
316 if body.Pos() <= c.Pos() && c.End() <= body.End() {
317 comments = append(comments, c)
318 }
319 }
320
321
322
323 body, comments = stripOutputComment(body, comments)
324
325
326 for _, d := range depDecls {
327 switch d := d.(type) {
328 case *ast.GenDecl:
329 if d.Doc != nil {
330 comments = append(comments, d.Doc)
331 }
332 case *ast.FuncDecl:
333 if d.Doc != nil {
334 comments = append(comments, d.Doc)
335 }
336 }
337 }
338
339
340 importDecl := &ast.GenDecl{
341 Tok: token.IMPORT,
342 Lparen: 1,
343 Rparen: 1,
344 }
345 for n, p := range namedImports {
346 s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}}
347 if path.Base(p) != n {
348 s.Name = ast.NewIdent(n)
349 }
350 importDecl.Specs = append(importDecl.Specs, s)
351 }
352 importDecl.Specs = append(importDecl.Specs, blankImports...)
353
354
355 funcDecl := &ast.FuncDecl{
356 Name: ast.NewIdent("main"),
357 Type: f.Type,
358 Body: body,
359 }
360
361 decls := make([]ast.Decl, 0, 2+len(depDecls))
362 decls = append(decls, importDecl)
363 decls = append(decls, depDecls...)
364 decls = append(decls, funcDecl)
365
366 sort.Slice(decls, func(i, j int) bool {
367 return decls[i].Pos() < decls[j].Pos()
368 })
369
370 sort.Slice(comments, func(i, j int) bool {
371 return comments[i].Pos() < comments[j].Pos()
372 })
373
374
375 return &ast.File{
376 Name: ast.NewIdent("main"),
377 Decls: decls,
378 Comments: comments,
379 }
380 }
381
382
383
384 func playExampleFile(file *ast.File) *ast.File {
385
386 comments := file.Comments
387 if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") {
388 comments = comments[1:]
389 }
390
391
392 var decls []ast.Decl
393 for _, d := range file.Decls {
394 if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") {
395
396 newF := *f
397 newF.Name = ast.NewIdent("main")
398 newF.Body, comments = stripOutputComment(f.Body, comments)
399 d = &newF
400 }
401 decls = append(decls, d)
402 }
403
404
405 f := *file
406 f.Name = ast.NewIdent("main")
407 f.Decls = decls
408 f.Comments = comments
409 return &f
410 }
411
412
413
414 func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) {
415
416 i, last := lastComment(body, comments)
417 if last == nil || !outputPrefix.MatchString(last.Text()) {
418 return body, comments
419 }
420
421
422 newBody := &ast.BlockStmt{
423 Lbrace: body.Lbrace,
424 List: body.List,
425 Rbrace: last.Pos(),
426 }
427 newComments := make([]*ast.CommentGroup, len(comments)-1)
428 copy(newComments, comments[:i])
429 copy(newComments[i:], comments[i+1:])
430 return newBody, newComments
431 }
432
433
434 func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) {
435 if b == nil {
436 return
437 }
438 pos, end := b.Pos(), b.End()
439 for j, cg := range c {
440 if cg.Pos() < pos {
441 continue
442 }
443 if cg.End() > end {
444 break
445 }
446 i, last = j, cg
447 }
448 return
449 }
450
451
452
453
454
455
456
457
458
459
460
461
462
463 func classifyExamples(p *Package, examples []*Example) {
464 if len(examples) == 0 {
465 return
466 }
467
468
469 ids := make(map[string]*[]*Example)
470 ids[""] = &p.Examples
471 for _, f := range p.Funcs {
472 if !token.IsExported(f.Name) {
473 continue
474 }
475 ids[f.Name] = &f.Examples
476 }
477 for _, t := range p.Types {
478 if !token.IsExported(t.Name) {
479 continue
480 }
481 ids[t.Name] = &t.Examples
482 for _, f := range t.Funcs {
483 if !token.IsExported(f.Name) {
484 continue
485 }
486 ids[f.Name] = &f.Examples
487 }
488 for _, m := range t.Methods {
489 if !token.IsExported(m.Name) {
490 continue
491 }
492 ids[strings.TrimPrefix(m.Recv, "*")+"_"+m.Name] = &m.Examples
493 }
494 }
495
496
497 for _, ex := range examples {
498
499
500
501
502
503
504 for i := len(ex.Name); i >= 0; i = strings.LastIndexByte(ex.Name[:i], '_') {
505 prefix, suffix, ok := splitExampleName(ex.Name, i)
506 if !ok {
507 continue
508 }
509 exs, ok := ids[prefix]
510 if !ok {
511 continue
512 }
513 ex.Suffix = suffix
514 *exs = append(*exs, ex)
515 break
516 }
517 }
518
519
520 for _, exs := range ids {
521 sort.Slice((*exs), func(i, j int) bool {
522 return (*exs)[i].Suffix < (*exs)[j].Suffix
523 })
524 }
525 }
526
527
528
529
530
531
532
533 func splitExampleName(s string, i int) (prefix, suffix string, ok bool) {
534 if i == len(s) {
535 return s, "", true
536 }
537 if i == len(s)-1 {
538 return "", "", false
539 }
540 prefix, suffix = s[:i], s[i+1:]
541 return prefix, suffix, isExampleSuffix(suffix)
542 }
543
544 func isExampleSuffix(s string) bool {
545 r, size := utf8.DecodeRuneInString(s)
546 return size > 0 && unicode.IsLower(r)
547 }
548
View as plain text