235 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			235 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
// 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/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
 | 
						|
}
 | 
						|
 | 
						|
// 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) (list []link) {
 | 
						|
	modes := identModesFor(node)
 | 
						|
 | 
						|
	// NOTE: We are expecting ast.Inspect to call the
 | 
						|
	//       callback function in source text order.
 | 
						|
	ast.Inspect(node, func(node ast.Node) bool {
 | 
						|
		switch n := node.(type) {
 | 
						|
		case *ast.Ident:
 | 
						|
			m := modes[n]
 | 
						|
			info := link{mode: m}
 | 
						|
			switch m {
 | 
						|
			case identUse:
 | 
						|
				if n.Obj == nil && predeclared[n.Name] {
 | 
						|
					info.path = builtinPkgPath
 | 
						|
				}
 | 
						|
				info.name = n.Name
 | 
						|
			case identDef:
 | 
						|
				// any declaration expect const or var - empty link
 | 
						|
			case identVal:
 | 
						|
				// const or var declaration
 | 
						|
				info.name = n.Name
 | 
						|
			}
 | 
						|
			list = append(list, info)
 | 
						|
			return false
 | 
						|
		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 {
 | 
						|
				// x must be a package for 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.
 | 
						|
							info := link{path: path}
 | 
						|
							list = append(list, info)
 | 
						|
							info.name = n.Sel.Name
 | 
						|
							list = append(list, info)
 | 
						|
							return false
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return true
 | 
						|
	})
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
)
 | 
						|
 | 
						|
// identModesFor returns a map providing the identMode for each identifier used by node.
 | 
						|
func identModesFor(node ast.Node) map[*ast.Ident]identMode {
 | 
						|
	m := make(map[*ast.Ident]identMode)
 | 
						|
 | 
						|
	ast.Inspect(node, func(node ast.Node) bool {
 | 
						|
		switch n := node.(type) {
 | 
						|
		case *ast.Field:
 | 
						|
			for _, n := range n.Names {
 | 
						|
				m[n] = identDef
 | 
						|
			}
 | 
						|
		case *ast.ImportSpec:
 | 
						|
			if name := n.Name; name != nil {
 | 
						|
				m[name] = identDef
 | 
						|
			}
 | 
						|
		case *ast.ValueSpec:
 | 
						|
			for _, n := range n.Names {
 | 
						|
				m[n] = identVal
 | 
						|
			}
 | 
						|
		case *ast.TypeSpec:
 | 
						|
			m[n.Name] = identDef
 | 
						|
		case *ast.FuncDecl:
 | 
						|
			m[n.Name] = identDef
 | 
						|
		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 {
 | 
						|
						m[n] = identVal
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return true
 | 
						|
	})
 | 
						|
 | 
						|
	return m
 | 
						|
}
 | 
						|
 | 
						|
// 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,
 | 
						|
}
 |