blob: f3104401c9bf8819ae2c25623e644024a39b0815 [file] [log] [blame]
// 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.
for _, imp := range f.Imports {
pth, _ := strconv.Unquote(imp.Path.Value)
if pth == "appengine" || strings.HasPrefix(pth, "appengine/") {
imp.Path.Value = strconv.Quote(mapPackage(pth))
fixed = true
}
}
// 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".
//
// TODO(dsymonds): Drop the "appengine" import if it is now orphaned.
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
}
})
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...)
}