func extractMultipleStatementsAsFunc( astFile *ast.File, fileSet *token.FileSet, stmtsToExtract []ast.Node, parentNode ast.Node, extractedFuncName string) { params := varIdentsUsedIn(stmtsToExtract) varsDeclaredWithinStmtsToExtract := varIdentsDeclaredWithin(stmtsToExtract) util.MapStringAstIdentRemoveKeys(params, namesOf(varsDeclaredWithinStmtsToExtract)) util.MapStringAstIdentRemoveKeys(params, namesOf(globalVarIdents(astFile))) allStmts := stmtsFromBlockStmt(parentNode) indexOfExtractedStmt := indexOf(stmtsToExtract[0].(ast.Stmt), *allStmts) varsUsedAfterwards := overlappingVarsIdentsUsedIn((*allStmts)[indexOfExtractedStmt+len(stmtsToExtract):], varsDeclaredWithinStmtsToExtract) newStmt := funcCallStmt(varsUsedAfterwards, extractedFuncName, params, (*allStmts)[indexOfExtractedStmt].Pos()) replaceStmtsWithFuncCallStmt(newStmt, allStmts, indexOfExtractedStmt, len(stmtsToExtract)) areaRemoved := areaRemoved(fileSet, (stmtsToExtract)[0].Pos(), (stmtsToExtract)[len(stmtsToExtract)-1].End()) lineLengths := lineLengthsFrom(fileSet) lineNum, numLinesToCut, newLineLength := replacementModifications(fileSet, (stmtsToExtract)[0].Pos(), (stmtsToExtract)[len(stmtsToExtract)-1].End(), newStmt.End(), lineLengths, areaRemoved) shiftPosesAfterPos(astFile, newStmt, (stmtsToExtract)[len(stmtsToExtract)-1].End(), newStmt.End()-stmtsToExtract[len(stmtsToExtract)-1].End()) multipleStmtFuncDecl := CopyNode(multipleStmtFuncDeclWith( extractedFuncName, fieldsFrom(params), stmtsFromNodes(stmtsToExtract), exprsFrom(varsUsedAfterwards), )).(*ast.FuncDecl) var moveOffset token.Pos RecalcPoses(multipleStmtFuncDecl, astFile.End()+2, &moveOffset, 0) astFile.Decls = append(astFile.Decls, multipleStmtFuncDecl) areaToBeAppended := insertionModificationsForStmts(astFile, multipleStmtFuncDecl, areaRemoved, exprsFrom(varsUsedAfterwards)) lineLengths = append( lineLengths[:lineNum+1], lineLengths[lineNum+1+numLinesToCut:]...) lineLengths[lineNum] = newLineLength lineLengths = append(lineLengths, areaToBeAppended...) newFileSet := token.NewFileSet() newFileSet.AddFile(fileSet.File(1).Name(), 1, int(astFile.End())) newFileSet.File(1).SetLines(ConvertLineLengthsToLineOffsets(lineLengths)) *fileSet = *newFileSet moveComments(astFile, moveOffset /*, needs a range to restict which comments to move*/) }
// PathEnclosingInterval returns the node that encloses the source // interval [start, end), and all its ancestors up to the AST root. // // The definition of "enclosing" used by this function considers // additional whitespace abutting a node to be enclosed by it. // In this example: // // z := x + y // add them // <-A-> // <----B-----> // // the ast.BinaryExpr(+) node is considered to enclose interval B // even though its [Pos()..End()) is actually only interval A. // This behaviour makes user interfaces more tolerant of imperfect // input. // // This function treats tokens as nodes, though they are not included // in the result. e.g. PathEnclosingInterval("+") returns the // enclosing ast.BinaryExpr("x + y"). // // If start==end, the 1-char interval following start is used instead. // // The 'exact' result is true if the interval contains only path[0] // and perhaps some adjacent whitespace. It is false if the interval // overlaps multiple children of path[0], or if it contains only // interior whitespace of path[0]. // In this example: // // z := x + y // add them // <--C--> <---E--> // ^ // D // // intervals C, D and E are inexact. C is contained by the // z-assignment statement, because it spans three of its children (:=, // x, +). So too is the 1-char interval D, because it contains only // interior whitespace of the assignment. E is considered interior // whitespace of the BlockStmt containing the assignment. // // Precondition: [start, end) both lie within the same file as root. // TODO(adonovan): return (nil, false) in this case and remove precond. // Requires FileSet; see tokenFileContainsPos. // // Postcondition: path is never nil; it always contains at least 'root'. // func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) { // fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging // Precondition: node.[Pos..End) and adjoining whitespace contain [start, end). var visit func(node ast.Node) bool visit = func(node ast.Node) bool { path = append(path, node) nodePos := node.Pos() nodeEnd := node.End() // fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging // Intersect [start, end) with interval of node. if start < nodePos { start = nodePos } if end > nodeEnd { end = nodeEnd } // Find sole child that contains [start, end). children := childrenOf(node) l := len(children) for i, child := range children { // [childPos, childEnd) is unaugmented interval of child. childPos := child.Pos() childEnd := child.End() // [augPos, augEnd) is whitespace-augmented interval of child. augPos := childPos augEnd := childEnd if i > 0 { augPos = children[i-1].End() // start of preceding whitespace } if i < l-1 { nextChildPos := children[i+1].Pos() // Does [start, end) lie between child and next child? if start >= augEnd && end <= nextChildPos { return false // inexact match } augEnd = nextChildPos // end of following whitespace } // fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n", // i, augPos, augEnd, start, end) // debugging // Does augmented child strictly contain [start, end)? if augPos <= start && end <= augEnd { _, isToken := child.(tokenNode) return isToken || visit(child) } // Does [start, end) overlap multiple children? // i.e. left-augmented child contains start // but LR-augmented child does not contain end. if start < childEnd && end > augEnd { break } } // No single child contained [start, end), // so node is the result. Is it exact? // (It's tempting to put this condition before the // child loop, but it gives the wrong result in the // case where a node (e.g. ExprStmt) and its sole // child have equal intervals.) if start == nodePos && end == nodeEnd { return true // exact match } return false // inexact: overlaps multiple children } if start > end { start, end = end, start } if start < root.End() && end > root.Pos() { if start == end { end = start + 1 // empty interval => interval of size 1 } exact = visit(root) // Reverse the path: for i, l := 0, len(path); i < l/2; i++ { path[i], path[l-1-i] = path[l-1-i], path[i] } } else { // Selection lies within whitespace preceding the // first (or following the last) declaration in the file. // The result nonetheless always includes the ast.File. path = append(path, root) } return }
func afterImports(file *ast.File) token.Pos { return file.End() }
func extractExpressionAsFunc( astFile *ast.File, fileSet *token.FileSet, expr ast.Expr, parent ast.Node, extractedFuncName string) { params := varIdentsUsedIn([]ast.Node{expr}) util.MapStringAstIdentRemoveKeys(params, namesOf(globalVarIdents(astFile))) newExpr := CopyNode(callExprWith(extractedFuncName, params)).(ast.Expr) RecalcPoses(newExpr, expr.Pos(), nil, 0) switch typedNode := parent.(type) { case *ast.AssignStmt: for i, rhs := range typedNode.Rhs { if rhs == expr { typedNode.Rhs[i] = newExpr } } for i, lhs := range typedNode.Lhs { if lhs == expr { typedNode.Lhs[i] = newExpr } } case *ast.CallExpr: for i, arg := range typedNode.Args { if arg == expr { typedNode.Args[i] = newExpr } } case *ast.ExprStmt: typedNode.X = newExpr case *ast.ReturnStmt: for i, result := range typedNode.Results { if result == expr { typedNode.Results[i] = newExpr } } case *ast.IfStmt: if typedNode.Cond == expr { typedNode.Cond = newExpr } case *ast.CaseClause: for i, caseExpr := range typedNode.List { if caseExpr == expr { typedNode.List[i] = newExpr } } case *ast.SwitchStmt: if typedNode.Tag == expr { typedNode.Tag = newExpr } case *ast.ForStmt: if typedNode.Cond == expr { typedNode.Cond = newExpr } case *ast.RangeStmt: if typedNode.Key == expr { typedNode.Key = newExpr } else if typedNode.Value == expr { typedNode.Value = newExpr } else if typedNode.X == expr { typedNode.X = newExpr } case *ast.SendStmt: if typedNode.Chan == expr { typedNode.Chan = newExpr } else if typedNode.Value == expr { typedNode.Value = newExpr } case *ast.IncDecStmt: if typedNode.X == expr { typedNode.X = newExpr } case *ast.ValueSpec: for i, value := range typedNode.Values { if value == expr { typedNode.Values[i] = newExpr } } default: panic(fmt.Sprintf("Type %v not supported yet", reflect.TypeOf(parent))) } areaRemoved := areaRemoved(fileSet, expr.Pos(), expr.End()) lineLengths := lineLengthsFrom(fileSet) lineNum, numLinesToCut, newLineLength := replacementModifications(fileSet, expr.Pos(), expr.End(), newExpr.End(), lineLengths, areaRemoved) shiftPosesAfterPos(astFile, newExpr, expr.End(), newExpr.End()-expr.End()) singleExprStmtFuncDeclWith := CopyNode(singleExprStmtFuncDeclWith(extractedFuncName, fieldsFrom(params), expr)).(*ast.FuncDecl) var moveOffset token.Pos RecalcPoses(singleExprStmtFuncDeclWith, astFile.End()+2, &moveOffset, 0) astFile.Decls = append(astFile.Decls, singleExprStmtFuncDeclWith) areaToBeAppended := insertionModifications(astFile, singleExprStmtFuncDeclWith, areaRemoved) lineLengths = append( lineLengths[:lineNum+1], lineLengths[lineNum+1+numLinesToCut:]...) lineLengths[lineNum] = newLineLength lineLengths = append(lineLengths, areaToBeAppended...) newFileSet := token.NewFileSet() newFileSet.AddFile(fileSet.File(1).Name(), 1, int(astFile.End())) success := newFileSet.File(1).SetLines(ConvertLineLengthsToLineOffsets(lineLengths)) if !success { panic("Could not SetLines on File.") } *fileSet = *newFileSet moveComments(astFile, moveOffset /*, needs a range to restict which comments to move*/) }