go/printer: format doc comments

[This CL is part of a sequence implementing the proposal #51082.
The design doc is at https://go.dev/s/godocfmt-design.]

Use go/doc/comment to reformat doc comments into a
standard form, enabling future expansion later and generally
making it easier to edit and read doc comments.

For #51082.

Change-Id: I6ab3b80846f03d781951111e4c36f86f47d21bb2
Reviewed-on: https://go-review.googlesource.com/c/go/+/384264
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Russ Cox 2022-01-29 18:25:41 -05:00
parent e1b0862925
commit 078cc6a04f
10 changed files with 234 additions and 14 deletions

View File

@ -159,6 +159,7 @@ func (g *CommentGroup) Text() string {
}
// isDirective reports whether c is a comment directive.
// This code is also in go/printer.
func isDirective(c string) bool {
// "//line " is a line directive.
// (The // has been removed.)

152
src/go/printer/comment.go Normal file
View File

@ -0,0 +1,152 @@
// Copyright 2022 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.
package printer
import (
"go/ast"
"go/doc/comment"
"strings"
)
// formatDocComment reformats the doc comment list,
// returning the canonical formatting.
func formatDocComment(list []*ast.Comment) []*ast.Comment {
// Extract comment text (removing comment markers).
var kind, text string
var directives []*ast.Comment
if len(list) == 1 && strings.HasPrefix(list[0].Text, "/*") {
kind = "/*"
text = list[0].Text
if !strings.Contains(text, "\n") || allStars(text) {
// Single-line /* .. */ comment in doc comment position,
// or multiline old-style comment like
// /*
// * Comment
// * text here.
// */
// Should not happen, since it will not work well as a
// doc comment, but if it does, just ignore:
// reformatting it will only make the situation worse.
return list
}
text = text[2 : len(text)-2] // cut /* and */
} else if strings.HasPrefix(list[0].Text, "//") {
kind = "//"
var b strings.Builder
for _, c := range list {
if !strings.HasPrefix(c.Text, "//") {
return list
}
// Accumulate //go:build etc lines separately.
if isDirective(c.Text[2:]) {
directives = append(directives, c)
continue
}
b.WriteString(strings.TrimPrefix(c.Text[2:], " "))
b.WriteString("\n")
}
text = b.String()
} else {
// Not sure what this is, so leave alone.
return list
}
if text == "" {
return list
}
// Parse comment and reformat as text.
var p comment.Parser
d := p.Parse(text)
var pr comment.Printer
text = string(pr.Comment(d))
// For /* */ comment, return one big comment with text inside.
slash := list[0].Slash
if kind == "/*" {
c := &ast.Comment{
Slash: slash,
Text: "/*\n" + text + "*/",
}
return []*ast.Comment{c}
}
// For // comment, return sequence of // lines.
var out []*ast.Comment
for text != "" {
var line string
line, text, _ = strings.Cut(text, "\n")
if line == "" {
line = "//"
} else if strings.HasPrefix(line, "\t") {
line = "//" + line
} else {
line = "// " + line
}
out = append(out, &ast.Comment{
Slash: slash,
Text: line,
})
}
if len(directives) > 0 {
out = append(out, &ast.Comment{
Slash: slash,
Text: "//",
})
for _, c := range directives {
out = append(out, &ast.Comment{
Slash: slash,
Text: c.Text,
})
}
}
return out
}
// isDirective reports whether c is a comment directive.
// See go.dev/issue/37974.
// This code is also in go/ast.
func isDirective(c string) bool {
// "//line " is a line directive.
// (The // has been removed.)
if strings.HasPrefix(c, "line ") {
return true
}
// "//[a-z0-9]+:[a-z0-9]"
// (The // has been removed.)
colon := strings.Index(c, ":")
if colon <= 0 || colon+1 >= len(c) {
return false
}
for i := 0; i <= colon+1; i++ {
if i == colon {
continue
}
b := c[i]
if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') {
return false
}
}
return true
}
// allStars reports whether text is the interior of an
// old-style /* */ comment with a star at the start of each line.
func allStars(text string) bool {
for i := 0; i < len(text); i++ {
if text[i] == '\n' {
j := i + 1
for j < len(text) && (text[j] == ' ' || text[j] == '\t') {
j++
}
if j < len(text) && text[j] != '*' {
return false
}
}
}
return true
}

View File

@ -738,11 +738,28 @@ func (p *printer) containsLinebreak() bool {
func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
var last *ast.Comment
for p.commentBefore(next) {
for _, c := range p.comment.List {
list := p.comment.List
changed := false
if p.lastTok != token.IMPORT && // do not rewrite cgo's import "C" comments
p.posFor(p.comment.Pos()).Column == 1 &&
p.posFor(p.comment.End()+1) == next {
// Unindented comment abutting next token position:
// a top-level doc comment.
list = formatDocComment(list)
changed = true
}
for _, c := range list {
p.writeCommentPrefix(p.posFor(c.Pos()), next, last, tok)
p.writeComment(c)
last = c
}
// In case list was rewritten, change print state to where
// the original list would have ended.
if len(p.comment.List) > 0 && changed {
last = p.comment.List[len(p.comment.List)-1]
p.pos = p.posFor(last.End())
p.last = p.pos
}
p.nextComment()
}

View File

@ -6,6 +6,7 @@ package printer
import (
"bytes"
"errors"
"flag"
"fmt"
"go/ast"
@ -92,8 +93,7 @@ func checkEqual(aname, bname string, a, b []byte) error {
if bytes.Equal(a, b) {
return nil
}
return fmt.Errorf("diff %s %s\n%s", aname, bname, diff.Diff(aname, a, bname, b))
return errors.New(string(diff.Diff(aname, a, bname, b)))
}
func runcheck(t *testing.T, source, golden string, mode checkMode) {

View File

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
// This is a package for testing comment placement by go/printer.
//
package main
import "fmt" // fmt
@ -97,6 +96,13 @@ type S3 struct {
f3 int // f3 is not exported
}
// Here is a comment.
// Here is an accidentally unindented line.
// More comment.
//
//dir:ect ive
type directiveCheck struct{}
// This comment group should be separated
// with a newline from the next comment
// group.
@ -116,9 +122,7 @@ func f0() {
x := pi
}
//
// This comment should be associated with f1, with one blank line before the comment.
//
func f1() {
f0()
/* 1 */
@ -691,6 +695,7 @@ func _() {
// Print line directives correctly.
// The following is a legal line directive.
//
//line foo:1
func _() {
_ = 0

View File

@ -97,6 +97,12 @@ type S3 struct {
f3 int // f3 is not exported
}
// Here is a comment.
//Here is an accidentally unindented line.
//dir:ect ive
// More comment.
type directiveCheck struct{}
// This comment group should be separated
// with a newline from the next comment
// group.
@ -616,7 +622,7 @@ func _() {
func _() {
f(); f()
f(); /* comment */ f()
f() /* comment */; f()
f() /* comment */; f()
f(); /* a */ /* b */ f()
f() /* a */ /* b */; f()
f() /* a */; /* b */ f()
@ -663,7 +669,7 @@ func _() {
// This way, commas interspersed in lists stay with the respective expression.
func f(x/* comment */, y int, z int /* comment */, u, v, w int /* comment */) {
f(x /* comment */, y)
f(x /* comment */,
f(x /* comment */,
y)
f(
x /* comment */,
@ -718,10 +724,10 @@ var lflag bool // -l - disable line directives
// Trailing white space in comments should be trimmed
func _() {
// This comment has 4 blanks following that should be trimmed:
/* Each line of this comment has blanks or tabs following that should be trimmed:
line 2:
line 3:
// This comment has 4 blanks following that should be trimmed:
/* Each line of this comment has blanks or tabs following that should be trimmed:
line 2:
line 3:
*/
}

View File

@ -1,5 +1,4 @@
// This is a package for testing comment placement by go/printer.
//
package main
// The SZ struct; it is empty.

View File

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
// This is a package for testing comment placement by go/printer.
//
package main
// Test cases for idempotent comment formatting (was issue 1835).

21
src/go/printer/testdata/doc.golden vendored Normal file
View File

@ -0,0 +1,21 @@
package p
/*
Doc comment.
- List1.
- List2.
*/
var X int
/* erroneous doc comment */
var Y int
/*
* Another erroneous
* doc comment.
*/
var Z int

20
src/go/printer/testdata/doc.input vendored Normal file
View File

@ -0,0 +1,20 @@
package p
/*
Doc comment.
- List1.
- List2.
*/
var X int
/* erroneous doc comment */
var Y int
/*
* Another erroneous
* doc comment.
*/
var Z int