func referencesObj(info *types.Info, expr ast.Expr, obj types.Object) bool {
	var hasObj bool
	ast.Inspect(expr, func(n ast.Node) bool {
		if n == nil {
			return false
		}
		ident, ok := n.(*ast.Ident)
		if !ok {
			return true
		}
		objUse := info.Uses[ident]
		if obj == objUse {
			hasObj = true
			return false
		}
		return false
	})
	return hasObj
}
referencesObj checks whether the given object appears in the given expression.
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)
}
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
}
func adjustRangeForWhitespace(rng span.Range, tok *token.File, content []byte) (span.Range, error) {
	offset, err := Offset(tok, rng.Start)
	if err != nil {
		return span.Range{}, err
	}
	for offset < len(content) {
		if !unicode.IsSpace(rune(content[offset])) {
			break
		}
		offset += 1
	}
	rng.Start = tok.Pos(offset)
	offset, err = Offset(tok, rng.End)
	if err != nil {
		return span.Range{}, err
	}
	for o := offset - 1; 0 <= o && o < len(content); o-- {
		if !unicode.IsSpace(rune(content[o])) {
			break
		}
		offset = o
	}
	rng.End = tok.Pos(offset)
	return rng, nil
}
func findParent(start ast.Node, target ast.Node) ast.Node {
	var parent ast.Node
	analysisinternal.WalkASTWithParent(start, func(n, p ast.Node) bool {
		if n == target {
			parent = p
			return false
		}
		return true
	})
	return parent
}
type variable struct {
	obj types.Object
	free bool
	assigned bool
	defined bool
}
func collectFreeVars(info *types.Info, file *ast.File, fileScope, pkgScope *types.Scope, rng span.Range, node ast.Node) ([]*variable, error) {
	id := func(n *ast.Ident) (types.Object, bool) {
		obj := info.Uses[n]
		if obj == nil {
			return info.Defs[n], false
		}
		if obj.Name() == "_" {
			return nil, false  
		}
		if _, ok := obj.(*types.PkgName); ok {
			return nil, false  
		}
		if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) {
			return nil, false  
		}
		scope := obj.Parent()
		if scope == nil {
			return nil, false  
		}
		if scope == fileScope || scope == pkgScope {
			return nil, false  
		}
		if rng.Start <= obj.Pos() && obj.Pos() <= rng.End {
			return obj, false  
		}
		return obj, true
	}
	var sel func(n *ast.SelectorExpr) (types.Object, bool)
	sel = func(n *ast.SelectorExpr) (types.Object, bool) {
		switch x := astutil.Unparen(n.X).(type) {
		case *ast.SelectorExpr:
			return sel(x)
		case *ast.Ident:
			return id(x)
		}
		return nil, false
	}
	seen := make(map[types.Object]*variable)
	firstUseIn := make(map[types.Object]token.Pos)
	var vars []types.Object
	ast.Inspect(node, func(n ast.Node) bool {
		if n == nil {
			return false
		}
		if rng.Start <= n.Pos() && n.End() <= rng.End {
			var obj types.Object
			var isFree, prune bool
			switch n := n.(type) {
			case *ast.Ident:
				obj, isFree = id(n)
			case *ast.SelectorExpr:
				obj, isFree = sel(n)
				prune = true
			}
			if obj != nil {
				seen[obj] = &variable{
					obj:  obj,
					free: isFree,
				}
				vars = append(vars, obj)
				first, ok := firstUseIn[obj]
				if !ok || n.Pos() < first {
					firstUseIn[obj] = n.Pos()
				}
				if prune {
					return false
				}
			}
		}
		return n.Pos() <= rng.End
	})
	ast.Inspect(node, func(n ast.Node) bool {
		if n == nil {
			return false
		}
		if n.Pos() < rng.Start || n.End() > rng.End {
			return n.Pos() <= rng.End
		}
		switch n := n.(type) {
		case *ast.AssignStmt:
			for _, assignment := range n.Lhs {
				lhs, ok := assignment.(*ast.Ident)
				if !ok {
					continue
				}
				obj, _ := id(lhs)
				if obj == nil {
					continue
				}
				if _, ok := seen[obj]; !ok {
					continue
				}
				seen[obj].assigned = true
				if n.Tok != token.DEFINE {
					continue
				}
				if firstUseIn[obj] != lhs.Pos() {
					continue
				}
				for _, expr := range n.Rhs {
					if referencesObj(info, expr, obj) {
						continue
					}
					if _, ok := seen[obj]; !ok {
						continue
					}
					seen[obj].defined = true
					break
				}
			}
			return false
		case *ast.DeclStmt:
			gen, ok := n.Decl.(*ast.GenDecl)
			if !ok {
				return false
			}
			for _, spec := range gen.Specs {
				vSpecs, ok := spec.(*ast.ValueSpec)
				if !ok {
					continue
				}
				for _, vSpec := range vSpecs.Names {
					obj, _ := id(vSpec)
					if obj == nil {
						continue
					}
					if _, ok := seen[obj]; !ok {
						continue
					}
					seen[obj].assigned = true
				}
			}
			return false
		case *ast.IncDecStmt:
			if ident, ok := n.X.(*ast.Ident); !ok {
				return false
			} else if obj, _ := id(ident); obj == nil {
				return false
			} else {
				if _, ok := seen[obj]; !ok {
					return false
				}
				seen[obj].assigned = true
			}
		}
		return true
	})
	var variables []*variable
	for _, obj := range vars {
		v, ok := seen[obj]
		if !ok {
			return nil, fmt.Errorf("no seen types.Object for %v", obj)
		}
		variables = append(variables, v)
	}
	return variables, nil
}