func extractFunctionMethod(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info, isMethod bool) (*analysis.SuggestedFix, error) {
	errorPrefix := "extractFunction"
	if isMethod {
		errorPrefix = "extractMethod"
	}
	p, ok, methodOk, err := CanExtractFunction(fset, rng, src, file)
	if (!ok && !isMethod) || (!methodOk && isMethod) {
		return nil, fmt.Errorf("%s: cannot extract %s: %v", errorPrefix,
			fset.Position(rng.Start), err)
	}
	tok, path, rng, outer, start := p.tok, p.path, p.rng, p.outer, p.start
	fileScope := info.Scopes[file]
	if fileScope == nil {
		return nil, fmt.Errorf("%s: file scope is empty", errorPrefix)
	}
	pkgScope := fileScope.Parent()
	if pkgScope == nil {
		return nil, fmt.Errorf("%s: package scope is empty", errorPrefix)
	}
	var retStmts []*ast.ReturnStmt
	var hasNonNestedReturn bool
	startParent := findParent(outer, start)
	ast.Inspect(outer, func(n ast.Node) bool {
		if n == nil {
			return false
		}
		if n.Pos() < rng.Start || n.End() > rng.End {
			return n.Pos() <= rng.End
		}
		ret, ok := n.(*ast.ReturnStmt)
		if !ok {
			return true
		}
		if findParent(outer, n) == startParent {
			hasNonNestedReturn = true
		}
		retStmts = append(retStmts, ret)
		return false
	})
	containsReturnStatement := len(retStmts) > 0
	variables, err := collectFreeVars(info, file, fileScope, pkgScope, rng, path[0])
	if err != nil {
		return nil, err
	}
	var (
		receiverUsed bool
		receiver     *ast.Field
		receiverName string
		receiverObj  types.Object
	)
	if isMethod {
		if outer == nil || outer.Recv == nil || len(outer.Recv.List) == 0 {
			return nil, fmt.Errorf("%s: cannot extract need method receiver", errorPrefix)
		}
		receiver = outer.Recv.List[0]
		if len(receiver.Names) == 0 || receiver.Names[0] == nil {
			return nil, fmt.Errorf("%s: cannot extract need method receiver name", errorPrefix)
		}
		recvName := receiver.Names[0]
		receiverName = recvName.Name
		receiverObj = info.ObjectOf(recvName)
	}
	var (
		params, returns         []ast.Expr      
		paramTypes, returnTypes []*ast.Field    
		uninitialized           []types.Object  
	)
	seenVars := make(map[types.Object]ast.Expr)
	seenUninitialized := make(map[types.Object]struct{})
	var canRedefineCount int
	for _, v := range variables {
		if _, ok := seenVars[v.obj]; ok {
			continue
		}
		if v.obj.Name() == "_" {
			continue
		}
		typ := analysisinternal.TypeExpr(fset, file, pkg, v.obj.Type())
		if typ == nil {
			return nil, fmt.Errorf("nil AST expression for type: %v", v.obj.Name())
		}
		seenVars[v.obj] = typ
		identifier := ast.NewIdent(v.obj.Name())
		if v.obj.Parent() == nil {
			return nil, fmt.Errorf("parent nil")
		}
		isUsed, firstUseAfter := objUsed(info, span.NewRange(fset, rng.End, v.obj.Parent().End()), v.obj)
		if v.assigned && isUsed && !varOverridden(info, firstUseAfter, v.obj, v.free, outer) {
			returnTypes = append(returnTypes, &ast.Field{Type: typ})
			returns = append(returns, identifier)
			if !v.free {
				uninitialized = append(uninitialized, v.obj)
			} else if v.obj.Parent().Pos() == startParent.Pos() {
				canRedefineCount++
			}
		}
		if v.free && !v.defined {
			if isMethod && v.obj == receiverObj {
				receiverUsed = true
				continue
			}
			params = append(params, identifier)
			paramTypes = append(paramTypes, &ast.Field{
				Names: []*ast.Ident{identifier},
				Type:  typ,
			})
		}
	}
	enclosing := outer.Type
	for _, p := range path {
		if p == enclosing {
			break
		}
		if fl, ok := p.(*ast.FuncLit); ok {
			enclosing = fl.Type
			break
		}
	}
	startOffset, err := Offset(tok, rng.Start)
	if err != nil {
		return nil, err
	}
	endOffset, err := Offset(tok, rng.End)
	if err != nil {
		return nil, err
	}
	selection := src[startOffset:endOffset]
	extractedBlock, err := parseBlockStmt(fset, selection)
	if err != nil {
		return nil, err
	}
	var retVars []*returnVariable
	var ifReturn *ast.IfStmt
	if containsReturnStatement {
		if !hasNonNestedReturn {
			if err := adjustReturnStatements(returnTypes, seenVars, fset, file,
				pkg, extractedBlock); err != nil {
				return nil, err
			}
		}
		retVars, ifReturn, err = generateReturnInfo(enclosing, pkg, path, file, info, fset, rng.Start, hasNonNestedReturn)
		if err != nil {
			return nil, err
		}
	}
	hasReturnValues := len(returns)+len(retVars) > 0
	if hasReturnValues && !hasNonNestedReturn {
		extractedBlock.List = append(extractedBlock.List, &ast.ReturnStmt{
			Results: append(returns, getZeroVals(retVars)...),
		})
	}
	sym := token.ASSIGN
	canDefineCount := len(uninitialized) + canRedefineCount
	canDefine := len(uninitialized)+len(retVars) > 0 && canDefineCount == len(returns)
	if canDefine {
		sym = token.DEFINE
	}
	var name, funName string
	if isMethod {
		name = "newMethod"
		funName = name
	} else {
		name = "newFunction"
		funName, _ = generateAvailableIdentifier(rng.Start, file, path, info, name, 0)
	}
	extractedFunCall := generateFuncCall(hasNonNestedReturn, hasReturnValues, params,
		append(returns, getNames(retVars)...), funName, sym, receiverName)
	newFunc := &ast.FuncDecl{
		Name: ast.NewIdent(funName),
		Type: &ast.FuncType{
			Params:  &ast.FieldList{List: paramTypes},
			Results: &ast.FieldList{List: append(returnTypes, getDecls(retVars)...)},
		},
		Body: extractedBlock,
	}
	if isMethod {
		var names []*ast.Ident
		if receiverUsed {
			names = append(names, ast.NewIdent(receiverName))
		}
		newFunc.Recv = &ast.FieldList{
			List: []*ast.Field{{
				Names: names,
				Type:  receiver.Type,
			}},
		}
	}
	var declarations []ast.Stmt
	if canDefineCount != len(returns) {
		declarations = initializeVars(uninitialized, retVars, seenUninitialized, seenVars)
	}
	var declBuf, replaceBuf, newFuncBuf, ifBuf, commentBuf bytes.Buffer
	if err := format.Node(&declBuf, fset, declarations); err != nil {
		return nil, err
	}
	if err := format.Node(&replaceBuf, fset, extractedFunCall); err != nil {
		return nil, err
	}
	if ifReturn != nil {
		if err := format.Node(&ifBuf, fset, ifReturn); err != nil {
			return nil, err
		}
	}
	if err := format.Node(&newFuncBuf, fset, newFunc); err != nil {
		return nil, err
	}
	for _, cg := range file.Comments {
		if cg.Pos().IsValid() && cg.Pos() < rng.End && cg.Pos() >= rng.Start {
			for _, c := range cg.List {
				fmt.Fprintln(&commentBuf, c.Text)
			}
		}
	}
	outerStart, err := Offset(tok, outer.Pos())
	if err != nil {
		return nil, err
	}
	outerEnd, err := Offset(tok, outer.End())
	if err != nil {
		return nil, err
	}
	before := src[outerStart:startOffset]
	after := src[endOffset:outerEnd]
	indent, err := calculateIndentation(src, tok, start)
	if err != nil {
		return nil, err
	}
	newLineIndent := "\n" + indent
	var fullReplacement strings.Builder
	fullReplacement.Write(before)
	if commentBuf.Len() > 0 {
		comments := strings.ReplaceAll(commentBuf.String(), "\n", newLineIndent)
		fullReplacement.WriteString(comments)
	}
	if declBuf.Len() > 0 {  
		initializations := strings.ReplaceAll(declBuf.String(), "\n", newLineIndent) +
			newLineIndent
		fullReplacement.WriteString(initializations)
	}
	fullReplacement.Write(replaceBuf.Bytes())  
	if ifBuf.Len() > 0 {                       
		ifstatement := newLineIndent +
			strings.ReplaceAll(ifBuf.String(), "\n", newLineIndent)
		fullReplacement.WriteString(ifstatement)
	}
	fullReplacement.Write(after)
	fullReplacement.WriteString("\n\n")        
	fullReplacement.Write(newFuncBuf.Bytes())  
	return &analysis.SuggestedFix{
		TextEdits: []analysis.TextEdit{{
			Pos:     outer.Pos(),
			End:     outer.End(),
			NewText: []byte(fullReplacement.String()),
		}},
	}, nil
}
extractFunctionMethod refactors the selected block of code into a new function/method.
It also replaces the selected block of code with a call to the extracted
function. First, we manually adjust the selection range. We remove trailing
and leading whitespace characters to ensure the range is precisely bounded
by AST nodes. Next, we determine the variables that will be the parameters
and return values of the extracted function/method. Lastly, we construct the call
of the function/method and insert this call as well as the extracted function/method into
their proper locations.
package source
import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"go/types"
	"strings"
	"unicode"
	"golang.org/x/tools/go/analysis"
	"golang.org/x/tools/go/ast/astutil"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/analysisinternal"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/span"
)
func extractVariable(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, _ *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
	expr, path, ok, err := CanExtractVariable(rng, file)
	if !ok {
		return nil, fmt.Errorf("extractVariable: cannot extract %s: %v", fset.Position(rng.Start), err)
	}
	var lhsNames []string
	switch expr := expr.(type) {
	case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.SliceExpr,
		*ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr:
		lhsName, _ := generateAvailableIdentifier(expr.Pos(), file, path, info, "x", 0)
		lhsNames = append(lhsNames, lhsName)
	case *ast.CallExpr:
		tup, ok := info.TypeOf(expr).(*types.Tuple)
		if !ok {
			lhsName, _ := generateAvailableIdentifier(expr.Pos(), file, path, info, "x", 0)
			lhsNames = append(lhsNames, lhsName)
			break
		}
		idx := 0
		for i := 0; i < tup.Len(); i++ {
			var lhsName string
			lhsName, idx = generateAvailableIdentifier(expr.Pos(), file, path, info, "x", idx)
			lhsNames = append(lhsNames, lhsName)
		}
	default:
		return nil, fmt.Errorf("cannot extract %T", expr)
	}
	insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
	if insertBeforeStmt == nil {
		return nil, fmt.Errorf("cannot find location to insert extraction")
	}
	tok := fset.File(expr.Pos())
	if tok == nil {
		return nil, fmt.Errorf("no file for pos %v", fset.Position(file.Pos()))
	}
	indent, err := calculateIndentation(src, tok, insertBeforeStmt)
	if err != nil {
		return nil, err
	}
	newLineIndent := "\n" + indent
	lhs := strings.Join(lhsNames, ", ")
	assignStmt := &ast.AssignStmt{
		Lhs: []ast.Expr{ast.NewIdent(lhs)},
		Tok: token.DEFINE,
		Rhs: []ast.Expr{expr},
	}
	var buf bytes.Buffer
	if err := format.Node(&buf, fset, assignStmt); err != nil {
		return nil, err
	}
	assignment := strings.ReplaceAll(buf.String(), "\n", newLineIndent) + newLineIndent
	return &analysis.SuggestedFix{
		TextEdits: []analysis.TextEdit{
			{
				Pos:     insertBeforeStmt.Pos(),
				End:     insertBeforeStmt.Pos(),
				NewText: []byte(assignment),
			},
			{
				Pos:     rng.Start,
				End:     rng.End,
				NewText: []byte(lhs),
			},
		},
	}, nil
}
func CanExtractVariable(rng span.Range, file *ast.File) (ast.Expr, []ast.Node, bool, error) {
	if rng.Start == rng.End {
		return nil, nil, false, fmt.Errorf("start and end are equal")
	}
	path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.End)
	if len(path) == 0 {
		return nil, nil, false, fmt.Errorf("no path enclosing interval")
	}
	for _, n := range path {
		if _, ok := n.(*ast.ImportSpec); ok {
			return nil, nil, false, fmt.Errorf("cannot extract variable in an import block")
		}
	}
	node := path[0]
	if rng.Start != node.Pos() || rng.End != node.End() {
		return nil, nil, false, fmt.Errorf("range does not map to an AST node")
	}
	expr, ok := node.(ast.Expr)
	if !ok {
		return nil, nil, false, fmt.Errorf("node is not an expression")
	}
	switch expr.(type) {
	case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.CallExpr,
		*ast.SliceExpr, *ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr:
		return expr, path, true, nil
	}
	return nil, nil, false, fmt.Errorf("cannot extract an %T to a variable", expr)
}
func calculateIndentation(content []byte, tok *token.File, insertBeforeStmt ast.Node) (string, error) {
	line := tok.Line(insertBeforeStmt.Pos())
	lineOffset, err := Offset(tok, tok.LineStart(line))
	if err != nil {
		return "", err
	}
	stmtOffset, err := Offset(tok, insertBeforeStmt.Pos())
	if err != nil {
		return "", err
	}
	return string(content[lineOffset:stmtOffset]), nil
}
func generateAvailableIdentifier(pos token.Pos, file *ast.File, path []ast.Node, info *types.Info, prefix string, idx int) (string, int) {
	scopes := CollectScopes(info, path, pos)
	return generateIdentifier(idx, prefix, func(name string) bool {
		return file.Scope.Lookup(name) != nil || !isValidName(name, scopes)
	})
}
func generateIdentifier(idx int, prefix string, hasCollision func(string) bool) (string, int) {
	name := prefix
	if idx != 0 {
		name += fmt.Sprintf("%d", idx)
	}
	for hasCollision(name) {
		idx++
		name = fmt.Sprintf("%v%d", prefix, idx)
	}
	return name, idx + 1
}
func isValidName(name string, scopes []*types.Scope) bool {
	for _, scope := range scopes {
		if scope == nil {
			continue
		}
		if scope.Lookup(name) != nil {
			return false
		}
	}
	return true
}
type returnVariable struct {
	name ast.Expr
	decl *ast.Field
	zeroVal ast.Expr
}
func extractMethod(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
	return extractFunctionMethod(fset, rng, src, file, pkg, info, true)
}
func extractFunction(fset *token.FileSet, rng span.Range, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
	return extractFunctionMethod(fset, rng, src, file, pkg, info, false)
}