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.
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.
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.
28 func LinkifyText(w io.Writer, text []byte, n ast.Node) {
32 prev := "" // prev HTML tag
33 linkWriter := func(w io.Writer, _ int, start bool) {
37 fmt.Fprintf(w, `</%s>`, prev)
46 switch info := links[i]; {
47 case info.path != "" && info.name == "":
49 fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path)
51 case info.path != "" && info.name != "":
52 // qualified identifier
53 fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.name)
55 case info.path == "" && info.name != "":
58 fmt.Fprintf(w, `<span id="%s">`, info.name)
60 } else if ast.IsExported(info.name) {
61 fmt.Fprintf(w, `<a href="#%s">`, info.name)
69 idents := tokenSelection(text, token.IDENT)
70 comments := tokenSelection(text, token.COMMENT)
71 FormatSelections(w, text, linkWriter, idents, selectionTag, comments)
74 // A link describes the (HTML) link information for an identifier.
75 // The zero value of a link represents "no link".
78 path, name string // package path, identifier name
79 isVal bool // identifier is defined in a const or var declaration
82 // linksFor returns the list of links for the identifiers used
83 // by node in the same order as they appear in the source.
85 func 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)
92 ast.Inspect(node, func(node ast.Node) bool {
93 switch n := node.(type) {
95 for _, n := range n.Names {
99 if name := n.Name; name != nil {
100 linkMap[name] = link{}
103 for _, n := range n.Names {
104 linkMap[n] = link{name: n.Name, isVal: true}
107 linkMap[n.Name] = link{}
109 linkMap[n.Name] = link{}
110 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
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 {
122 linkMap[n] = link{isVal: true}
126 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.
138 linkMap[x] = link{path: path}
139 linkMap[n.Sel] = link{path: path, name: n.Sel.Name}
144 case *ast.CompositeLit:
145 // Detect field names within composite literals. These links should
146 // be prefixed by the type name.
149 switch typ := n.Type.(type) {
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.
161 linkMap[x] = link{path: path}
162 linkMap[typ.Sel] = link{path: path, name: typ.Sel.Name}
164 prefix = typ.Sel.Name + "."
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
177 linkMap[k] = link{path: fieldPath, name: name}
182 if l, ok := linkMap[n]; ok {
183 links = append(links, l)
185 l := link{name: n.Name}
186 if n.Obj == nil && doc.IsPredeclared(n.Name) {
187 l.path = builtinPkgPath
189 links = append(links, l)