1
2
3
4
5
6
7
8
9
10
11 package godoc
12
13 import (
14 "fmt"
15 "go/ast"
16 "go/doc"
17 "go/token"
18 "io"
19 "strconv"
20 )
21
22
23
24
25
26
27 func LinkifyText(w io.Writer, text []byte, n ast.Node) {
28 links := linksFor(n)
29
30 i := 0
31 prev := ""
32 linkWriter := func(w io.Writer, _ int, start bool) {
33
34 if !start {
35 if prev != "" {
36 fmt.Fprintf(w, `</%s>`, prev)
37 prev = ""
38 }
39 return
40 }
41
42
43 prev = ""
44 if i < len(links) {
45 switch info := links[i]; {
46 case info.path != "" && info.name == "":
47
48 fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path)
49 prev = "a"
50 case info.path != "" && info.name != "":
51
52 fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.name)
53 prev = "a"
54 case info.path == "" && info.name != "":
55
56 if info.isVal {
57 fmt.Fprintf(w, `<span id="%s">`, info.name)
58 prev = "span"
59 } else if ast.IsExported(info.name) {
60 fmt.Fprintf(w, `<a href="#%s">`, info.name)
61 prev = "a"
62 }
63 }
64 i++
65 }
66 }
67
68 idents := tokenSelection(text, token.IDENT)
69 comments := tokenSelection(text, token.COMMENT)
70 FormatSelections(w, text, linkWriter, idents, selectionTag, comments)
71 }
72
73
74
75 type link struct {
76 path, name string
77 isVal bool
78 }
79
80
81
82 func linksFor(node ast.Node) (links []link) {
83
84
85
86
87 linkMap := make(map[*ast.Ident]link)
88
89 typeParams := make(map[string]bool)
90
91 ast.Inspect(node, func(node ast.Node) bool {
92 switch n := node.(type) {
93 case *ast.Field:
94 for _, n := range n.Names {
95 linkMap[n] = link{}
96 }
97 case *ast.ImportSpec:
98 if name := n.Name; name != nil {
99 linkMap[name] = link{}
100 }
101 case *ast.ValueSpec:
102 for _, n := range n.Names {
103 linkMap[n] = link{name: n.Name, isVal: true}
104 }
105 case *ast.FuncDecl:
106 linkMap[n.Name] = link{}
107 if n.Recv != nil {
108 recv := n.Recv.List[0].Type
109 if r, isstar := recv.(*ast.StarExpr); isstar {
110 recv = r.X
111 }
112 switch x := recv.(type) {
113 case *ast.IndexExpr:
114 if ident, _ := x.Index.(*ast.Ident); ident != nil {
115 typeParams[ident.Name] = true
116 }
117 case *ast.IndexListExpr:
118 for _, index := range x.Indices {
119 if ident, _ := index.(*ast.Ident); ident != nil {
120 typeParams[ident.Name] = true
121 }
122 }
123 }
124 }
125 case *ast.TypeSpec:
126 linkMap[n.Name] = link{}
127 case *ast.AssignStmt:
128
129
130
131 if n.Tok == token.DEFINE {
132
133
134
135 for _, x := range n.Lhs {
136
137
138 if n, _ := x.(*ast.Ident); n != nil {
139 linkMap[n] = link{isVal: true}
140 }
141 }
142 }
143 case *ast.SelectorExpr:
144
145
146
147 if x, _ := n.X.(*ast.Ident); x != nil {
148
149 if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
150 if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
151
152 if path, err := strconv.Unquote(spec.Path.Value); err == nil {
153
154
155 linkMap[x] = link{path: path}
156 linkMap[n.Sel] = link{path: path, name: n.Sel.Name}
157 }
158 }
159 }
160 }
161 case *ast.CompositeLit:
162
163
164 fieldPath := ""
165 prefix := ""
166 switch typ := n.Type.(type) {
167 case *ast.Ident:
168 prefix = typ.Name + "."
169 case *ast.SelectorExpr:
170 if x, _ := typ.X.(*ast.Ident); x != nil {
171
172 if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
173 if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
174
175 if path, err := strconv.Unquote(spec.Path.Value); err == nil {
176
177
178 linkMap[x] = link{path: path}
179 linkMap[typ.Sel] = link{path: path, name: typ.Sel.Name}
180 fieldPath = path
181 prefix = typ.Sel.Name + "."
182 }
183 }
184 }
185 }
186 }
187 for _, e := range n.Elts {
188 if kv, ok := e.(*ast.KeyValueExpr); ok {
189 if k, ok := kv.Key.(*ast.Ident); ok {
190
191
192
193 name := prefix + k.Name
194 linkMap[k] = link{path: fieldPath, name: name}
195 }
196 }
197 }
198 case *ast.Ident:
199 if l, ok := linkMap[n]; ok {
200 links = append(links, l)
201 } else {
202 l := link{name: n.Name}
203 if n.Obj == nil {
204 if doc.IsPredeclared(n.Name) {
205 l.path = builtinPkgPath
206 } else {
207 if typeParams[n.Name] {
208
209
210
211
212 l = link{}
213 }
214 }
215 } else {
216 if n.Obj.Kind == ast.Typ {
217 if _, isfield := n.Obj.Decl.(*ast.Field); isfield {
218
219
220 l = link{}
221 }
222 }
223 }
224 links = append(links, l)
225 }
226 }
227 return true
228 })
229 return
230 }
231
View as plain text