migrate.go 5.5 KB

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