186 lines
4.5 KiB
Go
186 lines
4.5 KiB
Go
|
// 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...)
|
||
|
}
|