]> Cypherpunks repositories - gostls13.git/commitdiff
go/printer: format doc comments
authorRuss Cox <rsc@golang.org>
Sat, 29 Jan 2022 23:25:41 +0000 (18:25 -0500)
committerRuss Cox <rsc@golang.org>
Mon, 11 Apr 2022 16:31:50 +0000 (16:31 +0000)
[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>
src/go/ast/ast.go
src/go/printer/comment.go [new file with mode: 0644]
src/go/printer/printer.go
src/go/printer/printer_test.go
src/go/printer/testdata/comments.golden
src/go/printer/testdata/comments.input
src/go/printer/testdata/comments.x
src/go/printer/testdata/comments2.golden
src/go/printer/testdata/doc.golden [new file with mode: 0644]
src/go/printer/testdata/doc.input [new file with mode: 0644]

index 61855359f86b6ad8e8bc10f571d3419c8748785c..8d467a78284ee933f06bae03e6b0e34ebba2e352 100644 (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.)
diff --git a/src/go/printer/comment.go b/src/go/printer/comment.go
new file mode 100644 (file)
index 0000000..9749146
--- /dev/null
@@ -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
+}
index 5014f59ab5c5d585b7fa3376b855d89d5b06f899..25eec6bd751a2d82ad03eef06ef5819035d127d4 100644 (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()
        }
 
index ad2d86052a460efcd4dca0d6742bcbedd17d6815..cb62b3e4f35db0fa78377dec4d7a318c27503ecb 100644 (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) {
index 1a21fff331473753c408478c794099348c8e140e..d03da3b65afd029825be36fc2adcff8e674b3229 100644 (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
index aa428a2aa68bfe4838fa5401f5e3b5d816a4e412..2a15fa44a5dd93f57d04f38844301ec7425e8003 100644 (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:
 */
 }
 
index ae7729286e5a63aae5c784d1e16d6b5973e66612..5d088ab2c3802a9ef3b5b7a9d8af36dfcf7b2346 100644 (file)
@@ -1,5 +1,4 @@
 // This is a package for testing comment placement by go/printer.
-//
 package main
 
 // The SZ struct; it is empty.
index 8b3a94ddcd03a0ac75ab7e6c2bbfa5dbb88c2b95..83213d1a9db50cf7977037581bc62eeb5edcb0a8 100644 (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).
diff --git a/src/go/printer/testdata/doc.golden b/src/go/printer/testdata/doc.golden
new file mode 100644 (file)
index 0000000..7ac241a
--- /dev/null
@@ -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
+
+
diff --git a/src/go/printer/testdata/doc.input b/src/go/printer/testdata/doc.input
new file mode 100644 (file)
index 0000000..5c057ed
--- /dev/null
@@ -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
+
+