| // 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 { |
| after, found := strings.CutPrefix(c.Text, "//") |
| if !found { |
| return list |
| } |
| // Accumulate //go:build etc lines separately. |
| if isDirective(after) { |
| directives = append(directives, c) |
| continue |
| } |
| b.WriteString(strings.TrimPrefix(after, " ")) |
| 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. |
| // "//extern " is for gccgo. |
| // "//export " is for cgo. |
| // (The // has been removed.) |
| if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") { |
| 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 |
| } |