+++ /dev/null
-// 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.
-
-package godoc
-
-import (
- "fmt"
- "go/ast"
- "go/doc"
- "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.isVal {
- 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 {
- path, name string // package path, identifier name
- isVal bool // identifier is defined in a const or var declaration
-}
-
-// 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)
-
- ast.Inspect(node, func(node ast.Node) bool {
- switch n := node.(type) {
- case *ast.Field:
- for _, n := range n.Names {
- linkMap[n] = link{}
- }
- case *ast.ImportSpec:
- if name := n.Name; name != nil {
- linkMap[name] = link{}
- }
- case *ast.ValueSpec:
- for _, n := range n.Names {
- linkMap[n] = link{name: n.Name, isVal: true}
- }
- case *ast.FuncDecl:
- linkMap[n.Name] = link{}
- case *ast.TypeSpec:
- linkMap[n.Name] = link{}
- 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 {
- linkMap[n] = link{isVal: true}
- }
- }
- }
- 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{path: path}
- linkMap[n.Sel] = link{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{path: path}
- linkMap[typ.Sel] = link{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{path: fieldPath, name: name}
- }
- }
- }
- case *ast.Ident:
- if l, ok := linkMap[n]; ok {
- links = append(links, l)
- } else {
- l := link{name: n.Name}
- if n.Obj == nil && doc.IsPredeclared(n.Name) {
- l.path = builtinPkgPath
- }
- links = append(links, l)
- }
- }
- return true
- })
- return
-}