godoc: make struct fields linkable in HTML mode
This adds <span id="StructName.FieldName"> elements around field names, starting at the comment if present, so people can link to /pkg/somepkg/#SomeStruct.SomeField. Fixes golang/go#16753 Change-Id: I4a8b30605d18e9e33e3d42f273a95067ac491438 Reviewed-on: https://go-review.googlesource.com/33690 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
34fe8ce027
commit
0f65b31aee
|
@ -190,6 +190,9 @@ func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify b
|
||||||
var buf2 bytes.Buffer
|
var buf2 bytes.Buffer
|
||||||
if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
|
if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
|
||||||
LinkifyText(&buf2, buf1.Bytes(), n)
|
LinkifyText(&buf2, buf1.Bytes(), n)
|
||||||
|
if st, name := isStructTypeDecl(n); st != nil {
|
||||||
|
addStructFieldIDAttributes(&buf2, name, st)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
|
FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
|
||||||
}
|
}
|
||||||
|
@ -197,6 +200,84 @@ func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify b
|
||||||
return buf2.String()
|
return buf2.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isStructTypeDecl checks whether n is a struct declaration.
|
||||||
|
// It either returns a non-nil StructType and its name, or zero values.
|
||||||
|
func isStructTypeDecl(n ast.Node) (st *ast.StructType, name string) {
|
||||||
|
gd, ok := n.(*ast.GenDecl)
|
||||||
|
if !ok || gd.Tok != token.TYPE {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
if gd.Lparen > 0 {
|
||||||
|
// Parenthesized type. Who does that, anyway?
|
||||||
|
// TODO: Reportedly gri does. Fix this to handle that too.
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
if len(gd.Specs) != 1 {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
ts, ok := gd.Specs[0].(*ast.TypeSpec)
|
||||||
|
if !ok {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
st, ok = ts.Type.(*ast.StructType)
|
||||||
|
if !ok {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
return st, ts.Name.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// addStructFieldIDAttributes modifies the contents of buf such that
|
||||||
|
// all struct fields of the named struct have <span id='name.Field'>
|
||||||
|
// in them, so people can link to /#Struct.Field.
|
||||||
|
func addStructFieldIDAttributes(buf *bytes.Buffer, name string, st *ast.StructType) {
|
||||||
|
if st.Fields == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v := buf.Bytes()
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
for _, f := range st.Fields.List {
|
||||||
|
if len(f.Names) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fieldName := f.Names[0].Name
|
||||||
|
commentStart := []byte("// " + fieldName + " ")
|
||||||
|
if bytes.Contains(v, commentStart) {
|
||||||
|
// For fields with a doc string of the
|
||||||
|
// conventional form, we put the new span into
|
||||||
|
// the comment instead of the field.
|
||||||
|
// The "conventional" form is a complete sentence
|
||||||
|
// per https://golang.org/s/style#comment-sentences like:
|
||||||
|
//
|
||||||
|
// // Foo is an optional Fooer to foo the foos.
|
||||||
|
// Foo Fooer
|
||||||
|
//
|
||||||
|
// In this case, we want the #StructName.Foo
|
||||||
|
// link to make the browser go to the comment
|
||||||
|
// line "Foo is an optional Fooer" instead of
|
||||||
|
// the "Foo Fooer" line, which could otherwise
|
||||||
|
// obscure the docs above the browser's "fold".
|
||||||
|
//
|
||||||
|
// TODO: do this better, so it works for all
|
||||||
|
// comments, including unconventional ones.
|
||||||
|
v = bytes.Replace(v, commentStart, []byte(`<span id="`+name+"."+fieldName+`">// `+fieldName+" </span>"), 1)
|
||||||
|
} else {
|
||||||
|
rx := regexp.MustCompile(`(?m)^\s*` + fieldName + `\b`)
|
||||||
|
var matched bool
|
||||||
|
v = rx.ReplaceAllFunc(v, func(sub []byte) []byte {
|
||||||
|
if matched {
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
matched = true
|
||||||
|
return []byte(`<span id="` + name + "." + fieldName + `">` + string(sub) + "</span>")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Write(v)
|
||||||
|
}
|
||||||
|
|
||||||
func comment_htmlFunc(comment string) string {
|
func comment_htmlFunc(comment string) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
// TODO(gri) Provide list of words (e.g. function parameters)
|
// TODO(gri) Provide list of words (e.g. function parameters)
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
package godoc
|
package godoc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -116,3 +119,40 @@ func TestSanitizeFunc(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that we add <span id="StructName.FieldName"> elements
|
||||||
|
// to the HTML of struct fields.
|
||||||
|
func TestStructFieldsIDAttributes(t *testing.T) {
|
||||||
|
p := &Presentation{
|
||||||
|
DeclLinks: true,
|
||||||
|
}
|
||||||
|
src := []byte(`
|
||||||
|
package foo
|
||||||
|
|
||||||
|
type T struct {
|
||||||
|
NoDoc string
|
||||||
|
|
||||||
|
// Doc has a comment.
|
||||||
|
Doc string
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
af, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
genDecl := af.Decls[0].(*ast.GenDecl)
|
||||||
|
pi := &PageInfo{
|
||||||
|
FSet: fset,
|
||||||
|
}
|
||||||
|
got := p.node_htmlFunc(pi, genDecl, true)
|
||||||
|
want := `type T struct {
|
||||||
|
<span id="T.NoDoc">NoDoc</span> <a href="/pkg/builtin/#string">string</a>
|
||||||
|
|
||||||
|
<span class="comment"><span id="T.Doc">// Doc </span>has a comment.</span>
|
||||||
|
Doc <a href="/pkg/builtin/#string">string</a>
|
||||||
|
}`
|
||||||
|
if got != want {
|
||||||
|
t.Errorf(" got: %q\nwant: %q\n", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue