forked from swaggo/swag
-
Notifications
You must be signed in to change notification settings - Fork 0
/
formatter.go
132 lines (116 loc) · 3.26 KB
/
formatter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package swag
import (
"bytes"
"crypto/md5"
"fmt"
"go/ast"
goparser "go/parser"
"go/token"
"io"
"log"
"os"
"regexp"
"strings"
"text/tabwriter"
)
const splitTag = "&*"
// Check of @Param @Success @Failure @Response @Header
var specialTagForSplit = map[string]bool{
paramAttr: true,
successAttr: true,
failureAttr: true,
responseAttr: true,
headerAttr: true,
}
var skipChar = map[byte]byte{
'"': '"',
'(': ')',
'{': '}',
'[': ']',
}
// Formatter implements a formatter for Go source files.
type Formatter struct {
// debugging output goes here
debug Debugger
}
// NewFormatter create a new formatter instance.
func NewFormatter() *Formatter {
formatter := &Formatter{
debug: log.New(os.Stdout, "", log.LstdFlags),
}
return formatter
}
// Format formats swag comments in contents. It uses fileName to report errors
// that happen during parsing of contents.
func (f *Formatter) Format(fileName string, contents []byte) ([]byte, error) {
fileSet := token.NewFileSet()
ast, err := goparser.ParseFile(fileSet, fileName, contents, goparser.ParseComments)
if err != nil {
return nil, err
}
formattedComments := bytes.Buffer{}
oldComments := map[string]string{}
if ast.Comments != nil {
for _, comment := range ast.Comments {
formatFuncDoc(comment.List, &formattedComments, oldComments)
}
}
return formatComments(fileName, contents, formattedComments.Bytes(), oldComments), nil
}
func formatComments(fileName string, contents []byte, formattedComments []byte, oldComments map[string]string) []byte {
for _, comment := range bytes.Split(formattedComments, []byte("\n")) {
splits := bytes.SplitN(comment, []byte(splitTag), 2)
if len(splits) == 2 {
hash, line := splits[0], splits[1]
contents = bytes.Replace(contents, []byte(oldComments[string(hash)]), line, 1)
}
}
return contents
}
func formatFuncDoc(commentList []*ast.Comment, formattedComments io.Writer, oldCommentsMap map[string]string) {
w := tabwriter.NewWriter(formattedComments, 0, 0, 2, ' ', 0)
for _, comment := range commentList {
text := comment.Text
if attr, body, found := swagComment(text); found {
cmd5 := fmt.Sprintf("%x", md5.Sum([]byte(text)))
oldCommentsMap[cmd5] = text
formatted := "// " + attr
if body != "" {
formatted += "\t" + splitComment2(attr, body)
}
// md5 + splitTag + srcCommentLine
// eg. xxx&*@Description get struct array
_, _ = fmt.Fprintln(w, cmd5+splitTag+formatted)
}
}
// format by tabwriter
_ = w.Flush()
}
func splitComment2(attr, body string) string {
if specialTagForSplit[strings.ToLower(attr)] {
for i := 0; i < len(body); i++ {
if skipEnd, ok := skipChar[body[i]]; ok {
if skipLen := strings.IndexByte(body[i+1:], skipEnd); skipLen > 0 {
i += skipLen
}
} else if body[i] == ' ' {
j := i
for ; j < len(body) && body[j] == ' '; j++ {
}
body = replaceRange(body, i, j, "\t")
}
}
}
return body
}
func replaceRange(s string, start, end int, new string) string {
return s[:start] + new + s[end:]
}
var swagCommentLineExpression = regexp.MustCompile(`^\/\/\s+(@[\S.]+)\s*(.*)`)
func swagComment(comment string) (string, string, bool) {
matches := swagCommentLineExpression.FindStringSubmatch(comment)
if matches == nil {
return "", "", false
}
return matches[1], matches[2], true
}