1
0
mirror of https://github.com/golang/go synced 2024-11-20 03:34:40 -07:00

cmd/godoc: emit id's for constants and variables

Fixes #5077.

R=r
CC=golang-dev
https://golang.org/cl/8021044
This commit is contained in:
Robert Griesemer 2013-03-27 15:14:28 -07:00
parent 8d4f381f5c
commit 611e8dbf52
2 changed files with 59 additions and 35 deletions

View File

@ -25,36 +25,41 @@ import (
// formatted the same way as with FormatText. // formatted the same way as with FormatText.
// //
func LinkifyText(w io.Writer, text []byte, n ast.Node) { func LinkifyText(w io.Writer, text []byte, n ast.Node) {
links := links(n) links := linksFor(n)
i := 0 // links index i := 0 // links index
open := false // status of html tag prev := "" // prev HTML tag
linkWriter := func(w io.Writer, _ int, start bool) { linkWriter := func(w io.Writer, _ int, start bool) {
// end tag // end tag
if !start { if !start {
if open { if prev != "" {
fmt.Fprintf(w, `</a>`) fmt.Fprintf(w, `</%s>`, prev)
open = false prev = ""
} }
return return
} }
// start tag // start tag
open = false prev = ""
if i < len(links) { if i < len(links) {
switch info := links[i]; { switch info := links[i]; {
case info.path != "" && info.ident == nil: case info.path != "" && info.name == "":
// package path // package path
fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path) fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path)
open = true prev = "a"
case info.path != "" && info.ident != nil: case info.path != "" && info.name != "":
// qualified identifier // qualified identifier
fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.ident.Name) fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.name)
open = true prev = "a"
case info.path == "" && info.ident != nil: case info.path == "" && info.name != "":
// locally declared identifier // local identifier
fmt.Fprintf(w, `<a href="#%s">`, info.ident.Name) if info.mode == identVal {
open = true fmt.Fprintf(w, `<span id="%s">`, info.name)
prev = "span"
} else {
fmt.Fprintf(w, `<a href="#%s">`, info.name)
prev = "a"
}
} }
i++ i++
} }
@ -69,27 +74,34 @@ func LinkifyText(w io.Writer, text []byte, n ast.Node) {
// The zero value of a link represents "no link". // The zero value of a link represents "no link".
// //
type link struct { type link struct {
path string mode identMode
ident *ast.Ident path, name string // package path, identifier name
} }
// links returns the list of links for the identifiers used // linksFor returns the list of links for the identifiers used
// by node in the same order as they appear in the source. // by node in the same order as they appear in the source.
// //
func links(node ast.Node) (list []link) { func linksFor(node ast.Node) (list []link) {
defs := defs(node) modes := identModesFor(node)
// NOTE: We are expecting ast.Inspect to call the // NOTE: We are expecting ast.Inspect to call the
// callback function in source text order. // callback function in source text order.
ast.Inspect(node, func(node ast.Node) bool { ast.Inspect(node, func(node ast.Node) bool {
switch n := node.(type) { switch n := node.(type) {
case *ast.Ident: case *ast.Ident:
info := link{} m := modes[n]
if !defs[n] { info := link{mode: m}
switch m {
case identUse:
if n.Obj == nil && predeclared[n.Name] { if n.Obj == nil && predeclared[n.Name] {
info.path = builtinPkgPath info.path = builtinPkgPath
} }
info.ident = n 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) list = append(list, info)
return false return false
@ -107,7 +119,7 @@ func links(node ast.Node) (list []link) {
// and one for the qualified identifier. // and one for the qualified identifier.
info := link{path: path} info := link{path: path}
list = append(list, info) list = append(list, info)
info.ident = n.Sel info.name = n.Sel.Name
list = append(list, info) list = append(list, info)
return false return false
} }
@ -121,28 +133,37 @@ func links(node ast.Node) (list []link) {
return return
} }
// defs returns the set of identifiers that are declared ("defined") by node. // The identMode describes how an identifier is "used" at its source location.
func defs(node ast.Node) map[*ast.Ident]bool { type identMode int
m := make(map[*ast.Ident]bool)
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 { ast.Inspect(node, func(node ast.Node) bool {
switch n := node.(type) { switch n := node.(type) {
case *ast.Field: case *ast.Field:
for _, n := range n.Names { for _, n := range n.Names {
m[n] = true m[n] = identDef
} }
case *ast.ImportSpec: case *ast.ImportSpec:
if name := n.Name; name != nil { if name := n.Name; name != nil {
m[name] = true m[name] = identDef
} }
case *ast.ValueSpec: case *ast.ValueSpec:
for _, n := range n.Names { for _, n := range n.Names {
m[n] = true m[n] = identVal
} }
case *ast.TypeSpec: case *ast.TypeSpec:
m[n.Name] = true m[n.Name] = identDef
case *ast.FuncDecl: case *ast.FuncDecl:
m[n.Name] = true m[n.Name] = identDef
case *ast.AssignStmt: case *ast.AssignStmt:
// Short variable declarations only show up if we apply // Short variable declarations only show up if we apply
// this code to all source code (as opposed to exported // this code to all source code (as opposed to exported
@ -155,7 +176,7 @@ func defs(node ast.Node) map[*ast.Ident]bool {
// Each lhs expression should be an // Each lhs expression should be an
// ident, but we are conservative and check. // ident, but we are conservative and check.
if n, _ := x.(*ast.Ident); n != nil { if n, _ := x.(*ast.Ident); n != nil {
m[n] = true m[n] = identVal
} }
} }
} }
@ -167,6 +188,9 @@ func defs(node ast.Node) map[*ast.Ident]bool {
} }
// The predeclared map represents the set of all predeclared identifiers. // 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{ var predeclared = map[string]bool{
"bool": true, "bool": true,
"byte": true, "byte": true,

View File

@ -411,7 +411,7 @@ func main() {
info.PDoc.ImportPath = flag.Arg(0) info.PDoc.ImportPath = flag.Arg(0)
} }
// If we have more than one argument, use the remaining arguments for filtering // If we have more than one argument, use the remaining arguments for filtering.
if flag.NArg() > 1 { if flag.NArg() > 1 {
args := flag.Args()[1:] args := flag.Args()[1:]
rx := makeRx(args) rx := makeRx(args)