123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- // 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...)
- }
|