// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Extract example functions from file ASTs.

package doc

import (
	
	
	
	
	
	
	
	
	
	
)

// An Example represents an example function found in a test source file.
type Example struct {
	Name        string // name of the item being exemplified (including optional suffix)
	Suffix      string // example suffix, without leading '_' (only populated by NewFromFiles)
	Doc         string // example function doc string
	Code        ast.Node
	Play        *ast.File // a whole program version of the example
	Comments    []*ast.CommentGroup
	Output      string // expected output
	Unordered   bool
	EmptyOutput bool // expect empty output
	Order       int  // original source code order
}

// Examples returns the examples found in testFiles, sorted by Name field.
// The Order fields record the order in which the examples were encountered.
// The Suffix field is not populated when Examples is called directly, it is
// only populated by [NewFromFiles] for examples it finds in _test.go files.
//
// Playable Examples must be in a package whose name ends in "_test".
// An Example is "playable" (the Play field is non-nil) in either of these
// circumstances:
//   - The example function is self-contained: the function references only
//     identifiers from other packages (or predeclared identifiers, such as
//     "int") and the test file does not include a dot import.
//   - The entire test file is the example: the file contains exactly one
//     example function, zero test, fuzz test, or benchmark function, and at
//     least one top-level function, type, variable, or constant declaration
//     other than the example function.
func ( ...*ast.File) []*Example {
	var  []*Example
	for ,  := range  {
		 := false // file contains tests, fuzz test, or benchmarks
		 := 0      // number of non-import declarations in the file
		var  []*Example
		for ,  := range .Decls {
			if ,  := .(*ast.GenDecl);  && .Tok != token.IMPORT {
				++
				continue
			}
			,  := .(*ast.FuncDecl)
			if ! || .Recv != nil {
				continue
			}
			++
			 := .Name.Name
			if isTest(, "Test") || isTest(, "Benchmark") || isTest(, "Fuzz") {
				 = true
				continue
			}
			if !isTest(, "Example") {
				continue
			}
			if  := .Type.Params; len(.List) != 0 {
				continue // function has params; not a valid example
			}
			if .Body == nil { // ast.File.Body nil dereference (see issue 28044)
				continue
			}
			var  string
			if .Doc != nil {
				 = .Doc.Text()
			}
			, ,  := exampleOutput(.Body, .Comments)
			 = append(, &Example{
				Name:        [len("Example"):],
				Doc:         ,
				Code:        .Body,
				Play:        playExample(, ),
				Comments:    .Comments,
				Output:      ,
				Unordered:   ,
				EmptyOutput:  == "" && ,
				Order:       len(),
			})
		}
		if ! &&  > 1 && len() == 1 {
			// If this file only has one example function, some
			// other top-level declarations, and no tests or
			// benchmarks, use the whole file as the example.
			[0].Code = 
			[0].Play = playExampleFile()
		}
		 = append(, ...)
	}
	// sort by name
	slices.SortFunc(, func(,  *Example) int {
		return cmp.Compare(.Name, .Name)
	})
	return 
}

var outputPrefix = lazyregexp.New(`(?i)^[[:space:]]*(unordered )?output:`)

// Extracts the expected output and whether there was a valid output comment.
func exampleOutput( *ast.BlockStmt,  []*ast.CommentGroup) ( string, ,  bool) {
	if ,  := lastComment(, );  != nil {
		// test that it begins with the correct prefix
		 := .Text()
		if  := outputPrefix.FindStringSubmatchIndex();  != nil {
			if [2] != -1 {
				 = true
			}
			 = [[1]:]
			// Strip zero or more spaces followed by \n or a single space.
			 = strings.TrimLeft(, " ")
			if len() > 0 && [0] == '\n' {
				 = [1:]
			}
			return , , true
		}
	}
	return "", false, false // no suitable comment found
}

// isTest tells whether name looks like a test, example, fuzz test, or
// benchmark. It is a Test (say) if there is a character after Test that is not
// a lower-case letter. (We don't want Testiness.)
func isTest(,  string) bool {
	if !strings.HasPrefix(, ) {
		return false
	}
	if len() == len() { // "Test" is ok
		return true
	}
	,  := utf8.DecodeRuneInString([len():])
	return !unicode.IsLower()
}

// playExample synthesizes a new *ast.File based on the provided
// file with the provided function body as the body of main.
func playExample( *ast.File,  *ast.FuncDecl) *ast.File {
	 := .Body

	if !strings.HasSuffix(.Name.Name, "_test") {
		// We don't support examples that are part of the
		// greater package (yet).
		return nil
	}

	// Collect top-level declarations in the file.
	 := make(map[*ast.Object]ast.Decl)
	 := make(map[string][]ast.Decl)

	for ,  := range .Decls {
		switch d := .(type) {
		case *ast.FuncDecl:
			if .Recv == nil {
				[.Name.Obj] = 
			} else {
				if len(.Recv.List) == 1 {
					 := .Recv.List[0].Type
					,  := baseTypeName()
					[] = append([], )
				}
			}
		case *ast.GenDecl:
			for ,  := range .Specs {
				switch s := .(type) {
				case *ast.TypeSpec:
					[.Name.Obj] = 
				case *ast.ValueSpec:
					for ,  := range .Names {
						[.Obj] = 
					}
				}
			}
		}
	}

	// Find unresolved identifiers and uses of top-level declarations.
	,  := findDeclsAndUnresolved(, , )

	// Remove predeclared identifiers from unresolved list.
	for  := range  {
		if predeclaredTypes[] || predeclaredConstants[] || predeclaredFuncs[] {
			delete(, )
		}
	}

	// Use unresolved identifiers to determine the imports used by this
	// example. The heuristic assumes package names match base import
	// paths for imports w/o renames (should be good enough most of the time).
	var  []ast.Spec
	var  []ast.Spec // _ imports

	// To preserve the blank lines between groups of imports, find the
	// start position of each group, and assign that position to all
	// imports from that group.
	 := findImportGroupStarts(.Imports)
	 := func( *ast.ImportSpec) token.Pos {
		for ,  := range  {
			if .Path.ValuePos <  {
				return [-1]
			}
		}
		return [len()-1]
	}

	for ,  := range .Imports {
		,  := strconv.Unquote(.Path.Value)
		if  != nil {
			continue
		}
		if  == "syscall/js" {
			// We don't support examples that import syscall/js,
			// because the package syscall/js is not available in the playground.
			return nil
		}
		 := path.Base()
		if .Name != nil {
			 = .Name.Name
			switch  {
			case "_":
				 = append(, )
				continue
			case ".":
				// We can't resolve dot imports (yet).
				return nil
			}
		}
		if [] {
			// Copy the spec and its path to avoid modifying the original.
			 := *
			 := *.Path
			.Path = &
			.Path.ValuePos = (&)
			 = append(, &)
			delete(, )
		}
	}

	// If there are other unresolved identifiers, give up because this
	// synthesized file is not going to build.
	if len() > 0 {
		return nil
	}

	// Include documentation belonging to blank imports.
	var  []*ast.CommentGroup
	for ,  := range  {
		if  := .(*ast.ImportSpec).Doc;  != nil {
			 = append(, )
		}
	}

	// Include comments that are inside the function body.
	for ,  := range .Comments {
		if .Pos() <= .Pos() && .End() <= .End() {
			 = append(, )
		}
	}

	// Strip the "Output:" or "Unordered output:" comment and adjust body
	// end position.
	,  = stripOutputComment(, )

	// Include documentation belonging to dependent declarations.
	for ,  := range  {
		switch d := .(type) {
		case *ast.GenDecl:
			if .Doc != nil {
				 = append(, .Doc)
			}
		case *ast.FuncDecl:
			if .Doc != nil {
				 = append(, .Doc)
			}
		}
	}

	// Synthesize import declaration.
	 := &ast.GenDecl{
		Tok:    token.IMPORT,
		Lparen: 1, // Need non-zero Lparen and Rparen so that printer
		Rparen: 1, // treats this as a factored import.
	}
	.Specs = append(, ...)

	// Synthesize main function.
	 := &ast.FuncDecl{
		Name: ast.NewIdent("main"),
		Type: .Type,
		Body: ,
	}

	 := make([]ast.Decl, 0, 2+len())
	 = append(, )
	 = append(, ...)
	 = append(, )

	slices.SortFunc(, func(,  ast.Decl) int {
		return cmp.Compare(.Pos(), .Pos())
	})
	slices.SortFunc(, func(,  *ast.CommentGroup) int {
		return cmp.Compare(.Pos(), .Pos())
	})

	// Synthesize file.
	return &ast.File{
		Name:     ast.NewIdent("main"),
		Decls:    ,
		Comments: ,
	}
}

