1
2
3
4
5
6
7
8
9
10
11
12 package pkgsite
13
14 import (
15 "bytes"
16 "fmt"
17 "go/ast"
18 "go/doc"
19 "go/printer"
20 "go/scanner"
21 "go/token"
22 "strconv"
23 "strings"
24 )
25
26
27
28
29
30
31
32
33
34
35
36
37 func PrintType(fset *token.FileSet, decl ast.Decl, toURL func(string, string) string, topLevelDecls map[interface{}]bool) string {
38 anchorLinksMap := generateAnchorLinks(decl, toURL, topLevelDecls)
39
40
41
42
43 var anchorLinks []string
44 ast.Inspect(decl, func(node ast.Node) bool {
45 if id, ok := node.(*ast.Ident); ok {
46 anchorLinks = append(anchorLinks, anchorLinksMap[id])
47 }
48 return true
49 })
50
51 v := &declVisitor{}
52 ast.Walk(v, decl)
53
54 var b bytes.Buffer
55 p := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 4}
56 p.Fprint(&b, fset, &printer.CommentedNode{Node: decl, Comments: v.Comments})
57 src := b.Bytes()
58 var out strings.Builder
59
60 fakeFset := token.NewFileSet()
61 file := fakeFset.AddFile("", fakeFset.Base(), b.Len())
62
63 var lastOffset int
64 var s scanner.Scanner
65 s.Init(file, src, nil, scanner.ScanComments)
66 identIdx := 0
67 scan:
68 for {
69 p, tok, lit := s.Scan()
70 line := file.Line(p) - 1
71 offset := file.Offset(p)
72
73
74 prevLines := strings.SplitAfter(string(src[lastOffset:offset]), "\n")
75 for i, ln := range prevLines {
76 n := line - len(prevLines) + i + 1
77 if n < 0 {
78 n = 0
79 }
80 out.WriteString(ln)
81 }
82
83 lastOffset = offset
84 switch tok {
85 case token.EOF:
86 break scan
87 case token.IDENT:
88 if identIdx < len(anchorLinks) && anchorLinks[identIdx] != "" {
89 fmt.Fprintf(&out, `<a href="%s">%s</a>`, anchorLinks[identIdx], lit)
90 } else {
91 out.WriteString(lit)
92 }
93 identIdx++
94 lastOffset += len(lit)
95 }
96 }
97 return out.String()
98 }
99
100
101
102
103
104 type declVisitor struct {
105 Comments []*ast.CommentGroup
106 }
107
108
109 func (v *declVisitor) Visit(n ast.Node) ast.Visitor {
110 switch n := n.(type) {
111 case *ast.BasicLit:
112 if n.Kind == token.STRING && len(n.Value) > 128 {
113 v.Comments = append(v.Comments,
114 &ast.CommentGroup{List: []*ast.Comment{{
115 Slash: n.Pos(),
116 Text: stringBasicLitSize(n.Value),
117 }}})
118 n.Value = `""`
119 }
120 case *ast.CompositeLit:
121 if len(n.Elts) > 100 {
122 v.Comments = append(v.Comments,
123 &ast.CommentGroup{List: []*ast.Comment{{
124 Slash: n.Lbrace,
125 Text: fmt.Sprintf("/* %d elements not displayed */", len(n.Elts)),
126 }}})
127 n.Elts = n.Elts[:0]
128 }
129 }
130 return v
131 }
132
133
134
135
136 func stringBasicLitSize(s string) string {
137 if len(s) > 0 && s[0] == '`' {
138
139 s = strings.ReplaceAll(s, "\r", "")
140 }
141 u, err := strconv.Unquote(s)
142 if err != nil {
143 return fmt.Sprintf("/* invalid %d byte string literal not displayed */", len(s))
144 }
145 return fmt.Sprintf("/* %d byte string literal not displayed */", len(u))
146 }
147
148
149
150 func generateAnchorLinks(decl ast.Decl, toURL func(string, string) string, topLevelDecls map[interface{}]bool) map[*ast.Ident]string {
151 m := map[*ast.Ident]string{}
152 ignore := map[ast.Node]bool{}
153 ast.Inspect(decl, func(node ast.Node) bool {
154 if ignore[node] {
155 return false
156 }
157 switch node := node.(type) {
158 case *ast.SelectorExpr:
159
160 if prefix, _ := node.X.(*ast.Ident); prefix != nil {
161 if obj := prefix.Obj; obj != nil && obj.Kind == ast.Pkg {
162 if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
163 if path, err := strconv.Unquote(spec.Path.Value); err == nil {
164
165
166 m[prefix] = toURL(path, "")
167 m[node.Sel] = toURL(path, node.Sel.Name)
168 return false
169 }
170 }
171 }
172 }
173 case *ast.Ident:
174 if node.Obj == nil && doc.IsPredeclared(node.Name) {
175 m[node] = toURL("builtin", node.Name)
176 } else if node.Obj != nil && topLevelDecls[node.Obj.Decl] {
177 m[node] = toURL("", node.Name)
178 }
179 case *ast.FuncDecl:
180 ignore[node.Name] = true
181 case *ast.TypeSpec:
182 ignore[node.Name] = true
183 case *ast.ValueSpec:
184 for _, n := range node.Names {
185 ignore[n] = true
186 }
187 case *ast.AssignStmt:
188 for _, n := range node.Lhs {
189 ignore[n] = true
190 }
191 }
192 return true
193 })
194 return m
195 }
196
197
198 func TopLevelDecls(pkg *doc.Package) map[interface{}]bool {
199 topLevelDecls := map[interface{}]bool{}
200 forEachPackageDecl(pkg, func(decl ast.Decl) {
201 topLevelDecls[decl] = true
202 if gd, _ := decl.(*ast.GenDecl); gd != nil {
203 for _, sp := range gd.Specs {
204 topLevelDecls[sp] = true
205 }
206 }
207 })
208 return topLevelDecls
209 }
210
211
212 func forEachPackageDecl(pkg *doc.Package, fnc func(decl ast.Decl)) {
213 for _, c := range pkg.Consts {
214 fnc(c.Decl)
215 }
216 for _, v := range pkg.Vars {
217 fnc(v.Decl)
218 }
219 for _, f := range pkg.Funcs {
220 fnc(f.Decl)
221 }
222 for _, t := range pkg.Types {
223 fnc(t.Decl)
224 for _, c := range t.Consts {
225 fnc(c.Decl)
226 }
227 for _, v := range t.Vars {
228 fnc(v.Decl)
229 }
230 for _, f := range t.Funcs {
231 fnc(f.Decl)
232 }
233 for _, m := range t.Methods {
234 fnc(m.Decl)
235 }
236 }
237 }
238
View as plain text