migrate.go 5.3 KB

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