blob: cf266d01f1d74a5d42b0448486e8747c716342da [file] [log] [blame]
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +10001// 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
Brad Fitzpatricke6ff53b2013-07-17 17:09:54 +100011package godoc
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100012
13import (
14 "fmt"
15 "go/ast"
Masahiro Furudate4de4a8d2017-08-25 15:00:21 +090016 "go/doc"
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100017 "go/token"
18 "io"
19 "strconv"
aarzillic6fca022022-02-01 18:45:42 +010020
21 "golang.org/x/tools/internal/typeparams"
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100022)
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.
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100029func 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
Masahiro Furudatece129152017-05-07 04:40:42 +090058 if info.isVal {
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100059 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".
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100077type link struct {
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100078 path, name string // package path, identifier name
Masahiro Furudatece129152017-05-07 04:40:42 +090079 isVal bool // identifier is defined in a const or var declaration
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100080}
81
Jay Conrode1bdc762017-02-10 16:20:20 -050082// linksFor returns the list of links for the identifiers used
83// by node in the same order as they appear in the source.
Jay Conrode1bdc762017-02-10 16:20:20 -050084func 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)
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100090
aarzillic6fca022022-02-01 18:45:42 +010091 typeParams := make(map[string]bool)
92
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100093 ast.Inspect(node, func(node ast.Node) bool {
94 switch n := node.(type) {
95 case *ast.Field:
96 for _, n := range n.Names {
Masahiro Furudatece129152017-05-07 04:40:42 +090097 linkMap[n] = link{}
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100098 }
99 case *ast.ImportSpec:
100 if name := n.Name; name != nil {
Masahiro Furudatece129152017-05-07 04:40:42 +0900101 linkMap[name] = link{}
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000102 }
103 case *ast.ValueSpec:
104 for _, n := range n.Names {
Masahiro Furudatece129152017-05-07 04:40:42 +0900105 linkMap[n] = link{name: n.Name, isVal: true}
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000106 }
Masahiro Furudatece129152017-05-07 04:40:42 +0900107 case *ast.FuncDecl:
108 linkMap[n.Name] = link{}
aarzillic6fca022022-02-01 18:45:42 +0100109 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 }
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000127 case *ast.TypeSpec:
Masahiro Furudatece129152017-05-07 04:40:42 +0900128 linkMap[n.Name] = link{}
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000129 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 {
Masahiro Furudatece129152017-05-07 04:40:42 +0900141 linkMap[n] = link{isVal: true}
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000142 }
143 }
144 }
Jay Conrode1bdc762017-02-10 16:20:20 -0500145 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.
Masahiro Furudatece129152017-05-07 04:40:42 +0900157 linkMap[x] = link{path: path}
158 linkMap[n.Sel] = link{path: path, name: n.Sel.Name}
Jay Conrode1bdc762017-02-10 16:20:20 -0500159 }
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.
Masahiro Furudatece129152017-05-07 04:40:42 +0900180 linkMap[x] = link{path: path}
181 linkMap[typ.Sel] = link{path: path, name: typ.Sel.Name}
Jay Conrode1bdc762017-02-10 16:20:20 -0500182 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
Masahiro Furudatece129152017-05-07 04:40:42 +0900196 linkMap[k] = link{path: fieldPath, name: name}
Jay Conrode1bdc762017-02-10 16:20:20 -0500197 }
198 }
199 }
200 case *ast.Ident:
201 if l, ok := linkMap[n]; ok {
202 links = append(links, l)
203 } else {
Masahiro Furudatece129152017-05-07 04:40:42 +0900204 l := link{name: n.Name}
aarzillic6fca022022-02-01 18:45:42 +0100205 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 }
Jay Conrode1bdc762017-02-10 16:20:20 -0500225 }
226 links = append(links, l)
227 }
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000228 }
229 return true
230 })
Jay Conrode1bdc762017-02-10 16:20:20 -0500231 return
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000232}