ae.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. // Copyright 2016 Google Inc. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. package main
  5. import (
  6. "go/ast"
  7. "path"
  8. "strconv"
  9. "strings"
  10. )
  11. const (
  12. ctxPackage = "golang.org/x/net/context"
  13. newPackageBase = "google.golang.org/"
  14. stutterPackage = false
  15. )
  16. func init() {
  17. register(fix{
  18. "ae",
  19. "2016-04-15",
  20. aeFn,
  21. `Update old App Engine APIs to new App Engine APIs`,
  22. })
  23. }
  24. // logMethod is the set of methods on appengine.Context used for logging.
  25. var logMethod = map[string]bool{
  26. "Debugf": true,
  27. "Infof": true,
  28. "Warningf": true,
  29. "Errorf": true,
  30. "Criticalf": true,
  31. }
  32. // mapPackage turns "appengine" into "google.golang.org/appengine", etc.
  33. func mapPackage(s string) string {
  34. if stutterPackage {
  35. s += "/" + path.Base(s)
  36. }
  37. return newPackageBase + s
  38. }
  39. func aeFn(f *ast.File) bool {
  40. // During the walk, we track the last thing seen that looks like
  41. // an appengine.Context, and reset it once the walk leaves a func.
  42. var lastContext *ast.Ident
  43. fixed := false
  44. // Update imports.
  45. mainImp := "appengine"
  46. for _, imp := range f.Imports {
  47. pth, _ := strconv.Unquote(imp.Path.Value)
  48. if pth == "appengine" || strings.HasPrefix(pth, "appengine/") {
  49. newPth := mapPackage(pth)
  50. imp.Path.Value = strconv.Quote(newPth)
  51. fixed = true
  52. if pth == "appengine" {
  53. mainImp = newPth
  54. }
  55. }
  56. }
  57. // Update any API changes.
  58. walk(f, func(n interface{}) {
  59. if ft, ok := n.(*ast.FuncType); ok && ft.Params != nil {
  60. // See if this func has an `appengine.Context arg`.
  61. // If so, remember its identifier.
  62. for _, param := range ft.Params.List {
  63. if !isPkgDot(param.Type, "appengine", "Context") {
  64. continue
  65. }
  66. if len(param.Names) == 1 {
  67. lastContext = param.Names[0]
  68. break
  69. }
  70. }
  71. return
  72. }
  73. if as, ok := n.(*ast.AssignStmt); ok {
  74. if len(as.Lhs) == 1 && len(as.Rhs) == 1 {
  75. // If this node is an assignment from an appengine.NewContext invocation,
  76. // remember the identifier on the LHS.
  77. if isCall(as.Rhs[0], "appengine", "NewContext") {
  78. if ident, ok := as.Lhs[0].(*ast.Ident); ok {
  79. lastContext = ident
  80. return
  81. }
  82. }
  83. // x (=|:=) appengine.Timeout(y, z)
  84. // should become
  85. // x, _ (=|:=) context.WithTimeout(y, z)
  86. if isCall(as.Rhs[0], "appengine", "Timeout") {
  87. addImport(f, ctxPackage)
  88. as.Lhs = append(as.Lhs, ast.NewIdent("_"))
  89. // isCall already did the type checking.
  90. sel := as.Rhs[0].(*ast.CallExpr).Fun.(*ast.SelectorExpr)
  91. sel.X = ast.NewIdent("context")
  92. sel.Sel = ast.NewIdent("WithTimeout")
  93. fixed = true
  94. return
  95. }
  96. }
  97. return
  98. }
  99. // If this node is a FuncDecl, we've finished the function, so reset lastContext.
  100. if _, ok := n.(*ast.FuncDecl); ok {
  101. lastContext = nil
  102. return
  103. }
  104. if call, ok := n.(*ast.CallExpr); ok {
  105. if isPkgDot(call.Fun, "appengine", "Datacenter") && len(call.Args) == 0 {
  106. insertContext(f, call, lastContext)
  107. fixed = true
  108. return
  109. }
  110. if isPkgDot(call.Fun, "taskqueue", "QueueStats") && len(call.Args) == 3 {
  111. call.Args = call.Args[:2] // drop last arg
  112. fixed = true
  113. return
  114. }
  115. sel, ok := call.Fun.(*ast.SelectorExpr)
  116. if !ok {
  117. return
  118. }
  119. if lastContext != nil && refersTo(sel.X, lastContext) && logMethod[sel.Sel.Name] {
  120. // c.Errorf(...)
  121. // should become
  122. // log.Errorf(c, ...)
  123. addImport(f, mapPackage("appengine/log"))
  124. sel.X = &ast.Ident{ // ast.NewIdent doesn't preserve the position.
  125. NamePos: sel.X.Pos(),
  126. Name: "log",
  127. }
  128. insertContext(f, call, lastContext)
  129. fixed = true
  130. return
  131. }
  132. }
  133. })
  134. // Change any `appengine.Context` to `context.Context`.
  135. // Do this in a separate walk because the previous walk
  136. // wants to identify "appengine.Context".
  137. walk(f, func(n interface{}) {
  138. expr, ok := n.(ast.Expr)
  139. if ok && isPkgDot(expr, "appengine", "Context") {
  140. addImport(f, ctxPackage)
  141. // isPkgDot did the type checking.
  142. n.(*ast.SelectorExpr).X.(*ast.Ident).Name = "context"
  143. fixed = true
  144. return
  145. }
  146. })
  147. // The changes above might remove the need to import "appengine".
  148. // Check if it's used, and drop it if it isn't.
  149. if fixed && !usesImport(f, mainImp) {
  150. deleteImport(f, mainImp)
  151. }
  152. return fixed
  153. }
  154. // ctx may be nil.
  155. func insertContext(f *ast.File, call *ast.CallExpr, ctx *ast.Ident) {
  156. if ctx == nil {
  157. // context is unknown, so use a plain "ctx".
  158. ctx = ast.NewIdent("ctx")
  159. } else {
  160. // Create a fresh *ast.Ident so we drop the position information.
  161. ctx = ast.NewIdent(ctx.Name)
  162. }
  163. call.Args = append([]ast.Expr{ctx}, call.Args...)
  164. }