migrate.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. package migrate
  2. import (
  3. "bytes"
  4. "fmt"
  5. "go/ast"
  6. "go/format"
  7. "go/parser"
  8. "go/token"
  9. "io/fs"
  10. "io/ioutil"
  11. "os"
  12. "path/filepath"
  13. "runtime"
  14. "strings"
  15. "time"
  16. "github.com/logrusorgru/aurora"
  17. "github.com/urfave/cli"
  18. "github.com/zeromicro/go-zero/tools/goctl/util/console"
  19. "github.com/zeromicro/go-zero/tools/goctl/util/ctx"
  20. "github.com/zeromicro/go-zero/tools/goctl/vars"
  21. )
  22. const zeromicroVersion = "v1.3.0"
  23. const (
  24. confirmUnknown = iota
  25. confirmAll
  26. confirmIgnore
  27. )
  28. var (
  29. fset = token.NewFileSet()
  30. builderxConfirm = confirmUnknown
  31. )
  32. func Migrate(c *cli.Context) error {
  33. verbose := c.Bool("verbose")
  34. version := c.String("version")
  35. if len(version) == 0 {
  36. version = zeromicroVersion
  37. }
  38. err := editMod(version, verbose)
  39. if err != nil {
  40. return err
  41. }
  42. err = rewriteImport(verbose)
  43. if err != nil {
  44. return err
  45. }
  46. err = tidy(verbose)
  47. if err != nil {
  48. return err
  49. }
  50. if verbose {
  51. console.Success("[OK] refactor finish, execute %q on project root to check status.",
  52. "go test -race ./...")
  53. }
  54. return nil
  55. }
  56. func rewriteImport(verbose bool) error {
  57. if verbose {
  58. console.Info("preparing to rewrite import ...")
  59. time.Sleep(200 * time.Millisecond)
  60. }
  61. cancelOnSignals()
  62. wd, err := os.Getwd()
  63. if err != nil {
  64. return err
  65. }
  66. project, err := ctx.Prepare(wd)
  67. if err != nil {
  68. return err
  69. }
  70. root := project.Dir
  71. fsys := os.DirFS(root)
  72. var final []*ast.Package
  73. err = fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
  74. if !d.IsDir() {
  75. return nil
  76. }
  77. if verbose {
  78. console.Info("walking to %q", path)
  79. }
  80. pkgs, err := parser.ParseDir(fset, path, func(info fs.FileInfo) bool {
  81. return strings.HasSuffix(info.Name(), ".go")
  82. }, parser.ParseComments)
  83. if err != nil {
  84. return err
  85. }
  86. err = rewriteFile(pkgs, verbose)
  87. if err != nil {
  88. return err
  89. }
  90. for _, v := range pkgs {
  91. final = append(final, v)
  92. }
  93. return nil
  94. })
  95. if err != nil {
  96. return err
  97. }
  98. if verbose {
  99. console.Info("start to write files ... ")
  100. }
  101. return writeFile(final, verbose)
  102. }
  103. func rewriteFile(pkgs map[string]*ast.Package, verbose bool) error {
  104. for _, pkg := range pkgs {
  105. for filename, file := range pkg.Files {
  106. var containsDeprecatedBuilderxPkg bool
  107. for _, imp := range file.Imports {
  108. if !strings.Contains(imp.Path.Value, deprecatedGoZeroMod) {
  109. continue
  110. }
  111. if verbose {
  112. console.Debug("[...] migrating %q ... ", filepath.Base(filename))
  113. }
  114. if strings.Contains(imp.Path.Value, deprecatedBuilderx) {
  115. containsDeprecatedBuilderxPkg = true
  116. var doNext bool
  117. refactorBuilderx(deprecatedBuilderx, replacementBuilderx, func(allow bool) {
  118. doNext = !allow
  119. if allow {
  120. newPath := strings.ReplaceAll(imp.Path.Value, deprecatedBuilderx, replacementBuilderx)
  121. imp.EndPos = imp.End()
  122. imp.Path.Value = newPath
  123. }
  124. })
  125. if !doNext {
  126. continue
  127. }
  128. }
  129. newPath := strings.ReplaceAll(imp.Path.Value, deprecatedGoZeroMod, goZeroMod)
  130. imp.EndPos = imp.End()
  131. imp.Path.Value = newPath
  132. }
  133. if containsDeprecatedBuilderxPkg {
  134. replacePkg(file)
  135. }
  136. }
  137. }
  138. return nil
  139. }
  140. func writeFile(pkgs []*ast.Package, verbose bool) error {
  141. for _, pkg := range pkgs {
  142. for filename, file := range pkg.Files {
  143. w := bytes.NewBuffer(nil)
  144. err := format.Node(w, fset, file)
  145. if err != nil {
  146. return fmt.Errorf("[rewriteImport] format file %s error: %+v", filename, err)
  147. }
  148. err = ioutil.WriteFile(filename, w.Bytes(), os.ModePerm)
  149. if err != nil {
  150. return fmt.Errorf("[rewriteImport] write file %s error: %+v", filename, err)
  151. }
  152. if verbose {
  153. console.Success("[OK] migrated %q successfully", filepath.Base(filename))
  154. }
  155. }
  156. }
  157. return nil
  158. }
  159. func replacePkg(file *ast.File) {
  160. scope := file.Scope
  161. if scope == nil {
  162. return
  163. }
  164. obj := scope.Objects
  165. for _, v := range obj {
  166. decl := v.Decl
  167. if decl == nil {
  168. continue
  169. }
  170. vs, ok := decl.(*ast.ValueSpec)
  171. if !ok {
  172. continue
  173. }
  174. values := vs.Values
  175. if len(values) != 1 {
  176. continue
  177. }
  178. value := values[0]
  179. callExpr, ok := value.(*ast.CallExpr)
  180. if !ok {
  181. continue
  182. }
  183. fn := callExpr.Fun
  184. if fn == nil {
  185. continue
  186. }
  187. selector, ok := fn.(*ast.SelectorExpr)
  188. if !ok {
  189. continue
  190. }
  191. x := selector.X
  192. sel := selector.Sel
  193. if x == nil || sel == nil {
  194. continue
  195. }
  196. ident, ok := x.(*ast.Ident)
  197. if !ok {
  198. continue
  199. }
  200. if ident.Name == "builderx" {
  201. ident.Name = "builder"
  202. ident.NamePos = ident.End()
  203. }
  204. if sel.Name == "FieldNames" {
  205. sel.Name = "RawFieldNames"
  206. sel.NamePos = sel.End()
  207. }
  208. }
  209. }
  210. func refactorBuilderx(deprecated, replacement string, fn func(allow bool)) {
  211. switch builderxConfirm {
  212. case confirmAll:
  213. fn(true)
  214. return
  215. case confirmIgnore:
  216. fn(false)
  217. return
  218. }
  219. msg := fmt.Sprintf(`Detects a deprecated package in the source code,
  220. Deprecated package: %q
  221. Replacement package: %q
  222. It's recommended to use the replacement package, do you want to replace?
  223. ['Y' for yes, 'N' for no, 'A' for all, 'I' for ignore]: `,
  224. deprecated, replacement)
  225. if runtime.GOOS != vars.OsWindows {
  226. msg = aurora.Yellow(msg).String()
  227. }
  228. fmt.Print(msg)
  229. for {
  230. var in string
  231. fmt.Scanln(&in)
  232. switch {
  233. case strings.EqualFold(in, "Y"):
  234. fn(true)
  235. return
  236. case strings.EqualFold(in, "N"):
  237. fn(false)
  238. return
  239. case strings.EqualFold(in, "A"):
  240. fn(true)
  241. builderxConfirm = confirmAll
  242. return
  243. case strings.EqualFold(in, "I"):
  244. fn(false)
  245. builderxConfirm = confirmIgnore
  246. return
  247. default:
  248. console.Warning("['Y' for yes, 'N' for no, 'A' for all, 'I' for ignore]: ")
  249. }
  250. }
  251. }