// Copyright 2016 Google Inc. All rights reserved. // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. package main import ( "go/ast" "path" "strconv" "strings" ) const ( ctxPackage = "golang.org/x/net/context" newPackageBase = "google.golang.org/" stutterPackage = false ) func init() { register(fix{ "ae", "2016-04-15", aeFn, `Update old App Engine APIs to new App Engine APIs`, }) } // logMethod is the set of methods on appengine.Context used for logging. var logMethod = map[string]bool{ "Debugf": true, "Infof": true, "Warningf": true, "Errorf": true, "Criticalf": true, } // mapPackage turns "appengine" into "google.golang.org/appengine", etc. func mapPackage(s string) string { if stutterPackage { s += "/" + path.Base(s) } return newPackageBase + s } func aeFn(f *ast.File) bool { // During the walk, we track the last thing seen that looks like // an appengine.Context, and reset it once the walk leaves a func. var lastContext *ast.Ident fixed := false // Update imports. mainImp := "appengine" for _, imp := range f.Imports { pth, _ := strconv.Unquote(imp.Path.Value) if pth == "appengine" || strings.HasPrefix(pth, "appengine/") { newPth := mapPackage(pth) imp.Path.Value = strconv.Quote(newPth) fixed = true if pth == "appengine" { mainImp = newPth } } } // Update any API changes. walk(f, func(n interface{}) { if ft, ok := n.(*ast.FuncType); ok && ft.Params != nil { // See if this func has an `appengine.Context arg`. // If so, remember its identifier. for _, param := range ft.Params.List { if !isPkgDot(param.Type, "appengine", "Context") { continue } if len(param.Names) == 1 { lastContext = param.Names[0] break } } return } if as, ok := n.(*ast.AssignStmt); ok { if len(as.Lhs) == 1 && len(as.Rhs) == 1 { // If this node is an assignment from an appengine.NewContext invocation, // remember the identifier on the LHS. if isCall(as.Rhs[0], "appengine", "NewContext") { if ident, ok := as.Lhs[0].(*ast.Ident); ok { lastContext = ident return } } // x (=|:=) appengine.Timeout(y, z) // should become // x, _ (=|:=) context.WithTimeout(y, z) if isCall(as.Rhs[0], "appengine", "Timeout") { addImport(f, ctxPackage) as.Lhs = append(as.Lhs, ast.NewIdent("_")) // isCall already did the type checking. sel := as.Rhs[0].(*ast.CallExpr).Fun.(*ast.SelectorExpr) sel.X = ast.NewIdent("context") sel.Sel = ast.NewIdent("WithTimeout") fixed = true return } } return } // If this node is a FuncDecl, we've finished the function, so reset lastContext. if _, ok := n.(*ast.FuncDecl); ok { lastContext = nil return } if call, ok := n.(*ast.CallExpr); ok { if isPkgDot(call.Fun, "appengine", "Datacenter") && len(call.Args) == 0 { insertContext(f, call, lastContext) fixed = true return } if isPkgDot(call.Fun, "taskqueue", "QueueStats") && len(call.Args) == 3 { call.Args = call.Args[:2] // drop last arg fixed = true return } sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { return } if lastContext != nil && refersTo(sel.X, lastContext) && logMethod[sel.Sel.Name] { // c.Errorf(...) // should become // log.Errorf(c, ...) addImport(f, mapPackage("appengine/log")) sel.X = &ast.Ident{ // ast.NewIdent doesn't preserve the position. NamePos: sel.X.Pos(), Name: "log", } insertContext(f, call, lastContext) fixed = true return } } }) // Change any `appengine.Context` to `context.Context`. // Do this in a separate walk because the previous walk // wants to identify "appengine.Context". walk(f, func(n interface{}) { expr, ok := n.(ast.Expr) if ok && isPkgDot(expr, "appengine", "Context") { addImport(f, ctxPackage) // isPkgDot did the type checking. n.(*ast.SelectorExpr).X.(*ast.Ident).Name = "context" fixed = true return } }) // The changes above might remove the need to import "appengine". // Check if it's used, and drop it if it isn't. if fixed && !usesImport(f, mainImp) { deleteImport(f, mainImp) } return fixed } // ctx may be nil. func insertContext(f *ast.File, call *ast.CallExpr, ctx *ast.Ident) { if ctx == nil { // context is unknown, so use a plain "ctx". ctx = ast.NewIdent("ctx") } else { // Create a fresh *ast.Ident so we drop the position information. ctx = ast.NewIdent(ctx.Name) } call.Args = append([]ast.Expr{ctx}, call.Args...) }