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
11package godoc
12
13import (
14	"fmt"
15	"go/ast"
16	"go/doc"
17	"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
57				if info.isVal {
58					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 {
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//
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)
91
92	ast.Inspect(node, func(node ast.Node) bool {
93		switch n := node.(type) {
94		case *ast.Field:
95			for _, n := range n.Names {
96				linkMap[n] = link{}
97			}
98		case *ast.ImportSpec:
99			if name := n.Name; name != nil {
100				linkMap[name] = link{}
101			}
102		case *ast.ValueSpec:
103			for _, n := range n.Names {
104				linkMap[n] = link{name: n.Name, isVal: true}
105			}
106		case *ast.FuncDecl:
107			linkMap[n.Name] = link{}
108		case *ast.TypeSpec:
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
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 {
122						linkMap[n] = link{isVal: true}
123					}
124				}
125			}
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}
140						}
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.
161								linkMap[x] = link{path: path}
162								linkMap[typ.Sel] = link{path: path, name: typ.Sel.Name}
163								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
177						linkMap[k] = link{path: fieldPath, name: name}
178					}
179				}
180			}
181		case *ast.Ident:
182			if l, ok := linkMap[n]; ok {
183				links = append(links, l)
184			} else {
185				l := link{name: n.Name}
186				if n.Obj == nil && doc.IsPredeclared(n.Name) {
187					l.path = builtinPkgPath
188				}
189				links = append(links, l)
190			}
191		}
192		return true
193	})
194	return
195}
196