Black Lives Matter. Support the Equal Justice Initiative.

Source file src/go/doc/example.go

Documentation: go/doc

     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 test source file.
    22  type Example struct {
    23  	Name        string // name of the item being exemplified (including optional suffix)
    24  	Suffix      string // example suffix, without leading '_' (only populated by NewFromFiles)
    25  	Doc         string // example function doc string
    26  	Code        ast.Node
    27  	Play        *ast.File // a whole program version of the example
    28  	Comments    []*ast.CommentGroup
    29  	Output      string // expected output
    30  	Unordered   bool
    31  	EmptyOutput bool // expect empty output
    32  	Order       int  // original source code order
    33  }
    34  
    35  // Examples returns the examples found in testFiles, sorted by Name field.
    36  // The Order fields record the order in which the examples were encountered.
    37  // The Suffix field is not populated when Examples is called directly, it is
    38  // only populated by NewFromFiles for examples it finds in _test.go files.
    39  //
    40  // Playable Examples must be in a package whose name ends in "_test".
    41  // An Example is "playable" (the Play field is non-nil) in either of these
    42  // circumstances:
    43  //   - The example function is self-contained: the function references only
    44  //     identifiers from other packages (or predeclared identifiers, such as
    45  //     "int") and the test file does not include a dot import.
    46  //   - The entire test file is the example: the file contains exactly one
    47  //     example function, zero test or benchmark functions, and at least one
    48  //     top-level function, type, variable, or constant declaration other
    49  //     than the example function.
    50  func Examples(testFiles ...*ast.File) []*Example {
    51  	var list []*Example
    52  	for _, file := range testFiles {
    53  		hasTests := false // file contains tests or benchmarks
    54  		numDecl := 0      // number of non-import declarations in the file
    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 // function has params; not a valid example
    76  			}
    77  			if f.Body == nil { // ast.File.Body nil dereference (see issue 28044)
    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  			// If this file only has one example function, some
    99  			// other top-level declarations, and no tests or
   100  			// benchmarks, use the whole file as the example.
   101  			flist[0].Code = file
   102  			flist[0].Play = playExampleFile(file)
   103  		}
   104  		list = append(list, flist...)
   105  	}
   106  	// sort by name
   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  // Extracts the expected output and whether there was a valid output comment
   116  func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, unordered, ok bool) {
   117  	if _, last := lastComment(b, comments); last != nil {
   118  		// test that it begins with the correct prefix
   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  			// Strip zero or more spaces followed by \n or a single space.
   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 // no suitable comment found
   134  }
   135  
   136  // isTest tells whether name looks like a test, example, or benchmark.
   137  // It is a Test (say) if there is a character after Test that is not a
   138  // lower-case letter. (We don't want Testiness.)
   139  func isTest(name, prefix string) bool {
   140  	if !strings.HasPrefix(name, prefix) {
   141  		return false
   142  	}
   143  	if len(name) == len(prefix) { // "Test" is ok
   144  		return true
   145  	}
   146  	rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
   147  	return !unicode.IsLower(rune)
   148  }
   149  
   150  // playExample synthesizes a new *ast.File based on the provided
   151  // file with the provided function body as the body of main.
   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  		// We don't support examples that are part of the
   157  		// greater package (yet).
   158  		return nil
   159  	}
   160  
   161  	// Collect top-level declarations in the file.
   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  	// Find unresolved identifiers and uses of top-level declarations.
   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  			// For selector expressions, only inspect the left hand side.
   211  			// (For an expression like fmt.Println, only add "fmt" to the
   212  			// set of unresolved names, not "Println".)
   213  			ast.Inspect(e.X, inspectFunc)
   214  			return false
   215  		case *ast.KeyValueExpr:
   216  			// For key value expressions, only inspect the value
   217  			// as the key should be resolved by the type of the
   218  			// composite literal.
   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  			// Inspect types of parameters and results. See #28492.
   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  			// Functions might not have a body. See #42706.
   241  			if d.Body != nil {
   242  				ast.Inspect(d.Body, inspectFunc)
   243  			}
   244  		case *ast.GenDecl:
   245  			for _, spec := range d.Specs {
   246  				switch s := spec.(type) {
   247  				case *ast.TypeSpec:
   248  					ast.Inspect(s.Type, inspectFunc)
   249  
   250  					depDecls = append(depDecls, typMethods[s.Name.Name]...)
   251  				case *ast.ValueSpec:
   252  					if s.Type != nil {
   253  						ast.Inspect(s.Type, inspectFunc)
   254  					}
   255  					for _, val := range s.Values {
   256  						ast.Inspect(val, inspectFunc)
   257  					}
   258  				}
   259  			}
   260  		}
   261  	}
   262  
   263  	// Remove predeclared identifiers from unresolved list.
   264  	for n := range unresolved {
   265  		if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] {
   266  			delete(unresolved, n)
   267  		}
   268  	}
   269  
   270  	// Use unresolved identifiers to determine the imports used by this
   271  	// example. The heuristic assumes package names match base import
   272  	// paths for imports w/o renames (should be good enough most of the time).
   273  	namedImports := make(map[string]string) // [name]path
   274  	var blankImports []ast.Spec             // _ imports
   275  	for _, s := range file.Imports {
   276  		p, err := strconv.Unquote(s.Path.Value)
   277  		if err != nil {
   278  			continue
   279  		}
   280  		if p == "syscall/js" {
   281  			// We don't support examples that import syscall/js,
   282  			// because the package syscall/js is not available in the playground.
   283  			return nil
   284  		}
   285  		n := path.Base(p)
   286  		if s.Name != nil {
   287  			n = s.Name.Name
   288  			switch n {
   289  			case "_":
   290  				blankImports = append(blankImports, s)
   291  				continue
   292  			case ".":
   293  				// We can't resolve dot imports (yet).
   294  				return nil
   295  			}
   296  		}
   297  		if unresolved[n] {
   298  			namedImports[n] = p
   299  			delete(unresolved, n)
   300  		}
   301  	}
   302  
   303  	// If there are other unresolved identifiers, give up because this
   304  	// synthesized file is not going to build.
   305  	if len(unresolved) > 0 {
   306  		return nil
   307  	}
   308  
   309  	// Include documentation belonging to blank imports.
   310  	var comments []*ast.CommentGroup
   311  	for _, s := range blankImports {
   312  		if c := s.(*ast.ImportSpec).Doc; c != nil {
   313  			comments = append(comments, c)
   314  		}
   315  	}
   316  
   317  	// Include comments that are inside the function body.
   318  	for _, c := range file.Comments {
   319  		if body.Pos() <= c.Pos() && c.End() <= body.End() {
   320  			comments = append(comments, c)
   321  		}
   322  	}
   323  
   324  	// Strip the "Output:" or "Unordered output:" comment and adjust body
   325  	// end position.
   326  	body, comments = stripOutputComment(body, comments)
   327  
   328  	// Include documentation belonging to dependent declarations.
   329  	for _, d := range depDecls {
   330  		switch d := d.(type) {
   331  		case *ast.GenDecl:
   332  			if d.Doc != nil {
   333  				comments = append(comments, d.Doc)
   334  			}
   335  		case *ast.FuncDecl:
   336  			if d.Doc != nil {
   337  				comments = append(comments, d.Doc)
   338  			}
   339  		}
   340  	}
   341  
   342  	// Synthesize import declaration.
   343  	importDecl := &ast.GenDecl{
   344  		Tok:    token.IMPORT,
   345  		Lparen: 1, // Need non-zero Lparen and Rparen so that printer
   346  		Rparen: 1, // treats this as a factored import.
   347  	}
   348  	for n, p := range namedImports {
   349  		s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}}
   350  		if path.Base(p) != n {
   351  			s.Name = ast.NewIdent(n)
   352  		}
   353  		importDecl.Specs = append(importDecl.Specs, s)
   354  	}
   355  	importDecl.Specs = append(importDecl.Specs, blankImports...)
   356  
   357  	// Synthesize main function.
   358  	funcDecl := &ast.FuncDecl{
   359  		Name: ast.NewIdent("main"),
   360  		Type: f.Type,
   361  		Body: body,
   362  	}
   363  
   364  	decls := make([]ast.Decl, 0, 2+len(depDecls))
   365  	decls = append(decls, importDecl)
   366  	decls = append(decls, depDecls...)
   367  	decls = append(decls, funcDecl)
   368  
   369  	sort.Slice(decls, func(i, j int) bool {
   370  		return decls[i].Pos() < decls[j].Pos()
   371  	})
   372  
   373  	sort.Slice(comments, func(i, j int) bool {
   374  		return comments[i].Pos() < comments[j].Pos()
   375  	})
   376  
   377  	// Synthesize file.
   378  	return &ast.File{
   379  		Name:     ast.NewIdent("main"),
   380  		Decls:    decls,
   381  		Comments: comments,
   382  	}
   383  }
   384  
   385  // playExampleFile takes a whole file example and synthesizes a new *ast.File
   386  // such that the example is function main in package main.
   387  func playExampleFile(file *ast.File) *ast.File {
   388  	// Strip copyright comment if present.
   389  	comments := file.Comments
   390  	if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") {
   391  		comments = comments[1:]
   392  	}
   393  
   394  	// Copy declaration slice, rewriting the ExampleX function to main.
   395  	var decls []ast.Decl
   396  	for _, d := range file.Decls {
   397  		if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") {
   398  			// Copy the FuncDecl, as it may be used elsewhere.
   399  			newF := *f
   400  			newF.Name = ast.NewIdent("main")
   401  			newF.Body, comments = stripOutputComment(f.Body, comments)
   402  			d = &newF
   403  		}
   404  		decls = append(decls, d)
   405  	}
   406  
   407  	// Copy the File, as it may be used elsewhere.
   408  	f := *file
   409  	f.Name = ast.NewIdent("main")
   410  	f.Decls = decls
   411  	f.Comments = comments
   412  	return &f
   413  }
   414  
   415  // stripOutputComment finds and removes the "Output:" or "Unordered output:"
   416  // comment from body and comments, and adjusts the body block's end position.
   417  func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) {
   418  	// Do nothing if there is no "Output:" or "Unordered output:" comment.
   419  	i, last := lastComment(body, comments)
   420  	if last == nil || !outputPrefix.MatchString(last.Text()) {
   421  		return body, comments
   422  	}
   423  
   424  	// Copy body and comments, as the originals may be used elsewhere.
   425  	newBody := &ast.BlockStmt{
   426  		Lbrace: body.Lbrace,
   427  		List:   body.List,
   428  		Rbrace: last.Pos(),
   429  	}
   430  	newComments := make([]*ast.CommentGroup, len(comments)-1)
   431  	copy(newComments, comments[:i])
   432  	copy(newComments[i:], comments[i+1:])
   433  	return newBody, newComments
   434  }
   435  
   436  // lastComment returns the last comment inside the provided block.
   437  func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) {
   438  	if b == nil {
   439  		return
   440  	}
   441  	pos, end := b.Pos(), b.End()
   442  	for j, cg := range c {
   443  		if cg.Pos() < pos {
   444  			continue
   445  		}
   446  		if cg.End() > end {
   447  			break
   448  		}
   449  		i, last = j, cg
   450  	}
   451  	return
   452  }
   453  
   454  // classifyExamples classifies examples and assigns them to the Examples field
   455  // of the relevant Func, Type, or Package that the example is associated with.
   456  //
   457  // The classification process is ambiguous in some cases:
   458  //
   459  // 	- ExampleFoo_Bar matches a type named Foo_Bar
   460  // 	  or a method named Foo.Bar.
   461  // 	- ExampleFoo_bar matches a type named Foo_bar
   462  // 	  or Foo (with a "bar" suffix).
   463  //
   464  // Examples with malformed names are not associated with anything.
   465  //
   466  func classifyExamples(p *Package, examples []*Example) {
   467  	if len(examples) == 0 {
   468  		return
   469  	}
   470  
   471  	// Mapping of names for funcs, types, and methods to the example listing.
   472  	ids := make(map[string]*[]*Example)
   473  	ids[""] = &p.Examples // package-level examples have an empty name
   474  	for _, f := range p.Funcs {
   475  		if !token.IsExported(f.Name) {
   476  			continue
   477  		}
   478  		ids[f.Name] = &f.Examples
   479  	}
   480  	for _, t := range p.Types {
   481  		if !token.IsExported(t.Name) {
   482  			continue
   483  		}
   484  		ids[t.Name] = &t.Examples
   485  		for _, f := range t.Funcs {
   486  			if !token.IsExported(f.Name) {
   487  				continue
   488  			}
   489  			ids[f.Name] = &f.Examples
   490  		}
   491  		for _, m := range t.Methods {
   492  			if !token.IsExported(m.Name) {
   493  				continue
   494  			}
   495  			ids[strings.TrimPrefix(m.Recv, "*")+"_"+m.Name] = &m.Examples
   496  		}
   497  	}
   498  
   499  	// Group each example with the associated func, type, or method.
   500  	for _, ex := range examples {
   501  		// Consider all possible split points for the suffix
   502  		// by starting at the end of string (no suffix case),
   503  		// then trying all positions that contain a '_' character.
   504  		//
   505  		// An association is made on the first successful match.
   506  		// Examples with malformed names that match nothing are skipped.
   507  		for i := len(ex.Name); i >= 0; i = strings.LastIndexByte(ex.Name[:i], '_') {
   508  			prefix, suffix, ok := splitExampleName(ex.Name, i)
   509  			if !ok {
   510  				continue
   511  			}
   512  			exs, ok := ids[prefix]
   513  			if !ok {
   514  				continue
   515  			}
   516  			ex.Suffix = suffix
   517  			*exs = append(*exs, ex)
   518  			break
   519  		}
   520  	}
   521  
   522  	// Sort list of example according to the user-specified suffix name.
   523  	for _, exs := range ids {
   524  		sort.Slice((*exs), func(i, j int) bool {
   525  			return (*exs)[i].Suffix < (*exs)[j].Suffix
   526  		})
   527  	}
   528  }
   529  
   530  // splitExampleName attempts to split example name s at index i,
   531  // and reports if that produces a valid split. The suffix may be
   532  // absent. Otherwise, it must start with a lower-case letter and
   533  // be preceded by '_'.
   534  //
   535  // One of i == len(s) or s[i] == '_' must be true.
   536  func splitExampleName(s string, i int) (prefix, suffix string, ok bool) {
   537  	if i == len(s) {
   538  		return s, "", true
   539  	}
   540  	if i == len(s)-1 {
   541  		return "", "", false
   542  	}
   543  	prefix, suffix = s[:i], s[i+1:]
   544  	return prefix, suffix, isExampleSuffix(suffix)
   545  }
   546  
   547  func isExampleSuffix(s string) bool {
   548  	r, size := utf8.DecodeRuneInString(s)
   549  	return size > 0 && unicode.IsLower(r)
   550  }
   551  

View as plain text