1 | // Copyright 2013 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | // This file implements LinkifyText which introduces |
6 | // links for identifiers pointing to their declarations. |
7 | // The approach does not cover all cases because godoc |
8 | // doesn't have complete type information, but it's |
9 | // reasonably good for browsing. |
10 | |
11 | package godoc |
12 | |
13 | import ( |
14 | "fmt" |
15 | "go/ast" |
16 | "go/doc" |
17 | "go/token" |
18 | "io" |
19 | "strconv" |
20 | |
21 | "golang.org/x/tools/internal/typeparams" |
22 | ) |
23 | |
24 | // LinkifyText HTML-escapes source text and writes it to w. |
25 | // Identifiers that are in a "use" position (i.e., that are |
26 | // not being declared), are wrapped with HTML links pointing |
27 | // to the respective declaration, if possible. Comments are |
28 | // formatted the same way as with FormatText. |
29 | func LinkifyText(w io.Writer, text []byte, n ast.Node) { |
30 | links := linksFor(n) |
31 | |
32 | i := 0 // links index |
33 | prev := "" // prev HTML tag |
34 | linkWriter := func(w io.Writer, _ int, start bool) { |
35 | // end tag |
36 | if !start { |
37 | if prev != "" { |
38 | fmt.Fprintf(w, `</%s>`, prev) |
39 | prev = "" |
40 | } |
41 | return |
42 | } |
43 | |
44 | // start tag |
45 | prev = "" |
46 | if i < len(links) { |
47 | switch info := links[i]; { |
48 | case info.path != "" && info.name == "": |
49 | // package path |
50 | fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path) |
51 | prev = "a" |
52 | case info.path != "" && info.name != "": |
53 | // qualified identifier |
54 | fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.name) |
55 | prev = "a" |
56 | case info.path == "" && info.name != "": |
57 | // local identifier |
58 | if info.isVal { |
59 | fmt.Fprintf(w, `<span id="%s">`, info.name) |
60 | prev = "span" |
61 | } else if ast.IsExported(info.name) { |
62 | fmt.Fprintf(w, `<a href="#%s">`, info.name) |
63 | prev = "a" |
64 | } |
65 | } |
66 | i++ |
67 | } |
68 | } |
69 | |
70 | idents := tokenSelection(text, token.IDENT) |
71 | comments := tokenSelection(text, token.COMMENT) |
72 | FormatSelections(w, text, linkWriter, idents, selectionTag, comments) |
73 | } |
74 | |
75 | // A link describes the (HTML) link information for an identifier. |
76 | // The zero value of a link represents "no link". |
77 | type link struct { |
78 | path, name string // package path, identifier name |
79 | isVal bool // identifier is defined in a const or var declaration |
80 | } |
81 | |
82 | // linksFor returns the list of links for the identifiers used |
83 | // by node in the same order as they appear in the source. |
84 | func linksFor(node ast.Node) (links []link) { |
85 | // linkMap tracks link information for each ast.Ident node. Entries may |
86 | // be created out of source order (for example, when we visit a parent |
87 | // definition node). These links are appended to the returned slice when |
88 | // their ast.Ident nodes are visited. |
89 | linkMap := make(map[*ast.Ident]link) |
90 | |
91 | typeParams := make(map[string]bool) |
92 | |
93 | ast.Inspect(node, func(node ast.Node) bool { |
94 | switch n := node.(type) { |
95 | case *ast.Field: |
96 | for _, n := range n.Names { |
97 | linkMap[n] = link{} |
98 | } |
99 | case *ast.ImportSpec: |
100 | if name := n.Name; name != nil { |
101 | linkMap[name] = link{} |
102 | } |
103 | case *ast.ValueSpec: |
104 | for _, n := range n.Names { |
105 | linkMap[n] = link{name: n.Name, isVal: true} |
106 | } |
107 | case *ast.FuncDecl: |
108 | linkMap[n.Name] = link{} |
109 | if n.Recv != nil { |
110 | recv := n.Recv.List[0].Type |
111 | if r, isstar := recv.(*ast.StarExpr); isstar { |
112 | recv = r.X |
113 | } |
114 | switch x := recv.(type) { |
115 | case *ast.IndexExpr: |
116 | if ident, _ := x.Index.(*ast.Ident); ident != nil { |
117 | typeParams[ident.Name] = true |
118 | } |
119 | case *typeparams.IndexListExpr: |
120 | for _, index := range x.Indices { |
121 | if ident, _ := index.(*ast.Ident); ident != nil { |
122 | typeParams[ident.Name] = true |
123 | } |
124 | } |
125 | } |
126 | } |
127 | case *ast.TypeSpec: |
128 | linkMap[n.Name] = link{} |
129 | case *ast.AssignStmt: |
130 | // Short variable declarations only show up if we apply |
131 | // this code to all source code (as opposed to exported |
132 | // declarations only). |
133 | if n.Tok == token.DEFINE { |
134 | // Some of the lhs variables may be re-declared, |
135 | // so technically they are not defs. We don't |
136 | // care for now. |
137 | for _, x := range n.Lhs { |
138 | // Each lhs expression should be an |
139 | // ident, but we are conservative and check. |
140 | if n, _ := x.(*ast.Ident); n != nil { |
141 | linkMap[n] = link{isVal: true} |
142 | } |
143 | } |
144 | } |
145 | case *ast.SelectorExpr: |
146 | // Detect qualified identifiers of the form pkg.ident. |
147 | // If anything fails we return true and collect individual |
148 | // identifiers instead. |
149 | if x, _ := n.X.(*ast.Ident); x != nil { |
150 | // Create links only if x is a qualified identifier. |
151 | if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg { |
152 | if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil { |
153 | // spec.Path.Value is the import path |
154 | if path, err := strconv.Unquote(spec.Path.Value); err == nil { |
155 | // Register two links, one for the package |
156 | // and one for the qualified identifier. |
157 | linkMap[x] = link{path: path} |
158 | linkMap[n.Sel] = link{path: path, name: n.Sel.Name} |
159 | } |
160 | } |
161 | } |
162 | } |
163 | case *ast.CompositeLit: |
164 | // Detect field names within composite literals. These links should |
165 | // be prefixed by the type name. |
166 | fieldPath := "" |
167 | prefix := "" |
168 | switch typ := n.Type.(type) { |
169 | case *ast.Ident: |
170 | prefix = typ.Name + "." |
171 | case *ast.SelectorExpr: |
172 | if x, _ := typ.X.(*ast.Ident); x != nil { |
173 | // Create links only if x is a qualified identifier. |
174 | if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg { |
175 | if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil { |
176 | // spec.Path.Value is the import path |
177 | if path, err := strconv.Unquote(spec.Path.Value); err == nil { |
178 | // Register two links, one for the package |
179 | // and one for the qualified identifier. |
180 | linkMap[x] = link{path: path} |
181 | linkMap[typ.Sel] = link{path: path, name: typ.Sel.Name} |
182 | fieldPath = path |
183 | prefix = typ.Sel.Name + "." |
184 | } |
185 | } |
186 | } |
187 | } |
188 | } |
189 | for _, e := range n.Elts { |
190 | if kv, ok := e.(*ast.KeyValueExpr); ok { |
191 | if k, ok := kv.Key.(*ast.Ident); ok { |
192 | // Note: there is some syntactic ambiguity here. We cannot determine |
193 | // if this is a struct literal or a map literal without type |
194 | // information. We assume struct literal. |
195 | name := prefix + k.Name |
196 | linkMap[k] = link{path: fieldPath, name: name} |
197 | } |
198 | } |
199 | } |
200 | case *ast.Ident: |
201 | if l, ok := linkMap[n]; ok { |
202 | links = append(links, l) |
203 | } else { |
204 | l := link{name: n.Name} |
205 | if n.Obj == nil { |
206 | if doc.IsPredeclared(n.Name) { |
207 | l.path = builtinPkgPath |
208 | } else { |
209 | if typeParams[n.Name] { |
210 | // If a type parameter was declared then do not generate a link. |
211 | // Doing this is necessary because type parameter identifiers do not |
212 | // have their Decl recorded sometimes, see |
213 | // https://golang.org/issue/50956. |
214 | l = link{} |
215 | } |
216 | } |
217 | } else { |
218 | if n.Obj.Kind == ast.Typ { |
219 | if _, isfield := n.Obj.Decl.(*ast.Field); isfield { |
220 | // If an identifier is a type declared in a field assume it is a type |
221 | // parameter and do not generate a link. |
222 | l = link{} |
223 | } |
224 | } |
225 | } |
226 | links = append(links, l) |
227 | } |
228 | } |
229 | return true |
230 | }) |
231 | return |
232 | } |
233 |
Members