...

Source file src/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		"internal/lazyregexp"
    13		"path"
    14		"sort"
    15		"strconv"
    16		"strings"
    17		"unicode"
    18		"unicode/utf8"
    19	)
    20	
    21	// An Example represents an example function found in a source files.
    22	type Example struct {
    23		Name        string // name of the item being exemplified
    24		Doc         string // example function doc string
    25		Code        ast.Node
    26		Play        *ast.File // a whole program version of the example
    27		Comments    []*ast.CommentGroup
    28		Output      string // expected output
    29		Unordered   bool
    30		EmptyOutput bool // expect empty output
    31		Order       int  // original source code order
    32	}
    33	
    34	// Examples returns the examples found in the files, sorted by Name field.
    35	// The Order fields record the order in which the examples were encountered.
    36	//
    37	// Playable Examples must be in a package whose name ends in "_test".
    38	// An Example is "playable" (the Play field is non-nil) in either of these
    39	// circumstances:
    40	//   - The example function is self-contained: the function references only
    41	//     identifiers from other packages (or predeclared identifiers, such as
    42	//     "int") and the test file does not include a dot import.
    43	//   - The entire test file is the example: the file contains exactly one
    44	//     example function, zero test or benchmark functions, and at least one
    45	//     top-level function, type, variable, or constant declaration other
    46	//     than the example function.
    47	func Examples(files ...*ast.File) []*Example {
    48		var list []*Example
    49		for _, file := range files {
    50			hasTests := false // file contains tests or benchmarks
    51			numDecl := 0      // number of non-import declarations in the file
    52			var flist []*Example
    53			for _, decl := range file.Decls {
    54				if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT {
    55					numDecl++
    56					continue
    57				}
    58				f, ok := decl.(*ast.FuncDecl)
    59				if !ok || f.Recv != nil {
    60					continue
    61				}
    62				numDecl++
    63				name := f.Name.Name
    64				if isTest(name, "Test") || isTest(name, "Benchmark") {
    65					hasTests = true
    66					continue
    67				}
    68				if !isTest(name, "Example") {
    69					continue
    70				}
    71				if f.Body == nil { // ast.File.Body nil dereference (see issue 28044)
    72					continue
    73				}
    74				var doc string
    75				if f.Doc != nil {
    76					doc = f.Doc.Text()
    77				}
    78				output, unordered, hasOutput := exampleOutput(f.Body, file.Comments)
    79				flist = append(flist, &Example{
    80					Name:        name[len("Example"):],
    81					Doc:         doc,
    82					Code:        f.Body,
    83					Play:        playExample(file, f),
    84					Comments:    file.Comments,
    85					Output:      output,
    86					Unordered:   unordered,
    87					EmptyOutput: output == "" && hasOutput,
    88					Order:       len(flist),
    89				})
    90			}
    91			if !hasTests && numDecl > 1 && len(flist) == 1 {
    92				// If this file only has one example function, some
    93				// other top-level declarations, and no tests or
    94				// benchmarks, use the whole file as the example.
    95				flist[0].Code = file
    96				flist[0].Play = playExampleFile(file)
    97			}
    98			list = append(list, flist...)
    99		}
   100		// sort by name
   101		sort.Slice(list, func(i, j int) bool {
   102			return list[i].Name < list[j].Name
   103		})
   104		return list
   105	}
   106	
   107	var outputPrefix = lazyregexp.New(`(?i)^[[:space:]]*(unordered )?output:`)
   108	
   109	// Extracts the expected output and whether there was a valid output comment
   110	func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, unordered, ok bool) {
   111		if _, last := lastComment(b, comments); last != nil {
   112			// test that it begins with the correct prefix
   113			text := last.Text()
   114			if loc := outputPrefix.FindStringSubmatchIndex(text); loc != nil {
   115				if loc[2] != -1 {
   116					unordered = true
   117				}
   118				text = text[loc[1]:]
   119				// Strip zero or more spaces followed by \n or a single space.
   120				text = strings.TrimLeft(text, " ")
   121				if len(text) > 0 && text[0] == '\n' {
   122					text = text[1:]
   123				}
   124				return text, unordered, true
   125			}
   126		}
   127		return "", false, false // no suitable comment found
   128	}
   129	
   130	// isTest tells whether name looks like a test, example, or benchmark.
   131	// It is a Test (say) if there is a character after Test that is not a
   132	// lower-case letter. (We don't want Testiness.)
   133	func isTest(name, prefix string) bool {
   134		if !strings.HasPrefix(name, prefix) {
   135			return false
   136		}
   137		if len(name) == len(prefix) { // "Test" is ok
   138			return true
   139		}
   140		rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
   141		return !unicode.IsLower(rune)
   142	}
   143	
   144	// playExample synthesizes a new *ast.File based on the provided
   145	// file with the provided function body as the body of main.
   146	func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
   147		body := f.Body
   148	
   149		if !strings.HasSuffix(file.Name.Name, "_test") {
   150			// We don't support examples that are part of the
   151			// greater package (yet).
   152			return nil
   153		}
   154	
   155		// Collect top-level declarations in the file.
   156		topDecls := make(map[*ast.Object]ast.Decl)
   157		typMethods := make(map[string][]ast.Decl)
   158	
   159		for _, decl := range file.Decls {
   160			switch d := decl.(type) {
   161			case *ast.FuncDecl:
   162				if d.Recv == nil {
   163					topDecls[d.Name.Obj] = d
   164				} else {
   165					if len(d.Recv.List) == 1 {
   166						t := d.Recv.List[0].Type
   167						tname, _ := baseTypeName(t)
   168						typMethods[tname] = append(typMethods[tname], d)
   169					}
   170				}
   171			case *ast.GenDecl:
   172				for _, spec := range d.Specs {
   173					switch s := spec.(type) {
   174					case *ast.TypeSpec:
   175						topDecls[s.Name.Obj] = d
   176					case *ast.ValueSpec:
   177						for _, name := range s.Names {
   178							topDecls[name.Obj] = d
   179						}
   180					}
   181				}
   182			}
   183		}
   184	
   185		// Find unresolved identifiers and uses of top-level declarations.
   186		unresolved := make(map[string]bool)
   187		var depDecls []ast.Decl
   188		hasDepDecls := make(map[ast.Decl]bool)
   189	
   190		var inspectFunc func(ast.Node) bool
   191		inspectFunc = func(n ast.Node) bool {
   192			switch e := n.(type) {
   193			case *ast.Ident:
   194				if e.Obj == nil && e.Name != "_" {
   195					unresolved[e.Name] = true
   196				} else if d := topDecls[e.Obj]; d != nil {
   197					if !hasDepDecls[d] {
   198						hasDepDecls[d] = true
   199						depDecls = append(depDecls, d)
   200					}
   201				}
   202				return true
   203			case *ast.SelectorExpr:
   204				// For selector expressions, only inspect the left hand side.
   205				// (For an expression like fmt.Println, only add "fmt" to the
   206				// set of unresolved names, not "Println".)
   207				ast.Inspect(e.X, inspectFunc)
   208				return false
   209			case *ast.KeyValueExpr:
   210				// For key value expressions, only inspect the value
   211				// as the key should be resolved by the type of the
   212				// composite literal.
   213				ast.Inspect(e.Value, inspectFunc)
   214				return false
   215			}
   216			return true
   217		}
   218		ast.Inspect(body, inspectFunc)
   219		for i := 0; i < len(depDecls); i++ {
   220			switch d := depDecls[i].(type) {
   221			case *ast.FuncDecl:
   222				// Inspect types of parameters and results. See #28492.
   223				if d.Type.Params != nil {
   224					for _, p := range d.Type.Params.List {
   225						ast.Inspect(p.Type, inspectFunc)
   226					}
   227				}
   228				if d.Type.Results != nil {
   229					for _, r := range d.Type.Results.List {
   230						ast.Inspect(r.Type, inspectFunc)
   231					}
   232				}
   233	
   234				ast.Inspect(d.Body, inspectFunc)
   235			case *ast.GenDecl:
   236				for _, spec := range d.Specs {
   237					switch s := spec.(type) {
   238					case *ast.TypeSpec:
   239						ast.Inspect(s.Type, inspectFunc)
   240	
   241						depDecls = append(depDecls, typMethods[s.Name.Name]...)
   242					case *ast.ValueSpec:
   243						if s.Type != nil {
   244							ast.Inspect(s.Type, inspectFunc)
   245						}
   246						for _, val := range s.Values {
   247							ast.Inspect(val, inspectFunc)
   248						}
   249					}
   250				}
   251			}
   252		}
   253	
   254		// Remove predeclared identifiers from unresolved list.
   255		for n := range unresolved {
   256			if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] {
   257				delete(unresolved, n)
   258			}
   259		}
   260	
   261		// Use unresolved identifiers to determine the imports used by this
   262		// example. The heuristic assumes package names match base import
   263		// paths for imports w/o renames (should be good enough most of the time).
   264		namedImports := make(map[string]string) // [name]path
   265		var blankImports []ast.Spec             // _ imports
   266		for _, s := range file.Imports {
   267			p, err := strconv.Unquote(s.Path.Value)
   268			if err != nil {
   269				continue
   270			}
   271			if p == "syscall/js" {
   272				// We don't support examples that import syscall/js,
   273				// because the package syscall/js is not available in the playground.
   274				return nil
   275			}
   276			n := path.Base(p)
   277			if s.Name != nil {
   278				n = s.Name.Name
   279				switch n {
   280				case "_":
   281					blankImports = append(blankImports, s)
   282					continue
   283				case ".":
   284					// We can't resolve dot imports (yet).
   285					return nil
   286				}
   287			}
   288			if unresolved[n] {
   289				namedImports[n] = p
   290				delete(unresolved, n)
   291			}
   292		}
   293	
   294		// If there are other unresolved identifiers, give up because this
   295		// synthesized file is not going to build.
   296		if len(unresolved) > 0 {
   297			return nil
   298		}
   299	
   300		// Include documentation belonging to blank imports.
   301		var comments []*ast.CommentGroup
   302		for _, s := range blankImports {
   303			if c := s.(*ast.ImportSpec).Doc; c != nil {
   304				comments = append(comments, c)
   305			}
   306		}
   307	
   308		// Include comments that are inside the function body.
   309		for _, c := range file.Comments {
   310			if body.Pos() <= c.Pos() && c.End() <= body.End() {
   311				comments = append(comments, c)
   312			}
   313		}
   314	
   315		// Strip the "Output:" or "Unordered output:" comment and adjust body
   316		// end position.
   317		body, comments = stripOutputComment(body, comments)
   318	
   319		// Include documentation belonging to dependent declarations.
   320		for _, d := range depDecls {
   321			switch d := d.(type) {
   322			case *ast.GenDecl:
   323				if d.Doc != nil {
   324					comments = append(comments, d.Doc)
   325				}
   326			case *ast.FuncDecl:
   327				if d.Doc != nil {
   328					comments = append(comments, d.Doc)
   329				}
   330			}
   331		}
   332	
   333		// Synthesize import declaration.
   334		importDecl := &ast.GenDecl{
   335			Tok:    token.IMPORT,
   336			Lparen: 1, // Need non-zero Lparen and Rparen so that printer
   337			Rparen: 1, // treats this as a factored import.
   338		}
   339		for n, p := range namedImports {
   340			s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}}
   341			if path.Base(p) != n {
   342				s.Name = ast.NewIdent(n)
   343			}
   344			importDecl.Specs = append(importDecl.Specs, s)
   345		}
   346		importDecl.Specs = append(importDecl.Specs, blankImports...)
   347	
   348		// Synthesize main function.
   349		funcDecl := &ast.FuncDecl{
   350			Name: ast.NewIdent("main"),
   351			Type: f.Type,
   352			Body: body,
   353		}
   354	
   355		decls := make([]ast.Decl, 0, 2+len(depDecls))
   356		decls = append(decls, importDecl)
   357		decls = append(decls, depDecls...)
   358		decls = append(decls, funcDecl)
   359	
   360		sort.Slice(decls, func(i, j int) bool {
   361			return decls[i].Pos() < decls[j].Pos()
   362		})
   363	
   364		sort.Slice(comments, func(i, j int) bool {
   365			return comments[i].Pos() < comments[j].Pos()
   366		})
   367	
   368		// Synthesize file.
   369		return &ast.File{
   370			Name:     ast.NewIdent("main"),
   371			Decls:    decls,
   372			Comments: comments,
   373		}
   374	}
   375	
   376	// playExampleFile takes a whole file example and synthesizes a new *ast.File
   377	// such that the example is function main in package main.
   378	func playExampleFile(file *ast.File) *ast.File {
   379		// Strip copyright comment if present.
   380		comments := file.Comments
   381		if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") {
   382			comments = comments[1:]
   383		}
   384	
   385		// Copy declaration slice, rewriting the ExampleX function to main.
   386		var decls []ast.Decl
   387		for _, d := range file.Decls {
   388			if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") {
   389				// Copy the FuncDecl, as it may be used elsewhere.
   390				newF := *f
   391				newF.Name = ast.NewIdent("main")
   392				newF.Body, comments = stripOutputComment(f.Body, comments)
   393				d = &newF
   394			}
   395			decls = append(decls, d)
   396		}
   397	
   398		// Copy the File, as it may be used elsewhere.
   399		f := *file
   400		f.Name = ast.NewIdent("main")
   401		f.Decls = decls
   402		f.Comments = comments
   403		return &f
   404	}
   405	
   406	// stripOutputComment finds and removes the "Output:" or "Unordered output:"
   407	// comment from body and comments, and adjusts the body block's end position.
   408	func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) {
   409		// Do nothing if there is no "Output:" or "Unordered output:" comment.
   410		i, last := lastComment(body, comments)
   411		if last == nil || !outputPrefix.MatchString(last.Text()) {
   412			return body, comments
   413		}
   414	
   415		// Copy body and comments, as the originals may be used elsewhere.
   416		newBody := &ast.BlockStmt{
   417			Lbrace: body.Lbrace,
   418			List:   body.List,
   419			Rbrace: last.Pos(),
   420		}
   421		newComments := make([]*ast.CommentGroup, len(comments)-1)
   422		copy(newComments, comments[:i])
   423		copy(newComments[i:], comments[i+1:])
   424		return newBody, newComments
   425	}
   426	
   427	// lastComment returns the last comment inside the provided block.
   428	func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) {
   429		if b == nil {
   430			return
   431		}
   432		pos, end := b.Pos(), b.End()
   433		for j, cg := range c {
   434			if cg.Pos() < pos {
   435				continue
   436			}
   437			if cg.End() > end {
   438				break
   439			}
   440			i, last = j, cg
   441		}
   442		return
   443	}
   444	

View as plain text