// findDeclsAndUnresolved returns all the top-level declarations mentioned in
// the body, and a set of unresolved symbols (those that appear in the body but
// have no declaration in the program).
//
// topDecls maps objects to the top-level declaration declaring them (not
// necessarily obj.Decl, as obj.Decl will be a Spec for GenDecls, but
// topDecls[obj] will be the GenDecl itself).
func findDeclsAndUnresolved( ast.Node,  map[*ast.Object]ast.Decl,  map[string][]ast.Decl) ([]ast.Decl, map[string]bool) {
	// This function recursively finds every top-level declaration used
	// transitively by the body, populating usedDecls and usedObjs. Then it
	// trims down the declarations to include only the symbols actually
	// referenced by the body.

	 := make(map[string]bool)
	var  []ast.Decl
	 := make(map[ast.Decl]bool)   // set of top-level decls reachable from the body
	 := make(map[*ast.Object]bool) // set of objects reachable from the body (each declared by a usedDecl)

	var  func(ast.Node) bool
	 = func( ast.Node) bool {
		switch e := .(type) {
		case *ast.Ident:
			if .Obj == nil && .Name != "_" {
				[.Name] = true
			} else if  := [.Obj];  != nil {

				[.Obj] = true
				if ![] {
					[] = true
					 = append(, )
				}
			}
			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(.X, )
			return false
		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(.Value, )
			return false
		}
		return true
	}

	 := func( *ast.FieldList) {
		if  != nil {
			for ,  := range .List {
				ast.Inspect(.Type, )
			}
		}
	}

	// Find the decls immediately referenced by body.
	ast.Inspect(, )
	// Now loop over them, adding to the list when we find a new decl that the
	// body depends on. Keep going until we don't find anything new.
	for  := 0;  < len(); ++ {
		switch d := [].(type) {
		case *ast.FuncDecl:
			// Inspect type parameters.
			(.Type.TypeParams)
			// Inspect types of parameters and results. See #28492.
			(.Type.Params)
			(.Type.Results)

			// Functions might not have a body. See #42706.
			if .Body != nil {
				ast.Inspect(.Body, )
			}
		case *ast.GenDecl:
			for ,  := range .Specs {
				switch s := .(type) {
				case *ast.TypeSpec:
					(.TypeParams)
					ast.Inspect(.Type, )
					 = append(, [.Name.Name]...)
				case *ast.ValueSpec:
					if .Type != nil {
						ast.Inspect(.Type, )
					}
					for ,  := range .Values {
						ast.Inspect(, )
					}
				}
			}
		}
	}

	// Some decls include multiple specs, such as a variable declaration with
	// multiple variables on the same line, or a parenthesized declaration. Trim
	// the declarations to include only the specs that are actually mentioned.
	// However, if there is a constant group with iota, leave it all: later
	// constant declarations in the group may have no value and so cannot stand
	// on their own, and removing any constant from the group could change the
	// values of subsequent ones.
	// See testdata/examples/iota.go for a minimal example.
	var  []ast.Decl
	for ,  := range  {
		switch d := .(type) {
		case *ast.FuncDecl:
			 = append(, )
		case *ast.GenDecl:
			 := false // does any spec have iota?
			// Collect all Specs that were mentioned in the example.
			var  []ast.Spec
			for ,  := range .Specs {
				switch s := .(type) {
				case *ast.TypeSpec:
					if [.Name.Obj] {
						 = append(, )
					}
				case *ast.ValueSpec:
					if ! {
						 = hasIota()
					}
					// A ValueSpec may have multiple names (e.g. "var a, b int").
					// Keep only the names that were mentioned in the example.
					// Exception: the multiple names have a single initializer (which
					// would be a function call with multiple return values). In that
					// case, keep everything.
					if len(.Names) > 1 && len(.Values) == 1 {
						 = append(, )
						continue
					}
					 := *
					.Names = nil
					.Values = nil
					for ,  := range .Names {
						if [.Obj] {
							.Names = append(.Names, )
							if .Values != nil {
								.Values = append(.Values, .Values[])
							}
						}
					}
					if len(.Names) > 0 {
						 = append(, &)
					}
				}
			}
			if len() > 0 {
				// Constant with iota? Keep it all.
				if .Tok == token.CONST &&  {
					 = append(, )
				} else {
					// Synthesize a GenDecl with just the Specs we need.
					 := * // copy the GenDecl
					.Specs = 
					if len() == 1 {
						// Remove grouping parens if there is only one spec.
						.Lparen = 0
					}
					 = append(, &)
				}
			}
		}
	}
	return , 
}

