Source file src/go/printer/comment.go

     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  	"strings"
    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 string
    18  	var directives []*ast.Comment
    19  	if len(list) == 1 && strings.HasPrefix(list[0].Text, "/*") {
    20  		kind = "/*"
    21  		text = list[0].Text
    22  		if !strings.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 strings.HasPrefix(list[0].Text, "//") {
    36  		kind = "//"
    37  		var b strings.Builder
    38  		for _, c := range list {
    39  			after, found := strings.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(strings.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 = string(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 string
    82  		line, text, _ = strings.Cut(text, "\n")
    83  		if line == "" {
    84  			line = "//"
    85  		} else if strings.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 string) bool {
   114  	// "//line " is a line directive.
   115  	// "//extern " is for gccgo.
   116  	// "//export " is for cgo.
   117  	// (The // has been removed.)
   118  	if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") {
   119  		return true
   120  	}
   121  
   122  	// "//[a-z0-9]+:[a-z0-9]"
   123  	// (The // has been removed.)
   124  	colon := strings.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 string) 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  

View as plain text