blob: e4add22a10450cce7503542f97977fefbe2bd788 [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"
20)
21
22// LinkifyText HTML-escapes source text and writes it to w.
23// Identifiers that are in a "use" position (i.e., that are
24// not being declared), are wrapped with HTML links pointing
25// to the respective declaration, if possible. Comments are
26// formatted the same way as with FormatText.
27//
28func LinkifyText(w io.Writer, text []byte, n ast.Node) {
29 links := linksFor(n)
30
31 i := 0 // links index
32 prev := "" // prev HTML tag
33 linkWriter := func(w io.Writer, _ int, start bool) {
34 // end tag
35 if !start {
36 if prev != "" {
37 fmt.Fprintf(w, `</%s>`, prev)
38 prev = ""
39 }
40 return
41 }
42
43 // start tag
44 prev = ""
45 if i < len(links) {
46 switch info := links[i]; {
47 case info.path != "" && info.name == "":
48 // package path
49 fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path)
50 prev = "a"
51 case info.path != "" && info.name != "":
52 // qualified identifier
53 fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.name)
54 prev = "a"
55 case info.path == "" && info.name != "":
56 // local identifier
Masahiro Furudatece129152017-05-07 04:40:42 +090057 if info.isVal {
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100058 fmt.Fprintf(w, `<span id="%s">`, info.name)
59 prev = "span"
60 } else if ast.IsExported(info.name) {
61 fmt.Fprintf(w, `<a href="#%s">`, info.name)
62 prev = "a"
63 }
64 }
65 i++
66 }
67 }
68
69 idents := tokenSelection(text, token.IDENT)
70 comments := tokenSelection(text, token.COMMENT)
71 FormatSelections(w, text, linkWriter, idents, selectionTag, comments)
72}
73
74// A link describes the (HTML) link information for an identifier.
75// The zero value of a link represents "no link".
76//
77type 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.
84//
85func linksFor(node ast.Node) (links []link) {
86 // linkMap tracks link information for each ast.Ident node. Entries may
87 // be created out of source order (for example, when we visit a parent
88 // definition node). These links are appended to the returned slice when
89 // their ast.Ident nodes are visited.
90 linkMap := make(map[*ast.Ident]link)
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100091
92 ast.Inspect(node, func(node ast.Node) bool {
93 switch n := node.(type) {
94 case *ast.Field:
95 for _, n := range n.Names {
Masahiro Furudatece129152017-05-07 04:40:42 +090096 linkMap[n] = link{}
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +100097 }
98 case *ast.ImportSpec:
99 if name := n.Name; name != nil {
Masahiro Furudatece129152017-05-07 04:40:42 +0900100 linkMap[name] = link{}
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000101 }
102 case *ast.ValueSpec:
103 for _, n := range n.Names {
Masahiro Furudatece129152017-05-07 04:40:42 +0900104 linkMap[n] = link{name: n.Name, isVal: true}
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000105 }
Masahiro Furudatece129152017-05-07 04:40:42 +0900106 case *ast.FuncDecl:
107 linkMap[n.Name] = link{}
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000108 case *ast.TypeSpec:
Masahiro Furudatece129152017-05-07 04:40:42 +0900109 linkMap[n.Name] = link{}
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000110 case *ast.AssignStmt:
111 // Short variable declarations only show up if we apply
112 // this code to all source code (as opposed to exported
113 // declarations only).
114 if n.Tok == token.DEFINE {
115 // Some of the lhs variables may be re-declared,
116 // so technically they are not defs. We don't
117 // care for now.
118 for _, x := range n.Lhs {
119 // Each lhs expression should be an
120 // ident, but we are conservative and check.
121 if n, _ := x.(*ast.Ident); n != nil {
Masahiro Furudatece129152017-05-07 04:40:42 +0900122 linkMap[n] = link{isVal: true}
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000123 }
124 }
125 }
Jay Conrode1bdc762017-02-10 16:20:20 -0500126 case *ast.SelectorExpr:
127 // Detect qualified identifiers of the form pkg.ident.
128 // If anything fails we return true and collect individual
129 // identifiers instead.
130 if x, _ := n.X.(*ast.Ident); x != nil {
131 // Create links only if x is a qualified identifier.
132 if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
133 if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
134 // spec.Path.Value is the import path
135 if path, err := strconv.Unquote(spec.Path.Value); err == nil {
136 // Register two links, one for the package
137 // and one for the qualified identifier.
Masahiro Furudatece129152017-05-07 04:40:42 +0900138 linkMap[x] = link{path: path}
139 linkMap[n.Sel] = link{path: path, name: n.Sel.Name}
Jay Conrode1bdc762017-02-10 16:20:20 -0500140 }
141 }
142 }
143 }
144 case *ast.CompositeLit:
145 // Detect field names within composite literals. These links should
146 // be prefixed by the type name.
147 fieldPath := ""
148 prefix := ""
149 switch typ := n.Type.(type) {
150 case *ast.Ident:
151 prefix = typ.Name + "."
152 case *ast.SelectorExpr:
153 if x, _ := typ.X.(*ast.Ident); x != nil {
154 // Create links only if x is a qualified identifier.
155 if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
156 if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
157 // spec.Path.Value is the import path
158 if path, err := strconv.Unquote(spec.Path.Value); err == nil {
159 // Register two links, one for the package
160 // and one for the qualified identifier.
Masahiro Furudatece129152017-05-07 04:40:42 +0900161 linkMap[x] = link{path: path}
162 linkMap[typ.Sel] = link{path: path, name: typ.Sel.Name}
Jay Conrode1bdc762017-02-10 16:20:20 -0500163 fieldPath = path
164 prefix = typ.Sel.Name + "."
165 }
166 }
167 }
168 }
169 }
170 for _, e := range n.Elts {
171 if kv, ok := e.(*ast.KeyValueExpr); ok {
172 if k, ok := kv.Key.(*ast.Ident); ok {
173 // Note: there is some syntactic ambiguity here. We cannot determine
174 // if this is a struct literal or a map literal without type
175 // information. We assume struct literal.
176 name := prefix + k.Name
Masahiro Furudatece129152017-05-07 04:40:42 +0900177 linkMap[k] = link{path: fieldPath, name: name}
Jay Conrode1bdc762017-02-10 16:20:20 -0500178 }
179 }
180 }
181 case *ast.Ident:
182 if l, ok := linkMap[n]; ok {
183 links = append(links, l)
184 } else {
Masahiro Furudatece129152017-05-07 04:40:42 +0900185 l := link{name: n.Name}
Masahiro Furudate4de4a8d2017-08-25 15:00:21 +0900186 if n.Obj == nil && doc.IsPredeclared(n.Name) {
Jay Conrode1bdc762017-02-10 16:20:20 -0500187 l.path = builtinPkgPath
188 }
189 links = append(links, l)
190 }
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000191 }
192 return true
193 })
Jay Conrode1bdc762017-02-10 16:20:20 -0500194 return
Andrew Gerrandd79f4fe2013-07-17 14:02:35 +1000195}