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  	"cmp"
    11  	"go/ast"
    12  	"go/token"
    13  	"internal/lazyregexp"
    14  	"slices"
    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, fuzz test, or benchmark function, and at
    48  //     least one top-level function, type, variable, or constant declaration
    49  //     other 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, fuzz test, 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") || isTest(name, "Fuzz") {
    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 results := f.Type.Results; results != nil && len(results.List) != 0 {
    78  				continue // function has results; not a valid example
    79  			}
    80  			if f.Body == nil { // ast.File.Body nil dereference (see issue 28044)
    81  				continue
    82  			}
    83  			var doc string
    84  			if f.Doc != nil {
    85  				doc = f.Doc.Text()
    86  			}
    87  			output, unordered, hasOutput := exampleOutput(f.Body, file.Comments)
    88  			flist = append(flist, &Example{
    89  				Name:        name[len("Example"):],
    90  				Doc:         doc,
    91  				Code:        f.Body,
    92  				Play:        playExample(file, f),
    93  				Comments:    file.Comments,
    94  				Output:      output,
    95  				Unordered:   unordered,
    96  				EmptyOutput: output == "" && hasOutput,
    97  				Order:       len(flist),
    98  			})
    99  		}
   100  		if !hasTests && numDecl > 1 && len(flist) == 1 {
   101  			// If this file only has one example function, some
   102  			// other top-level declarations, and no tests or
   103  			// benchmarks, use the whole file as the example.
   104  			flist[0].Code = file
   105  			flist[0].Play = playExampleFile(file)
   106  		}
   107  		list = append(list, flist...)
   108  	}
   109  	// sort by name
   110  	slices.SortFunc(list, func(a, b *Example) int {
   111  		return cmp.Compare(a.Name, b.Name)
   112  	})
   113  	return list
   114  }
   115  
   116  var outputPrefix = lazyregexp.New(`(?i)^[[:space:]]*(unordered )?output:`)
   117  
   118  // Extracts the expected output and whether there was a valid output comment.
   119  func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, unordered, ok bool) {
   120  	if _, last := lastComment(b, comments); last != nil {
   121  		// test that it begins with the correct prefix
   122  		text := last.Text()
   123  		if loc := outputPrefix.FindStringSubmatchIndex(text); loc != nil {
   124  			if loc[2] != -1 {
   125  				unordered = true
   126  			}
   127  			text = text[loc[1]:]
   128  			// Strip zero or more spaces followed by \n or a single space.
   129  			text = strings.TrimLeft(text, " ")
   130  			if len(text) > 0 && text[0] == '\n' {
   131  				text = text[1:]
   132  			}
   133  			return text, unordered, true
   134  		}
   135  	}
   136  	return "", false, false // no suitable comment found
   137  }
   138  
   139  // isTest tells whether name looks like a test, example, fuzz test, or
   140  // benchmark. It is a Test (say) if there is a character after Test that is not
   141  // a lower-case letter. (We don't want Testiness.)
   142  func isTest(name, prefix string) bool {
   143  	if !strings.HasPrefix(name, prefix) {
   144  		return false
   145  	}
   146  	if len(name) == len(prefix) { // "Test" is ok
   147  		return true
   148  	}
   149  	rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
   150  	return !unicode.IsLower(rune)
   151  }
   152  
   153  // playExample synthesizes a new *ast.File based on the provided
   154  // file with the provided function body as the body of main.
   155  func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
   156  	body := f.Body
   157  
   158  	if !strings.HasSuffix(file.Name.Name, "_test") {
   159  		// We don't support examples that are part of the
   160  		// greater package (yet).
   161  		return nil
   162  	}
   163  
   164  	// Collect top-level declarations in the file.
   165  	topDecls := make(map[*ast.Object]ast.Decl)
   166  	typMethods := make(map[string][]ast.Decl)
   167  
   168  	for _, decl := range file.Decls {
   169  		switch d := decl.(type) {
   170  		case *ast.FuncDecl:
   171  			if d.Recv == nil {
   172  				topDecls[d.Name.Obj] = d
   173  			} else {
   174  				if len(d.Recv.List) == 1 {
   175  					t := d.Recv.List[0].Type
   176  					tname, _ := baseTypeName(t)
   177  					typMethods[tname] = append(typMethods[tname], d)
   178  				}
   179  			}
   180  		case *ast.GenDecl:
   181  			for _, spec := range d.Specs {
   182  				switch s := spec.(type) {
   183  				case *ast.TypeSpec:
   184  					topDecls[s.Name.Obj] = d
   185  				case *ast.ValueSpec:
   186  					for _, name := range s.Names {
   187  						topDecls[name.Obj] = d
   188  					}
   189  				}
   190  			}
   191  		}
   192  	}
   193  
   194  	// Find unresolved identifiers and uses of top-level declarations.
   195  	depDecls, unresolved := findDeclsAndUnresolved(body, topDecls, typMethods)
   196  
   197  	// Use unresolved identifiers to determine the imports used by this
   198  	// example. The heuristic assumes package names match base import
   199  	// paths for imports w/o renames (should be good enough most of the time).
   200  	var namedImports []ast.Spec
   201  	var blankImports []ast.Spec // _ imports
   202  
   203  	// To preserve the blank lines between groups of imports, find the
   204  	// start position of each group, and assign that position to all
   205  	// imports from that group.
   206  	groupStarts := findImportGroupStarts(file.Imports)
   207  	groupStart := func(s *ast.ImportSpec) token.Pos {
   208  		for i, start := range groupStarts {
   209  			if s.Path.ValuePos < start {
   210  				return groupStarts[i-1]
   211  			}
   212  		}
   213  		return groupStarts[len(groupStarts)-1]
   214  	}
   215  
   216  	for _, s := range file.Imports {
   217  		p, err := strconv.Unquote(s.Path.Value)
   218  		if err != nil {
   219  			continue
   220  		}
   221  		if p == "syscall/js" {
   222  			// We don't support examples that import syscall/js,
   223  			// because the package syscall/js is not available in the playground.
   224  			return nil
   225  		}
   226  		n := assumedPackageName(p)
   227  		if s.Name != nil {
   228  			n = s.Name.Name
   229  			switch n {
   230  			case "_":
   231  				blankImports = append(blankImports, s)
   232  				continue
   233  			case ".":
   234  				// We can't resolve dot imports (yet).
   235  				return nil
   236  			}
   237  		}
   238  		if unresolved[n] {
   239  			// Copy the spec and its path to avoid modifying the original.
   240  			spec := *s
   241  			path := *s.Path
   242  			spec.Path = &path
   243  			updateBasicLitPos(spec.Path, groupStart(&spec))
   244  			namedImports = append(namedImports, &spec)
   245  			delete(unresolved, n)
   246  		}
   247  	}
   248  
   249  	// Remove predeclared identifiers from unresolved list.
   250  	for n := range unresolved {
   251  		if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] {
   252  			delete(unresolved, n)
   253  		}
   254  	}
   255  
   256  	// If there are other unresolved identifiers, give up because this
   257  	// synthesized file is not going to build.
   258  	if len(unresolved) > 0 {
   259  		return nil
   260  	}
   261  
   262  	// Include documentation belonging to blank imports.
   263  	var comments []*ast.CommentGroup
   264  	for _, s := range blankImports {
   265  		if c := s.(*ast.ImportSpec).Doc; c != nil {
   266  			comments = append(comments, c)
   267  		}
   268  	}
   269  
   270  	// Include comments that are inside the function body.
   271  	for _, c := range file.Comments {
   272  		if body.Pos() <= c.Pos() && c.End() <= body.End() {
   273  			comments = append(comments, c)
   274  		}
   275  	}
   276  
   277  	// Strip the "Output:" or "Unordered output:" comment and adjust body
   278  	// end position.
   279  	body, comments = stripOutputComment(body, comments)
   280  
   281  	// Include documentation belonging to dependent declarations.
   282  	for _, d := range depDecls {
   283  		switch d := d.(type) {
   284  		case *ast.GenDecl:
   285  			if d.Doc != nil {
   286  				comments = append(comments, d.Doc)
   287  			}
   288  		case *ast.FuncDecl:
   289  			if d.Doc != nil {
   290  				comments = append(comments, d.Doc)
   291  			}
   292  		}
   293  	}
   294  
   295  	// Synthesize import declaration.
   296  	importDecl := &ast.GenDecl{
   297  		Tok:    token.IMPORT,
   298  		Lparen: 1, // Need non-zero Lparen and Rparen so that printer
   299  		Rparen: 1, // treats this as a factored import.
   300  	}
   301  	importDecl.Specs = append(namedImports, blankImports...)
   302  
   303  	// Synthesize main function.
   304  	funcDecl := &ast.FuncDecl{
   305  		Name: ast.NewIdent("main"),
   306  		Type: f.Type,
   307  		Body: body,
   308  	}
   309  
   310  	decls := make([]ast.Decl, 0, 2+len(depDecls))
   311  	decls = append(decls, importDecl)
   312  	decls = append(decls, depDecls...)
   313  	decls = append(decls, funcDecl)
   314  
   315  	slices.SortFunc(decls, func(a, b ast.Decl) int {
   316  		return cmp.Compare(a.Pos(), b.Pos())
   317  	})
   318  	slices.SortFunc(comments, func(a, b *ast.CommentGroup) int {
   319  		return cmp.Compare(a.Pos(), b.Pos())
   320  	})
   321  
   322  	// Synthesize file.
   323  	return &ast.File{
   324  		Name:     ast.NewIdent("main"),
   325  		Decls:    decls,
   326  		Comments: comments,
   327  	}
   328  }
   329  
   330  // findDeclsAndUnresolved returns all the top-level declarations mentioned in
   331  // the body, and a set of unresolved symbols (those that appear in the body but
   332  // have no declaration in the program).
   333  //
   334  // topDecls maps objects to the top-level declaration declaring them (not
   335  // necessarily obj.Decl, as obj.Decl will be a Spec for GenDecls, but
   336  // topDecls[obj] will be the GenDecl itself).
   337  func findDeclsAndUnresolved(body ast.Node, topDecls map[*ast.Object]ast.Decl, typMethods map[string][]ast.Decl) ([]ast.Decl, map[string]bool) {
   338  	// This function recursively finds every top-level declaration used
   339  	// transitively by the body, populating usedDecls and usedObjs. Then it
   340  	// trims down the declarations to include only the symbols actually
   341  	// referenced by the body.
   342  
   343  	unresolved := make(map[string]bool)
   344  	var depDecls []ast.Decl
   345  	usedDecls := make(map[ast.Decl]bool)   // set of top-level decls reachable from the body
   346  	usedObjs := make(map[*ast.Object]bool) // set of objects reachable from the body (each declared by a usedDecl)
   347  
   348  	var inspectFunc func(ast.Node) bool
   349  	inspectFunc = func(n ast.Node) bool {
   350  		switch e := n.(type) {
   351  		case *ast.Ident:
   352  			if e.Obj == nil && e.Name != "_" {
   353  				unresolved[e.Name] = true
   354  			} else if d := topDecls[e.Obj]; d != nil {
   355  
   356  				usedObjs[e.Obj] = true
   357  				if !usedDecls[d] {
   358  					usedDecls[d] = true
   359  					depDecls = append(depDecls, d)
   360  				}
   361  			}
   362  			return true
   363  		case *ast.SelectorExpr:
   364  			// For selector expressions, only inspect the left hand side.
   365  			// (For an expression like fmt.Println, only add "fmt" to the
   366  			// set of unresolved names, not "Println".)
   367  			ast.Inspect(e.X, inspectFunc)
   368  			return false
   369  		case *ast.KeyValueExpr:
   370  			// For key value expressions, only inspect the value
   371  			// as the key should be resolved by the type of the
   372  			// composite literal.
   373  			ast.Inspect(e.Value, inspectFunc)
   374  			return false
   375  		}
   376  		return true
   377  	}
   378  
   379  	inspectFieldList := func(fl *ast.FieldList) {
   380  		if fl != nil {
   381  			for _, f := range fl.List {
   382  				ast.Inspect(f.Type, inspectFunc)
   383  			}
   384  		}
   385  	}
   386  
   387  	// Find the decls immediately referenced by body.
   388  	ast.Inspect(body, inspectFunc)
   389  	// Now loop over them, adding to the list when we find a new decl that the
   390  	// body depends on. Keep going until we don't find anything new.
   391  	for i := 0; i < len(depDecls); i++ {
   392  		switch d := depDecls[i].(type) {
   393  		case *ast.FuncDecl:
   394  			// Inspect type parameters.
   395  			inspectFieldList(d.Type.TypeParams)
   396  			// Inspect types of parameters and results. See #28492.
   397  			inspectFieldList(d.Type.Params)
   398  			inspectFieldList(d.Type.Results)
   399  
   400  			// Functions might not have a body. See #42706.
   401  			if d.Body != nil {
   402  				ast.Inspect(d.Body, inspectFunc)
   403  			}
   404  		case *ast.GenDecl:
   405  			for _, spec := range d.Specs {
   406  				switch s := spec.(type) {
   407  				case *ast.TypeSpec:
   408  					inspectFieldList(s.TypeParams)
   409  					ast.Inspect(s.Type, inspectFunc)
   410  					depDecls = append(depDecls, typMethods[s.Name.Name]...)
   411  				case *ast.ValueSpec:
   412  					if s.Type != nil {
   413  						ast.Inspect(s.Type, inspectFunc)
   414  					}
   415  					for _, val := range s.Values {
   416  						ast.Inspect(val, inspectFunc)
   417  					}
   418  				}
   419  			}
   420  		}
   421  	}
   422  
   423  	// Some decls include multiple specs, such as a variable declaration with
   424  	// multiple variables on the same line, or a parenthesized declaration. Trim
   425  	// the declarations to include only the specs that are actually mentioned.
   426  	// However, if there is a constant group with iota, leave it all: later
   427  	// constant declarations in the group may have no value and so cannot stand
   428  	// on their own, and removing any constant from the group could change the
   429  	// values of subsequent ones.
   430  	// See testdata/examples/iota.go for a minimal example.
   431  	var ds []ast.Decl
   432  	for _, d := range depDecls {
   433  		switch d := d.(type) {
   434  		case *ast.FuncDecl:
   435  			ds = append(ds, d)
   436  		case *ast.GenDecl:
   437  			containsIota := false // does any spec have iota?
   438  			// Collect all Specs that were mentioned in the example.
   439  			var specs []ast.Spec
   440  			for _, s := range d.Specs {
   441  				switch s := s.(type) {
   442  				case *ast.TypeSpec:
   443  					if usedObjs[s.Name.Obj] {
   444  						specs = append(specs, s)
   445  					}
   446  				case *ast.ValueSpec:
   447  					if !containsIota {
   448  						containsIota = hasIota(s)
   449  					}
   450  					// A ValueSpec may have multiple names (e.g. "var a, b int").
   451  					// Keep only the names that were mentioned in the example.
   452  					// Exception: the multiple names have a single initializer (which
   453  					// would be a function call with multiple return values). In that
   454  					// case, keep everything.
   455  					if len(s.Names) > 1 && len(s.Values) == 1 {
   456  						specs = append(specs, s)
   457  						continue
   458  					}
   459  					ns := *s
   460  					ns.Names = nil
   461  					ns.Values = nil
   462  					for i, n := range s.Names {
   463  						if usedObjs[n.Obj] {
   464  							ns.Names = append(ns.Names, n)
   465  							if s.Values != nil {
   466  								ns.Values = append(ns.Values, s.Values[i])
   467  							}
   468  						}
   469  					}
   470  					if len(ns.Names) > 0 {
   471  						specs = append(specs, &ns)
   472  					}
   473  				}
   474  			}
   475  			if len(specs) > 0 {
   476  				// Constant with iota? Keep it all.
   477  				if d.Tok == token.CONST && containsIota {
   478  					ds = append(ds, d)
   479  				} else {
   480  					// Synthesize a GenDecl with just the Specs we need.
   481  					nd := *d // copy the GenDecl
   482  					nd.Specs = specs
   483  					if len(specs) == 1 {
   484  						// Remove grouping parens if there is only one spec.
   485  						nd.Lparen = 0
   486  					}
   487  					ds = append(ds, &nd)
   488  				}
   489  			}
   490  		}
   491  	}
   492  	return ds, unresolved
   493  }
   494  
   495  func hasIota(s ast.Spec) bool {
   496  	for n := range ast.Preorder(s) {
   497  		// Check that this is the special built-in "iota" identifier, not
   498  		// a user-defined shadow.
   499  		if id, ok := n.(*ast.Ident); ok && id.Name == "iota" && id.Obj == nil {
   500  			return true
   501  		}
   502  	}
   503  	return false
   504  }
   505  
   506  // findImportGroupStarts finds the start positions of each sequence of import
   507  // specs that are not separated by a blank line.
   508  func findImportGroupStarts(imps []*ast.ImportSpec) []token.Pos {
   509  	startImps := findImportGroupStarts1(imps)
   510  	groupStarts := make([]token.Pos, len(startImps))
   511  	for i, imp := range startImps {
   512  		groupStarts[i] = imp.Pos()
   513  	}
   514  	return groupStarts
   515  }
   516  
   517  // Helper for findImportGroupStarts to ease testing.
   518  func findImportGroupStarts1(origImps []*ast.ImportSpec) []*ast.ImportSpec {
   519  	// Copy to avoid mutation.
   520  	imps := make([]*ast.ImportSpec, len(origImps))
   521  	copy(imps, origImps)
   522  	// Assume the imports are sorted by position.
   523  	slices.SortFunc(imps, func(a, b *ast.ImportSpec) int {
   524  		return cmp.Compare(a.Pos(), b.Pos())
   525  	})
   526  	// Assume gofmt has been applied, so there is a blank line between adjacent imps
   527  	// if and only if they are more than 2 positions apart (newline, tab).
   528  	var groupStarts []*ast.ImportSpec
   529  	prevEnd := token.Pos(-2)
   530  	for _, imp := range imps {
   531  		if imp.Pos()-prevEnd > 2 {
   532  			groupStarts = append(groupStarts, imp)
   533  		}
   534  		prevEnd = imp.End()
   535  		// Account for end-of-line comments.
   536  		if imp.Comment != nil {
   537  			prevEnd = imp.Comment.End()
   538  		}
   539  	}
   540  	return groupStarts
   541  }
   542  
   543  // playExampleFile takes a whole file example and synthesizes a new *ast.File
   544  // such that the example is function main in package main.
   545  func playExampleFile(file *ast.File) *ast.File {
   546  	// Strip copyright comment if present.
   547  	comments := file.Comments
   548  	if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") {
   549  		comments = comments[1:]
   550  	}
   551  
   552  	// Copy declaration slice, rewriting the ExampleX function to main.
   553  	var decls []ast.Decl
   554  	for _, d := range file.Decls {
   555  		if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") {
   556  			// Copy the FuncDecl, as it may be used elsewhere.
   557  			newF := *f
   558  			newF.Name = ast.NewIdent("main")
   559  			newF.Body, comments = stripOutputComment(f.Body, comments)
   560  			d = &newF
   561  		}
   562  		decls = append(decls, d)
   563  	}
   564  
   565  	// Copy the File, as it may be used elsewhere.
   566  	f := *file
   567  	f.Name = ast.NewIdent("main")
   568  	f.Decls = decls
   569  	f.Comments = comments
   570  	return &f
   571  }
   572  
   573  // stripOutputComment finds and removes the "Output:" or "Unordered output:"
   574  // comment from body and comments, and adjusts the body block's end position.
   575  func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) {
   576  	// Do nothing if there is no "Output:" or "Unordered output:" comment.
   577  	i, last := lastComment(body, comments)
   578  	if last == nil || !outputPrefix.MatchString(last.Text()) {
   579  		return body, comments
   580  	}
   581  
   582  	// Copy body and comments, as the originals may be used elsewhere.
   583  	newBody := &ast.BlockStmt{
   584  		Lbrace: body.Lbrace,
   585  		List:   body.List,
   586  		Rbrace: last.Pos(),
   587  	}
   588  	newComments := make([]*ast.CommentGroup, len(comments)-1)
   589  	copy(newComments, comments[:i])
   590  	copy(newComments[i:], comments[i+1:])
   591  	return newBody, newComments
   592  }
   593  
   594  // lastComment returns the last comment inside the provided block.
   595  func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) {
   596  	if b == nil {
   597  		return
   598  	}
   599  	pos, end := b.Pos(), b.End()
   600  	for j, cg := range c {
   601  		if cg.Pos() < pos {
   602  			continue
   603  		}
   604  		if cg.End() > end {
   605  			break
   606  		}
   607  		i, last = j, cg
   608  	}
   609  	return
   610  }
   611  
   612  // classifyExamples classifies examples and assigns them to the Examples field
   613  // of the relevant Func, Type, or Package that the example is associated with.
   614  //
   615  // The classification process is ambiguous in some cases:
   616  //
   617  //   - ExampleFoo_Bar matches a type named Foo_Bar
   618  //     or a method named Foo.Bar.
   619  //   - ExampleFoo_bar matches a type named Foo_bar
   620  //     or Foo (with a "bar" suffix).
   621  //
   622  // Examples with malformed names are not associated with anything.
   623  func classifyExamples(p *Package, examples []*Example) {
   624  	if len(examples) == 0 {
   625  		return
   626  	}
   627  	// Mapping of names for funcs, types, and methods to the example listing.
   628  	ids := make(map[string]*[]*Example)
   629  	ids[""] = &p.Examples // package-level examples have an empty name
   630  	for _, f := range p.Funcs {
   631  		if !token.IsExported(f.Name) {
   632  			continue
   633  		}
   634  		ids[f.Name] = &f.Examples
   635  	}
   636  	for _, t := range p.Types {
   637  		if !token.IsExported(t.Name) {
   638  			continue
   639  		}
   640  		ids[t.Name] = &t.Examples
   641  		for _, f := range t.Funcs {
   642  			if !token.IsExported(f.Name) {
   643  				continue
   644  			}
   645  			ids[f.Name] = &f.Examples
   646  		}
   647  		for _, m := range t.Methods {
   648  			if !token.IsExported(m.Name) {
   649  				continue
   650  			}
   651  			ids[strings.TrimPrefix(nameWithoutInst(m.Recv), "*")+"_"+m.Name] = &m.Examples
   652  		}
   653  	}
   654  
   655  	// Group each example with the associated func, type, or method.
   656  	for _, ex := range examples {
   657  		// Consider all possible split points for the suffix
   658  		// by starting at the end of string (no suffix case),
   659  		// then trying all positions that contain a '_' character.
   660  		//
   661  		// An association is made on the first successful match.
   662  		// Examples with malformed names that match nothing are skipped.
   663  		for i := len(ex.Name); i >= 0; i = strings.LastIndexByte(ex.Name[:i], '_') {
   664  			prefix, suffix, ok := splitExampleName(ex.Name, i)
   665  			if !ok {
   666  				continue
   667  			}
   668  			exs, ok := ids[prefix]
   669  			if !ok {
   670  				continue
   671  			}
   672  			ex.Suffix = suffix
   673  			*exs = append(*exs, ex)
   674  			break
   675  		}
   676  	}
   677  
   678  	// Sort list of example according to the user-specified suffix name.
   679  	for _, exs := range ids {
   680  		slices.SortFunc(*exs, func(a, b *Example) int {
   681  			return cmp.Compare(a.Suffix, b.Suffix)
   682  		})
   683  	}
   684  }
   685  
   686  // nameWithoutInst returns name if name has no brackets. If name contains
   687  // brackets, then it returns name with all the contents between (and including)
   688  // the outermost left and right bracket removed.
   689  //
   690  // Adapted from debug/gosym/symtab.go:Sym.nameWithoutInst.
   691  func nameWithoutInst(name string) string {
   692  	start := strings.Index(name, "[")
   693  	if start < 0 {
   694  		return name
   695  	}
   696  	end := strings.LastIndex(name, "]")
   697  	if end < 0 {
   698  		// Malformed name, should contain closing bracket too.
   699  		return name
   700  	}
   701  	return name[0:start] + name[end+1:]
   702  }
   703  
   704  // splitExampleName attempts to split example name s at index i,
   705  // and reports if that produces a valid split. The suffix may be
   706  // absent. Otherwise, it must start with a lower-case letter and
   707  // be preceded by '_'.
   708  //
   709  // One of i == len(s) or s[i] == '_' must be true.
   710  func splitExampleName(s string, i int) (prefix, suffix string, ok bool) {
   711  	if i == len(s) {
   712  		return s, "", true
   713  	}
   714  	if i == len(s)-1 {
   715  		return "", "", false
   716  	}
   717  	prefix, suffix = s[:i], s[i+1:]
   718  	return prefix, suffix, isExampleSuffix(suffix)
   719  }
   720  
   721  func isExampleSuffix(s string) bool {
   722  	r, size := utf8.DecodeRuneInString(s)
   723  	return size > 0 && unicode.IsLower(r)
   724  }
   725  
   726  // updateBasicLitPos updates lit.Pos,
   727  // ensuring that lit.End is displaced by the same amount.
   728  // (See https://go.dev/issue/76395.)
   729  func updateBasicLitPos(lit *ast.BasicLit, pos token.Pos) {
   730  	len := lit.End() - lit.Pos()
   731  	lit.ValuePos = pos
   732  	if lit.ValueEnd.IsValid() {
   733  		lit.ValueEnd = pos + len
   734  	}
   735  }
   736  

View as plain text