mirror of
https://github.com/golang/go
synced 2024-11-05 17:36:15 -07:00
godoc: update struct field anchor code
Now without regexps and allocations. And also match comments like: // Foo, if non-nil, ... The comma confused the old pattern. Updates golang/go#16753 Change-Id: I9016ee7b5933ea343950a39989952804c74a598b Reviewed-on: https://go-review.googlesource.com/33755 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Chris Broadfoot <cbro@golang.org>
This commit is contained in:
parent
0f86c627e2
commit
e5f9a3deee
115
godoc/godoc.go
115
godoc/godoc.go
@ -233,49 +233,94 @@ func addStructFieldIDAttributes(buf *bytes.Buffer, name string, st *ast.StructTy
|
|||||||
if st.Fields == nil {
|
if st.Fields == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var scratch bytes.Buffer
|
||||||
v := buf.Bytes()
|
|
||||||
buf.Reset()
|
|
||||||
|
|
||||||
for _, f := range st.Fields.List {
|
for _, f := range st.Fields.List {
|
||||||
if len(f.Names) == 0 {
|
if len(f.Names) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fieldName := f.Names[0].Name
|
fieldName := f.Names[0].Name
|
||||||
commentStart := []byte("// " + fieldName + " ")
|
scratch.Reset()
|
||||||
if bytes.Contains(v, commentStart) {
|
var added bool
|
||||||
// For fields with a doc string of the
|
foreachLine(buf.Bytes(), func(line []byte) {
|
||||||
// conventional form, we put the new span into
|
if !added && isLineForStructFieldID(line, fieldName) {
|
||||||
// the comment instead of the field.
|
added = true
|
||||||
// The "conventional" form is a complete sentence
|
fmt.Fprintf(&scratch, `<span id="%s.%s"></span>`, name, fieldName)
|
||||||
// per https://golang.org/s/style#comment-sentences like:
|
}
|
||||||
//
|
scratch.Write(line)
|
||||||
// // Foo is an optional Fooer to foo the foos.
|
})
|
||||||
// Foo Fooer
|
buf.Reset()
|
||||||
//
|
buf.Write(scratch.Bytes())
|
||||||
// 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
|
// foreachLine calls fn for each line of in, where a line includes
|
||||||
// obscure the docs above the browser's "fold".
|
// the trailing "\n", except on the last line, if it doesn't exist.
|
||||||
//
|
func foreachLine(in []byte, fn func(line []byte)) {
|
||||||
// TODO: do this better, so it works for all
|
for len(in) > 0 {
|
||||||
// comments, including unconventional ones.
|
nl := bytes.IndexByte(in, '\n')
|
||||||
v = bytes.Replace(v, commentStart, []byte(`<span id="`+name+"."+fieldName+`">// `+fieldName+" </span>"), 1)
|
if nl == -1 {
|
||||||
} else {
|
fn(in)
|
||||||
rx := regexp.MustCompile(`(?m)^\s*` + fieldName + `\b`)
|
return
|
||||||
var matched bool
|
}
|
||||||
v = rx.ReplaceAllFunc(v, func(sub []byte) []byte {
|
fn(in[:nl+1])
|
||||||
if matched {
|
in = in[nl+1:]
|
||||||
return sub
|
}
|
||||||
}
|
}
|
||||||
matched = true
|
|
||||||
return []byte(`<span id="` + name + "." + fieldName + `">` + string(sub) + "</span>")
|
// commentPrefix is the line prefix for comments after they've been HTMLified.
|
||||||
})
|
var commentPrefix = []byte(`<span class="comment">// `)
|
||||||
|
|
||||||
|
// isLineForStructFieldID reports whether line is a line we should
|
||||||
|
// add a <span id="#StructName.FieldName"> to. Only the fieldName is provided.
|
||||||
|
func isLineForStructFieldID(line []byte, fieldName string) bool {
|
||||||
|
line = bytes.TrimSpace(line)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// For comments
|
||||||
|
if bytes.HasPrefix(line, commentPrefix) {
|
||||||
|
if matchesIdentBoundary(line[len(commentPrefix):], fieldName) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return matchesIdentBoundary(line, fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
buf.Write(v)
|
// matchesIdentBoundary reports whether line matches /^ident\b/.
|
||||||
|
// A boundary is considered either none, or an ASCII non-alphanum.
|
||||||
|
func matchesIdentBoundary(line []byte, ident string) bool {
|
||||||
|
if len(line) < len(ident) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if string(line[:len(ident)]) != ident {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
rest := line[len(ident):]
|
||||||
|
return len(rest) == 0 || !isASCIIWordChar(rest[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// isASCIIWordChar reports whether b is an ASCII "word"
|
||||||
|
// character. (Matching /\w/ in ASCII mode)
|
||||||
|
func isASCIIWordChar(b byte) bool {
|
||||||
|
return 'a' <= b && b <= 'z' ||
|
||||||
|
'A' <= b && b <= 'Z' ||
|
||||||
|
'0' <= b && b <= '0' ||
|
||||||
|
b == '_'
|
||||||
}
|
}
|
||||||
|
|
||||||
func comment_htmlFunc(comment string) string {
|
func comment_htmlFunc(comment string) string {
|
||||||
|
@ -130,10 +130,13 @@ func TestStructFieldsIDAttributes(t *testing.T) {
|
|||||||
package foo
|
package foo
|
||||||
|
|
||||||
type T struct {
|
type T struct {
|
||||||
NoDoc string
|
NoDoc string
|
||||||
|
|
||||||
// Doc has a comment.
|
// Doc has a comment.
|
||||||
Doc string
|
Doc string
|
||||||
|
|
||||||
|
// Opt, if non-nil, is an option.
|
||||||
|
Opt *int
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
@ -147,12 +150,15 @@ type T struct {
|
|||||||
}
|
}
|
||||||
got := p.node_htmlFunc(pi, genDecl, true)
|
got := p.node_htmlFunc(pi, genDecl, true)
|
||||||
want := `type T struct {
|
want := `type T struct {
|
||||||
<span id="T.NoDoc">NoDoc</span> <a href="/pkg/builtin/#string">string</a>
|
<span id="T.NoDoc"></span>NoDoc <a href="/pkg/builtin/#string">string</a>
|
||||||
|
|
||||||
<span class="comment"><span id="T.Doc">// Doc </span>has a comment.</span>
|
<span id="T.Doc"></span><span class="comment">// Doc has a comment.</span>
|
||||||
Doc <a href="/pkg/builtin/#string">string</a>
|
Doc <a href="/pkg/builtin/#string">string</a>
|
||||||
|
|
||||||
|
<span id="T.Opt"></span><span class="comment">// Opt, if non-nil, is an option.</span>
|
||||||
|
Opt *<a href="/pkg/builtin/#int">int</a>
|
||||||
}`
|
}`
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf(" got: %q\nwant: %q\n", got, want)
|
t.Errorf("got: %s\n\nwant: %s\n", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user