func hasIota( ast.Spec) bool {
	 := false
	ast.Inspect(, func( ast.Node) bool {
		// Check that this is the special built-in "iota" identifier, not
		// a user-defined shadow.
		if ,  := .(*ast.Ident);  && .Name == "iota" && .Obj == nil {
			 = true
			return false
		}
		return true
	})
	return 
}

// findImportGroupStarts finds the start positions of each sequence of import
// specs that are not separated by a blank line.
func findImportGroupStarts( []*ast.ImportSpec) []token.Pos {
	 := findImportGroupStarts1()
	 := make([]token.Pos, len())
	for ,  := range  {
		[] = .Pos()
	}
	return 
}

// Helper for findImportGroupStarts to ease testing.
func findImportGroupStarts1( []*ast.ImportSpec) []*ast.ImportSpec {
	// Copy to avoid mutation.
	 := make([]*ast.ImportSpec, len())
	copy(, )
	// Assume the imports are sorted by position.
	slices.SortFunc(, func(,  *ast.ImportSpec) int {
		return cmp.Compare(.Pos(), .Pos())
	})
	// Assume gofmt has been applied, so there is a blank line between adjacent imps
	// if and only if they are more than 2 positions apart (newline, tab).
	var  []*ast.ImportSpec
	 := token.Pos(-2)
	for ,  := range  {
		if .Pos()- > 2 {
			 = append(, )
		}
		 = .End()
		// Account for end-of-line comments.
		if .Comment != nil {
			 = .Comment.End()
		}
	}
	return 
}

// playExampleFile takes a whole file example and synthesizes a new *ast.File
// such that the example is function main in package main.
func playExampleFile( *ast.File) *ast.File {
	// Strip copyright comment if present.
	 := .Comments
	if len() > 0 && strings.HasPrefix([0].Text(), "Copyright") {
		 = [1:]
	}

	// Copy declaration slice, rewriting the ExampleX function to main.
	var  []ast.Decl
	for ,  := range .Decls {
		if ,  := .(*ast.FuncDecl);  && isTest(.Name.Name, "Example") {
			// Copy the FuncDecl, as it may be used elsewhere.
			 := *
			.Name = ast.NewIdent("main")
			.Body,  = stripOutputComment(.Body, )
			 = &
		}
		 = append(, )
	}

	// Copy the File, as it may be used elsewhere.
	 := *
	.Name = ast.NewIdent("main")
	.Decls = 
	.Comments = 
	return &
}

// stripOutputComment finds and removes the "Output:" or "Unordered output:"
// comment from body and comments, and adjusts the body block's end position.
func stripOutputComment( *ast.BlockStmt,  []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) {
	// Do nothing if there is no "Output:" or "Unordered output:" comment.
	,  := lastComment(, )
	if  == nil || !outputPrefix.MatchString(.Text()) {
		return , 
	}

	// Copy body and comments, as the originals may be used elsewhere.
	 := &ast.BlockStmt{
		Lbrace: .Lbrace,
		List:   .List,
		Rbrace: .Pos(),
	}
	 := make([]*ast.CommentGroup, len()-1)
	copy(, [:])
	copy([:], [+1:])
	return , 
}

// lastComment returns the last comment inside the provided block.
func lastComment( *ast.BlockStmt,  []*ast.CommentGroup) ( int,  *ast.CommentGroup) {
	if  == nil {
		return
	}
	,  := .Pos(), .End()
	for ,  := range  {
		if .Pos() <  {
			continue
		}
		if .End() >  {
			break
		}
		,  = , 
	}
	return
}

// classifyExamples classifies examples and assigns them to the Examples field
// of the relevant Func, Type, or Package that the example is associated with.
//
// The classification process is ambiguous in some cases:
//
//   - ExampleFoo_Bar matches a type named Foo_Bar
//     or a method named Foo.Bar.
//   - ExampleFoo_bar matches a type named Foo_bar
//     or Foo (with a "bar" suffix).
//
// Examples with malformed names are not associated with anything.
func classifyExamples( *Package,  []*Example) {
	if len() == 0 {
		return
	}
	// Mapping of names for funcs, types, and methods to the example listing.
	 := make(map[string]*[]*Example)
	[""] = &.Examples // package-level examples have an empty name
	for ,  := range .Funcs {
		if !token.IsExported(.Name) {
			continue
		}
		[.Name] = &.Examples
	}
	for ,  := range .Types {
		if !token.IsExported(.Name) {
			continue
		}
		[.Name] = &.Examples
		for ,  := range .Funcs {
			if !token.IsExported(.Name) {
				continue
			}
			[.Name] = &.Examples
		}
		for ,  := range .Methods {
			if !token.IsExported(.Name) {
				continue
			}
			[strings.TrimPrefix(nameWithoutInst(.Recv), "*")+"_"+.Name] = &.Examples
		}
	}

	// Group each example with the associated func, type, or method.
	for ,  := range  {
		// Consider all possible split points for the suffix
		// by starting at the end of string (no suffix case),
		// then trying all positions that contain a '_' character.
		//
		// An association is made on the first successful match.
		// Examples with malformed names that match nothing are skipped.
		for  := len(.Name);  >= 0;  = strings.LastIndexByte(.Name[:], '_') {
			, ,  := splitExampleName(.Name, )
			if ! {
				continue
			}
			,  := []
			if ! {
				continue
			}
			.Suffix = 
			* = append(*, )
			break
		}
	}

	// Sort list of example according to the user-specified suffix name.
	for ,  := range  {
		slices.SortFunc(*, func(,  *Example) int {
			return cmp.Compare(.Suffix, .Suffix)
		})
	}
}

// nameWithoutInst returns name if name has no brackets. If name contains
// brackets, then it returns name with all the contents between (and including)
// the outermost left and right bracket removed.
//
// Adapted from debug/gosym/symtab.go:Sym.nameWithoutInst.
func nameWithoutInst( string) string {
	 := strings.Index(, "[")
	if  < 0 {
		return 
	}
	 := strings.LastIndex(, "]")
	if  < 0 {
		// Malformed name, should contain closing bracket too.
		return 
	}
	return [0:] + [+1:]
}

// splitExampleName attempts to split example name s at index i,
// and reports if that produces a valid split. The suffix may be
// absent. Otherwise, it must start with a lower-case letter and
// be preceded by '_'.
//
// One of i == len(s) or s[i] == '_' must be true.
func splitExampleName( string,  int) (,  string,  bool) {
	if  == len() {
		return , "", true
	}
	if  == len()-1 {
		return "", "", false
	}
	,  = [:], [+1:]
	return , , isExampleSuffix()
}

func isExampleSuffix( string) bool {
	,  := utf8.DecodeRuneInString()
	return  > 0 && unicode.IsLower()
}