зеркало из https://github.com/golang/tools.git
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:
Родитель
0f86c627e2
Коммит
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 {
|
||||
return
|
||||
}
|
||||
|
||||
v := buf.Bytes()
|
||||
buf.Reset()
|
||||
|
||||
var scratch bytes.Buffer
|
||||
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>")
|
||||
})
|
||||
scratch.Reset()
|
||||
var added bool
|
||||
foreachLine(buf.Bytes(), func(line []byte) {
|
||||
if !added && isLineForStructFieldID(line, fieldName) {
|
||||
added = true
|
||||
fmt.Fprintf(&scratch, `<span id="%s.%s"></span>`, name, fieldName)
|
||||
}
|
||||
scratch.Write(line)
|
||||
})
|
||||
buf.Reset()
|
||||
buf.Write(scratch.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// foreachLine calls fn for each line of in, where a line includes
|
||||
// the trailing "\n", except on the last line, if it doesn't exist.
|
||||
func foreachLine(in []byte, fn func(line []byte)) {
|
||||
for len(in) > 0 {
|
||||
nl := bytes.IndexByte(in, '\n')
|
||||
if nl == -1 {
|
||||
fn(in)
|
||||
return
|
||||
}
|
||||
fn(in[:nl+1])
|
||||
in = in[nl+1:]
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
|
|
@ -130,10 +130,13 @@ func TestStructFieldsIDAttributes(t *testing.T) {
|
|||
package foo
|
||||
|
||||
type T struct {
|
||||
NoDoc string
|
||||
NoDoc string
|
||||
|
||||
// Doc has a comment.
|
||||
Doc string
|
||||
// Doc has a comment.
|
||||
Doc string
|
||||
|
||||
// Opt, if non-nil, is an option.
|
||||
Opt *int
|
||||
}
|
||||
`)
|
||||
fset := token.NewFileSet()
|
||||
|
@ -147,12 +150,15 @@ type T struct {
|
|||
}
|
||||
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 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>
|
||||
|
||||
<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 {
|
||||
t.Errorf(" got: %q\nwant: %q\n", got, want)
|
||||
t.Errorf("got: %s\n\nwant: %s\n", got, want)
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче