format.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package format
  2. import (
  3. "bufio"
  4. "errors"
  5. "fmt"
  6. "go/format"
  7. "go/scanner"
  8. "io/ioutil"
  9. "os"
  10. "path/filepath"
  11. "strings"
  12. "github.com/tal-tech/go-zero/core/errorx"
  13. "github.com/tal-tech/go-zero/tools/goctl/api/parser"
  14. "github.com/tal-tech/go-zero/tools/goctl/api/util"
  15. ctlutil "github.com/tal-tech/go-zero/tools/goctl/util"
  16. "github.com/urfave/cli"
  17. )
  18. const (
  19. leftParenthesis = "("
  20. rightParenthesis = ")"
  21. leftBrace = "{"
  22. rightBrace = "}"
  23. )
  24. func GoFormatApi(c *cli.Context) error {
  25. useStdin := c.Bool("stdin")
  26. var be errorx.BatchError
  27. if useStdin {
  28. if err := ApiFormatByStdin(); err != nil {
  29. be.Add(err)
  30. }
  31. } else {
  32. dir := c.String("dir")
  33. if len(dir) == 0 {
  34. return errors.New("missing -dir")
  35. }
  36. _, err := os.Lstat(dir)
  37. if err != nil {
  38. return errors.New(dir + ": No such file or directory")
  39. }
  40. err = filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
  41. if strings.HasSuffix(path, ".api") {
  42. if err := ApiFormatByPath(path); err != nil {
  43. be.Add(util.WrapErr(err, fi.Name()))
  44. }
  45. }
  46. return nil
  47. })
  48. be.Add(err)
  49. }
  50. if be.NotNil() {
  51. scanner.PrintError(os.Stderr, be.Err())
  52. os.Exit(1)
  53. }
  54. return be.Err()
  55. }
  56. func ApiFormatByStdin() error {
  57. data, err := ioutil.ReadAll(os.Stdin)
  58. if err != nil {
  59. return err
  60. }
  61. result, err := apiFormat(string(data))
  62. if err != nil {
  63. return err
  64. }
  65. _, err = fmt.Print(result)
  66. return err
  67. }
  68. func ApiFormatByPath(apiFilePath string) error {
  69. data, err := ioutil.ReadFile(apiFilePath)
  70. if err != nil {
  71. return err
  72. }
  73. result, err := apiFormat(string(data))
  74. if err != nil {
  75. return err
  76. }
  77. _, err = parser.ParseContent(result)
  78. if err != nil {
  79. return err
  80. }
  81. return ioutil.WriteFile(apiFilePath, []byte(result), os.ModePerm)
  82. }
  83. func apiFormat(data string) (string, error) {
  84. _, err := parser.ParseContent(data)
  85. if err != nil {
  86. return "", err
  87. }
  88. var builder strings.Builder
  89. s := bufio.NewScanner(strings.NewReader(data))
  90. var tapCount = 0
  91. var newLineCount = 0
  92. var preLine string
  93. for s.Scan() {
  94. line := strings.TrimSpace(s.Text())
  95. if len(line) == 0 {
  96. if newLineCount > 0 {
  97. continue
  98. }
  99. newLineCount++
  100. } else {
  101. if preLine == rightBrace {
  102. builder.WriteString(ctlutil.NL)
  103. }
  104. newLineCount = 0
  105. }
  106. if tapCount == 0 {
  107. format, err := formatGoTypeDef(line, s, &builder)
  108. if err != nil {
  109. return "", err
  110. }
  111. if format {
  112. continue
  113. }
  114. }
  115. noCommentLine := util.RemoveComment(line)
  116. if noCommentLine == rightParenthesis || noCommentLine == rightBrace {
  117. tapCount -= 1
  118. }
  119. if tapCount < 0 {
  120. line := strings.TrimSuffix(noCommentLine, rightBrace)
  121. line = strings.TrimSpace(line)
  122. if strings.HasSuffix(line, leftBrace) {
  123. tapCount += 1
  124. }
  125. }
  126. util.WriteIndent(&builder, tapCount)
  127. builder.WriteString(line + ctlutil.NL)
  128. if strings.HasSuffix(noCommentLine, leftParenthesis) || strings.HasSuffix(noCommentLine, leftBrace) {
  129. tapCount += 1
  130. }
  131. preLine = line
  132. }
  133. return strings.TrimSpace(builder.String()), nil
  134. }
  135. func formatGoTypeDef(line string, scanner *bufio.Scanner, builder *strings.Builder) (bool, error) {
  136. noCommentLine := util.RemoveComment(line)
  137. tokenCount := 0
  138. if strings.HasPrefix(noCommentLine, "type") && (strings.HasSuffix(noCommentLine, leftParenthesis) ||
  139. strings.HasSuffix(noCommentLine, leftBrace)) {
  140. var typeBuilder strings.Builder
  141. typeBuilder.WriteString(mayInsertStructKeyword(line, &tokenCount) + ctlutil.NL)
  142. for scanner.Scan() {
  143. noCommentLine := util.RemoveComment(scanner.Text())
  144. typeBuilder.WriteString(mayInsertStructKeyword(scanner.Text(), &tokenCount) + ctlutil.NL)
  145. if noCommentLine == rightBrace || noCommentLine == rightParenthesis {
  146. tokenCount--
  147. }
  148. if tokenCount == 0 {
  149. ts, err := format.Source([]byte(typeBuilder.String()))
  150. if err != nil {
  151. return false, errors.New("error format \n" + typeBuilder.String())
  152. }
  153. result := strings.ReplaceAll(string(ts), " struct ", " ")
  154. result = strings.ReplaceAll(result, "type ()", "")
  155. builder.WriteString(result)
  156. break
  157. }
  158. }
  159. return true, nil
  160. }
  161. return false, nil
  162. }
  163. func mayInsertStructKeyword(line string, token *int) string {
  164. insertStruct := func() string {
  165. if strings.Contains(line, " struct") {
  166. return line
  167. }
  168. index := strings.Index(line, leftBrace)
  169. return line[:index] + " struct " + line[index:]
  170. }
  171. noCommentLine := util.RemoveComment(line)
  172. if strings.HasSuffix(noCommentLine, leftBrace) {
  173. *token++
  174. return insertStruct()
  175. }
  176. if strings.HasSuffix(noCommentLine, rightBrace) {
  177. noCommentLine = strings.TrimSuffix(noCommentLine, rightBrace)
  178. noCommentLine = util.RemoveComment(noCommentLine)
  179. if strings.HasSuffix(noCommentLine, leftBrace) {
  180. return insertStruct()
  181. }
  182. }
  183. if strings.HasSuffix(noCommentLine, leftParenthesis) {
  184. *token++
  185. }
  186. if strings.Contains(noCommentLine, "`") {
  187. return util.UpperFirst(strings.TrimSpace(line))
  188. }
  189. return line
  190. }