2013-07-16 22:02:35 -06:00
|
|
|
// Copyright 2013 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// This file implements LinkifyText which introduces
|
|
|
|
// links for identifiers pointing to their declarations.
|
|
|
|
// The approach does not cover all cases because godoc
|
|
|
|
// doesn't have complete type information, but it's
|
|
|
|
// reasonably good for browsing.
|
|
|
|
|
2013-07-17 01:09:54 -06:00
|
|
|
package godoc
|
2013-07-16 22:02:35 -06:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"go/ast"
|
|
|
|
"go/token"
|
|
|
|
"io"
|
|
|
|
"strconv"
|
|
|
|
)
|
|
|
|
|
|
|
|
// LinkifyText HTML-escapes source text and writes it to w.
|
|
|
|
// Identifiers that are in a "use" position (i.e., that are
|
|
|
|
// not being declared), are wrapped with HTML links pointing
|
|
|
|
// to the respective declaration, if possible. Comments are
|
|
|
|
// formatted the same way as with FormatText.
|
|
|
|
//
|
|
|
|
func LinkifyText(w io.Writer, text []byte, n ast.Node) {
|
|
|
|
links := linksFor(n)
|
|
|
|
|
|
|
|
i := 0 // links index
|
|
|
|
prev := "" // prev HTML tag
|
|
|
|
linkWriter := func(w io.Writer, _ int, start bool) {
|
|
|
|
// end tag
|
|
|
|
if !start {
|
|
|
|
if prev != "" {
|
|
|
|
fmt.Fprintf(w, `</%s>`, prev)
|
|
|
|
prev = ""
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// start tag
|
|
|
|
prev = ""
|
|
|
|
if i < len(links) {
|
|
|
|
switch info := links[i]; {
|
|
|
|
case info.path != "" && info.name == "":
|
|
|
|
// package path
|
|
|
|
fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path)
|
|
|
|
prev = "a"
|
|
|
|
case info.path != "" && info.name != "":
|
|
|
|
// qualified identifier
|
|
|
|
fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.name)
|
|
|
|
prev = "a"
|
|
|
|
case info.path == "" && info.name != "":
|
|
|
|
// local identifier
|
|
|
|
if info.mode == identVal {
|
|
|
|
fmt.Fprintf(w, `<span id="%s">`, info.name)
|
|
|
|
prev = "span"
|
|
|
|
} else if ast.IsExported(info.name) {
|
|
|
|
fmt.Fprintf(w, `<a href="#%s">`, info.name)
|
|
|
|
prev = "a"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
idents := tokenSelection(text, token.IDENT)
|
|
|
|
comments := tokenSelection(text, token.COMMENT)
|
|
|
|
FormatSelections(w, text, linkWriter, idents, selectionTag, comments)
|
|
|
|
}
|
|
|
|
|
|
|
|
// A link describes the (HTML) link information for an identifier.
|
|
|
|
// The zero value of a link represents "no link".
|
|
|
|
//
|
|
|
|
type link struct {
|
|
|
|
mode identMode
|
|
|
|
path, name string // package path, identifier name
|
|
|
|
}
|
|
|
|
|
|
|
|
// The identMode describes how an identifier is "used" at its source location.
|
|
|
|
type identMode int
|
|
|
|
|
|
|
|
const (
|
|
|
|
identUse identMode = iota // identifier is used (must be zero value for identMode)
|
|
|
|
identDef // identifier is defined
|
|
|
|
identVal // identifier is defined in a const or var declaration
|
|
|
|
)
|
|
|
|
|
2017-02-10 14:20:20 -07:00
|
|
|
// linksFor returns the list of links for the identifiers used
|
|
|
|
// by node in the same order as they appear in the source.
|
|
|
|
//
|
|
|
|
func linksFor(node ast.Node) (links []link) {
|
|
|
|
// linkMap tracks link information for each ast.Ident node. Entries may
|
|
|
|
// be created out of source order (for example, when we visit a parent
|
|
|
|
// definition node). These links are appended to the returned slice when
|
|
|
|
// their ast.Ident nodes are visited.
|
|
|
|
linkMap := make(map[*ast.Ident]link)
|
2013-07-16 22:02:35 -06:00
|
|
|
|
|
|
|
ast.Inspect(node, func(node ast.Node) bool {
|
|
|
|
switch n := node.(type) {
|
|
|
|
case *ast.Field:
|
|
|
|
for _, n := range n.Names {
|
2017-02-10 14:20:20 -07:00
|
|
|
linkMap[n] = link{mode: identDef}
|
2013-07-16 22:02:35 -06:00
|
|
|
}
|
|
|
|
case *ast.ImportSpec:
|
|
|
|
if name := n.Name; name != nil {
|
2017-02-10 14:20:20 -07:00
|
|
|
linkMap[name] = link{mode: identDef}
|
2013-07-16 22:02:35 -06:00
|
|
|
}
|
|
|
|
case *ast.ValueSpec:
|
|
|
|
for _, n := range n.Names {
|
2017-04-11 13:25:53 -06:00
|
|
|
linkMap[n] = link{mode: identVal, name: n.Name}
|
2013-07-16 22:02:35 -06:00
|
|
|
}
|
|
|
|
case *ast.TypeSpec:
|
2017-02-10 14:20:20 -07:00
|
|
|
linkMap[n.Name] = link{mode: identDef}
|
2013-07-16 22:02:35 -06:00
|
|
|
case *ast.AssignStmt:
|
|
|
|
// Short variable declarations only show up if we apply
|
|
|
|
// this code to all source code (as opposed to exported
|
|
|
|
// declarations only).
|
|
|
|
if n.Tok == token.DEFINE {
|
|
|
|
// Some of the lhs variables may be re-declared,
|
|
|
|
// so technically they are not defs. We don't
|
|
|
|
// care for now.
|
|
|
|
for _, x := range n.Lhs {
|
|
|
|
// Each lhs expression should be an
|
|
|
|
// ident, but we are conservative and check.
|
|
|
|
if n, _ := x.(*ast.Ident); n != nil {
|
2017-02-10 14:20:20 -07:00
|
|
|
linkMap[n] = link{mode: identVal}
|
2013-07-16 22:02:35 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-02-10 14:20:20 -07:00
|
|
|
case *ast.SelectorExpr:
|
|
|
|
// Detect qualified identifiers of the form pkg.ident.
|
|
|
|
// If anything fails we return true and collect individual
|
|
|
|
// identifiers instead.
|
|
|
|
if x, _ := n.X.(*ast.Ident); x != nil {
|
|
|
|
// Create links only if x is a qualified identifier.
|
|
|
|
if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
|
|
|
|
if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
|
|
|
|
// spec.Path.Value is the import path
|
|
|
|
if path, err := strconv.Unquote(spec.Path.Value); err == nil {
|
|
|
|
// Register two links, one for the package
|
|
|
|
// and one for the qualified identifier.
|
|
|
|
linkMap[x] = link{mode: identUse, path: path}
|
|
|
|
linkMap[n.Sel] = link{mode: identUse, path: path, name: n.Sel.Name}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case *ast.CompositeLit:
|
|
|
|
// Detect field names within composite literals. These links should
|
|
|
|
// be prefixed by the type name.
|
|
|
|
fieldPath := ""
|
|
|
|
prefix := ""
|
|
|
|
switch typ := n.Type.(type) {
|
|
|
|
case *ast.Ident:
|
|
|
|
prefix = typ.Name + "."
|
|
|
|
case *ast.SelectorExpr:
|
|
|
|
if x, _ := typ.X.(*ast.Ident); x != nil {
|
|
|
|
// Create links only if x is a qualified identifier.
|
|
|
|
if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
|
|
|
|
if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
|
|
|
|
// spec.Path.Value is the import path
|
|
|
|
if path, err := strconv.Unquote(spec.Path.Value); err == nil {
|
|
|
|
// Register two links, one for the package
|
|
|
|
// and one for the qualified identifier.
|
|
|
|
linkMap[x] = link{mode: identUse, path: path}
|
|
|
|
linkMap[typ.Sel] = link{mode: identUse, path: path, name: typ.Sel.Name}
|
|
|
|
fieldPath = path
|
|
|
|
prefix = typ.Sel.Name + "."
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, e := range n.Elts {
|
|
|
|
if kv, ok := e.(*ast.KeyValueExpr); ok {
|
|
|
|
if k, ok := kv.Key.(*ast.Ident); ok {
|
|
|
|
// Note: there is some syntactic ambiguity here. We cannot determine
|
|
|
|
// if this is a struct literal or a map literal without type
|
|
|
|
// information. We assume struct literal.
|
|
|
|
name := prefix + k.Name
|
|
|
|
linkMap[k] = link{mode: identUse, path: fieldPath, name: name}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case *ast.Ident:
|
|
|
|
if l, ok := linkMap[n]; ok {
|
|
|
|
links = append(links, l)
|
|
|
|
} else {
|
|
|
|
l := link{mode: identUse, name: n.Name}
|
|
|
|
if n.Obj == nil && predeclared[n.Name] {
|
|
|
|
l.path = builtinPkgPath
|
|
|
|
}
|
|
|
|
links = append(links, l)
|
|
|
|
}
|
2013-07-16 22:02:35 -06:00
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
2017-02-10 14:20:20 -07:00
|
|
|
return
|
2013-07-16 22:02:35 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// The predeclared map represents the set of all predeclared identifiers.
|
|
|
|
// TODO(gri) This information is also encoded in similar maps in go/doc,
|
|
|
|
// but not exported. Consider exporting an accessor and using
|
|
|
|
// it instead.
|
|
|
|
var predeclared = map[string]bool{
|
|
|
|
"bool": true,
|
|
|
|
"byte": true,
|
|
|
|
"complex64": true,
|
|
|
|
"complex128": true,
|
|
|
|
"error": true,
|
|
|
|
"float32": true,
|
|
|
|
"float64": true,
|
|
|
|
"int": true,
|
|
|
|
"int8": true,
|
|
|
|
"int16": true,
|
|
|
|
"int32": true,
|
|
|
|
"int64": true,
|
|
|
|
"rune": true,
|
|
|
|
"string": true,
|
|
|
|
"uint": true,
|
|
|
|
"uint8": true,
|
|
|
|
"uint16": true,
|
|
|
|
"uint32": true,
|
|
|
|
"uint64": true,
|
|
|
|
"uintptr": true,
|
|
|
|
"true": true,
|
|
|
|
"false": true,
|
|
|
|
"iota": true,
|
|
|
|
"nil": true,
|
|
|
|
"append": true,
|
|
|
|
"cap": true,
|
|
|
|
"close": true,
|
|
|
|
"complex": true,
|
|
|
|
"copy": true,
|
|
|
|
"delete": true,
|
|
|
|
"imag": true,
|
|
|
|
"len": true,
|
|
|
|
"make": true,
|
|
|
|
"new": true,
|
|
|
|
"panic": true,
|
|
|
|
"print": true,
|
|
|
|
"println": true,
|
|
|
|
"real": true,
|
|
|
|
"recover": true,
|
|
|
|
}
|