-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathformat.go
200 lines (188 loc) · 4.96 KB
/
format.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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package main
import (
"bytes"
"fmt"
"github.com/JohnWall2016/gogetdef/types"
"go/ast"
"go/printer"
"go/token"
"unicode"
"unicode/utf8"
)
func trimUnexportedElems(spec *ast.TypeSpec) {
switch typ := spec.Type.(type) {
case *ast.StructType:
typ.Fields = trimUnexportedFields(typ.Fields, false)
case *ast.InterfaceType:
typ.Methods = trimUnexportedFields(typ.Methods, true)
}
}
func trimUnexportedFields(fields *ast.FieldList, isInterface bool) *ast.FieldList {
what := "methods"
if !isInterface {
what = "fields"
}
trimmed := false
list := make([]*ast.Field, 0, len(fields.List))
for _, field := range fields.List {
names := field.Names
if len(names) == 0 {
// Embedded type. Use the name of the type. It must be of type ident or *ident.
// Nothing else is allowed.
switch ident := field.Type.(type) {
case *ast.Ident:
if isInterface && ident.Name == "error" && ident.Obj == nil {
// For documentation purposes, we consider the builtin error
// type special when embedded in an interface, such that it
// always gets shown publicly.
list = append(list, field)
continue
}
names = []*ast.Ident{ident}
case *ast.StarExpr:
// Must have the form *identifier.
// This is only valid on embedded types in structs.
if ident, ok := ident.X.(*ast.Ident); ok && !isInterface {
names = []*ast.Ident{ident}
}
case *ast.SelectorExpr:
// An embedded type may refer to a type in another package.
names = []*ast.Ident{ident.Sel}
}
if names == nil {
// Can only happen if AST is incorrect. Safe to continue with a nil list.
//log.Print("invalid program: unexpected type for embedded field")
}
}
// Trims if any is unexported. Good enough in practice.
ok := true
for _, name := range names {
if !isUpper(name.Name) {
trimmed = true
ok = false
break
}
}
if ok {
list = append(list, field)
}
}
if !trimmed {
return fields
}
unexportedField := &ast.Field{
Type: &ast.Ident{
// Hack: printer will treat this as a field with a named type.
// Setting Name and NamePos to ("", fields.Closing-1) ensures that
// when Pos and End are called on this field, they return the
// position right before closing '}' character.
Name: "",
NamePos: fields.Closing - 1,
},
Comment: &ast.CommentGroup{
List: []*ast.Comment{{Text: fmt.Sprintf("// Has unexported %s.\n", what)}},
},
}
return &ast.FieldList{
Opening: fields.Opening,
List: append(list, unexportedField),
Closing: fields.Closing,
}
}
// isUpper reports whether the name starts with an upper case letter.
func isUpper(name string) bool {
ch, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(ch)
}
func formatNode(n ast.Node, obj types.Object, fset *token.FileSet, showUnexported bool) string {
//fmt.Printf("formatting %T node\n", n)
var nc ast.Node
// Render a copy of the node with no documentation.
// We emit the documentation ourself.
switch n := n.(type) {
case *ast.FuncDecl:
cp := *n
cp.Doc = nil
// Don't print the whole function body
cp.Body = nil
nc = &cp
case *ast.Field:
// Not supported by go/printer
// TODO(dominikh): Methods in interfaces are syntactically
// represented as fields. Using types.Object.String for those
// causes them to look different from real functions.
// go/printer doesn't include the import paths in names, while
// Object.String does. Fix that.
return obj.String()
case *ast.TypeSpec:
specCp := *n
if showUnexported == false {
trimUnexportedElems(&specCp)
}
specCp.Doc = nil
typeSpec := ast.GenDecl{
Tok: token.TYPE,
Specs: []ast.Spec{&specCp},
}
nc = &typeSpec
case *ast.GenDecl:
cp := *n
cp.Doc = nil
if len(n.Specs) > 0 {
// Only print this one type, not all the types in the gendecl
switch n.Specs[0].(type) {
case *ast.TypeSpec:
spec := findTypeSpec(n, obj.Pos())
if spec != nil {
specCp := *spec
if showUnexported == false {
trimUnexportedElems(&specCp)
}
specCp.Doc = nil
cp.Specs = []ast.Spec{&specCp}
}
cp.Lparen = 0
cp.Rparen = 0
case *ast.ValueSpec:
spec := findVarSpec(n, obj.Pos())
if spec != nil {
specCp := *spec
specCp.Doc = nil
cp.Specs = []ast.Spec{&specCp}
}
cp.Lparen = 0
cp.Rparen = 0
}
}
nc = &cp
default:
return obj.String()
}
buf := &bytes.Buffer{}
cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
err := cfg.Fprint(buf, fset, nc)
if err != nil {
return obj.String()
}
return buf.String()
}
func findTypeSpec(decl *ast.GenDecl, pos token.Pos) *ast.TypeSpec {
for _, spec := range decl.Specs {
typeSpec := spec.(*ast.TypeSpec)
if typeSpec.Pos() == pos {
return typeSpec
}
}
return nil
}
func findVarSpec(decl *ast.GenDecl, pos token.Pos) *ast.ValueSpec {
for _, spec := range decl.Specs {
varSpec := spec.(*ast.ValueSpec)
for _, ident := range varSpec.Names {
if ident.Pos() == pos {
return varSpec
}
}
}
return nil
}