1 // Copyright 2022 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4 5 package printer
6 7 import (
8 "go/ast"
9 "go/doc/comment"
10 "bytes"
11 )
12 13 // formatDocComment reformats the doc comment list,
14 // returning the canonical formatting.
15 func formatDocComment(list []*ast.Comment) []*ast.Comment {
16 // Extract comment text (removing comment markers).
17 var kind, text []byte
18 var directives []*ast.Comment
19 if len(list) == 1 && bytes.HasPrefix(list[0].Text, "/*") {
20 kind = "/*"
21 text = list[0].Text
22 if !bytes.Contains(text, "\n") || allStars(text) {
23 // Single-line /* .. */ comment in doc comment position,
24 // or multiline old-style comment like
25 // /*
26 // * Comment
27 // * text here.
28 // */
29 // Should not happen, since it will not work well as a
30 // doc comment, but if it does, just ignore:
31 // reformatting it will only make the situation worse.
32 return list
33 }
34 text = text[2 : len(text)-2] // cut /* and */
35 } else if bytes.HasPrefix(list[0].Text, "//") {
36 kind = "//"
37 var b bytes.Buffer
38 for _, c := range list {
39 after, found := bytes.CutPrefix(c.Text, "//")
40 if !found {
41 return list
42 }
43 // Accumulate //go:build etc lines separately.
44 if isDirective(after) {
45 directives = append(directives, c)
46 continue
47 }
48 b.WriteString(bytes.TrimPrefix(after, " "))
49 b.WriteString("\n")
50 }
51 text = b.String()
52 } else {
53 // Not sure what this is, so leave alone.
54 return list
55 }
56 57 if text == "" {
58 return list
59 }
60 61 // Parse comment and reformat as text.
62 var p comment.Parser
63 d := p.Parse(text)
64 65 var pr comment.Printer
66 text = []byte(pr.Comment(d))
67 68 // For /* */ comment, return one big comment with text inside.
69 slash := list[0].Slash
70 if kind == "/*" {
71 c := &ast.Comment{
72 Slash: slash,
73 Text: "/*\n" + text + "*/",
74 }
75 return []*ast.Comment{c}
76 }
77 78 // For // comment, return sequence of // lines.
79 var out []*ast.Comment
80 for text != "" {
81 var line []byte
82 line, text, _ = bytes.Cut(text, "\n")
83 if line == "" {
84 line = "//"
85 } else if bytes.HasPrefix(line, "\t") {
86 line = "//" + line
87 } else {
88 line = "// " + line
89 }
90 out = append(out, &ast.Comment{
91 Slash: slash,
92 Text: line,
93 })
94 }
95 if len(directives) > 0 {
96 out = append(out, &ast.Comment{
97 Slash: slash,
98 Text: "//",
99 })
100 for _, c := range directives {
101 out = append(out, &ast.Comment{
102 Slash: slash,
103 Text: c.Text,
104 })
105 }
106 }
107 return out
108 }
109 110 // isDirective reports whether c is a comment directive.
111 // See go.dev/issue/37974.
112 // This code is also in go/ast.
113 func isDirective(c []byte) bool {
114 // "//line " is a line directive.
115 // "//extern " is for gccgo.
116 // "//export " is for cgo.
117 // (The // has been removed.)
118 if bytes.HasPrefix(c, "line ") || bytes.HasPrefix(c, "extern ") || bytes.HasPrefix(c, "export ") {
119 return true
120 }
121 122 // "//[a-z0-9]+:[a-z0-9]"
123 // (The // has been removed.)
124 colon := bytes.Index(c, ":")
125 if colon <= 0 || colon+1 >= len(c) {
126 return false
127 }
128 for i := 0; i <= colon+1; i++ {
129 if i == colon {
130 continue
131 }
132 b := c[i]
133 if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') {
134 return false
135 }
136 }
137 return true
138 }
139 140 // allStars reports whether text is the interior of an
141 // old-style /* */ comment with a star at the start of each line.
142 func allStars(text []byte) bool {
143 for i := 0; i < len(text); i++ {
144 if text[i] == '\n' {
145 j := i + 1
146 for j < len(text) && (text[j] == ' ' || text[j] == '\t') {
147 j++
148 }
149 if j < len(text) && text[j] != '*' {
150 return false
151 }
152 }
153 }
154 return true
155 }
156