xref: /aosp_15_r20/external/boringssl/src/util/convert_comments.go (revision 8fb009dc861624b67b6cdb62ea21f0f22d0c584b)
1// Copyright (c) 2017, Google Inc.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15//go:build ignore
16
17package main
18
19import (
20	"bytes"
21	"fmt"
22	"os"
23	"strings"
24)
25
26// convert_comments.go converts C-style block comments to C++-style line
27// comments. A block comment is converted if all of the following are true:
28//
29//   * The comment begins after the first blank line, to leave the license
30//     blocks alone.
31//
32//   * There are no characters between the '*/' and the end of the line.
33//
34//   * Either one of the following are true:
35//
36//     - The comment fits on one line.
37//
38//     - Each line the comment spans begins with N spaces, followed by '/*' for
39//       the initial line or ' *' for subsequent lines, where N is the same for
40//       each line.
41//
42// This tool is a heuristic. While it gets almost all cases correct, the final
43// output should still be looked over and fixed up as needed.
44
45// allSpaces returns true if |s| consists entirely of spaces.
46func allSpaces(s string) bool {
47	return strings.IndexFunc(s, func(r rune) bool { return r != ' ' }) == -1
48}
49
50// isContinuation returns true if |s| is a continuation line for a multi-line
51// comment indented to the specified column.
52func isContinuation(s string, column int) bool {
53	if len(s) < column+2 {
54		return false
55	}
56	if !allSpaces(s[:column]) {
57		return false
58	}
59	return s[column:column+2] == " *"
60}
61
62// indexFrom behaves like strings.Index but only reports matches starting at
63// |idx|.
64func indexFrom(s, sep string, idx int) int {
65	ret := strings.Index(s[idx:], sep)
66	if ret < 0 {
67		return -1
68	}
69	return idx + ret
70}
71
72// A lineGroup is a contiguous group of lines with an eligible comment at the
73// same column. Any trailing '*/'s will already be removed.
74type lineGroup struct {
75	// column is the column where the eligible comment begins. line[column]
76	// and line[column+1] will both be replaced with '/'. It is -1 if this
77	// group is not to be converted.
78	column int
79	lines  []string
80}
81
82func addLine(groups *[]lineGroup, line string, column int) {
83	if len(*groups) == 0 || (*groups)[len(*groups)-1].column != column {
84		*groups = append(*groups, lineGroup{column, nil})
85	}
86	(*groups)[len(*groups)-1].lines = append((*groups)[len(*groups)-1].lines, line)
87}
88
89// writeLine writes |line| to |out|, followed by a newline.
90func writeLine(out *bytes.Buffer, line string) {
91	out.WriteString(line)
92	out.WriteByte('\n')
93}
94
95func convertComments(path string, in []byte) []byte {
96	lines := strings.Split(string(in), "\n")
97
98	// Account for the trailing newline.
99	if len(lines) > 0 && len(lines[len(lines)-1]) == 0 {
100		lines = lines[:len(lines)-1]
101	}
102
103	// First pass: identify all comments to be converted. Group them into
104	// lineGroups with the same column.
105	var groups []lineGroup
106
107	// Find the license block separator.
108	for len(lines) > 0 {
109		line := lines[0]
110		lines = lines[1:]
111		addLine(&groups, line, -1)
112		if len(line) == 0 {
113			break
114		}
115	}
116
117	// inComment is true if we are in the middle of a comment.
118	var inComment bool
119	// comment is the currently buffered multi-line comment to convert. If
120	// |inComment| is true and it is nil, the current multi-line comment is
121	// not convertable and we copy lines to |out| as-is.
122	var comment []string
123	// column is the column offset of |comment|.
124	var column int
125	for len(lines) > 0 {
126		line := lines[0]
127		lines = lines[1:]
128
129		var idx int
130		if inComment {
131			// Stop buffering if this comment isn't eligible.
132			if comment != nil && !isContinuation(line, column) {
133				for _, l := range comment {
134					addLine(&groups, l, -1)
135				}
136				comment = nil
137			}
138
139			// Look for the end of the current comment.
140			idx = strings.Index(line, "*/")
141			if idx < 0 {
142				if comment != nil {
143					comment = append(comment, line)
144				} else {
145					addLine(&groups, line, -1)
146				}
147				continue
148			}
149
150			inComment = false
151			if comment != nil {
152				if idx == len(line)-2 {
153					// This is a convertable multi-line comment.
154					if idx >= column+2 {
155						// |idx| may be equal to
156						// |column| + 1, if the line is
157						// a '*/' on its own. In that
158						// case, we discard the line.
159						comment = append(comment, line[:idx])
160					}
161					for _, l := range comment {
162						addLine(&groups, l, column)
163					}
164					comment = nil
165					continue
166				}
167
168				// Flush the buffered comment unmodified.
169				for _, l := range comment {
170					addLine(&groups, l, -1)
171				}
172				comment = nil
173			}
174			idx += 2
175		}
176
177		// Parse starting from |idx|, looking for either a convertable
178		// line comment or a multi-line comment.
179		for {
180			idx = indexFrom(line, "/*", idx)
181			if idx < 0 {
182				addLine(&groups, line, -1)
183				break
184			}
185
186			endIdx := indexFrom(line, "*/", idx)
187			if endIdx < 0 {
188				// The comment is, so far, eligible for conversion.
189				inComment = true
190				column = idx
191				comment = []string{line}
192				break
193			}
194
195			if endIdx != len(line)-2 {
196				// Continue parsing for more comments in this line.
197				idx = endIdx + 2
198				continue
199			}
200
201			addLine(&groups, line[:endIdx], idx)
202			break
203		}
204	}
205
206	// Second pass: convert the lineGroups, adjusting spacing as needed.
207	var out bytes.Buffer
208	var lineNo int
209	for _, group := range groups {
210		if group.column < 0 {
211			for _, line := range group.lines {
212				writeLine(&out, line)
213			}
214		} else {
215			// Google C++ style prefers two spaces before a comment
216			// if it is on the same line as code, but clang-format
217			// has been placing one space for block comments. All
218			// comments within a group should be adjusted by the
219			// same amount.
220			var adjust string
221			for _, line := range group.lines {
222				if !allSpaces(line[:group.column]) && line[group.column-1] != '(' {
223					if line[group.column-1] != ' ' {
224						if len(adjust) < 2 {
225							adjust = "  "
226						}
227					} else if line[group.column-2] != ' ' {
228						if len(adjust) < 1 {
229							adjust = " "
230						}
231					}
232				}
233			}
234
235			for i, line := range group.lines {
236				// The OpenSSL style writes multiline block comments with a
237				// blank line at the top and bottom, like so:
238				//
239				//   /*
240				//    * Some multi-line
241				//    * comment
242				//    */
243				//
244				// The trailing lines are already removed above, when buffering.
245				// Remove the leading lines here. (The leading lines cannot be
246				// removed when buffering because we may discover the comment is
247				// not convertible in later lines.)
248				//
249				// Note the leading line cannot be easily removed if there is
250				// code before it, such as the following. Skip those cases.
251				//
252				//   foo(); /*
253				//           * Some multi-line
254				//           * comment
255				//           */
256				if i == 0 && allSpaces(line[:group.column]) && len(line) == group.column+2 {
257					continue
258				}
259				newLine := fmt.Sprintf("%s%s//%s", line[:group.column], adjust, strings.TrimRight(line[group.column+2:], " "))
260				if len(newLine) > 80 {
261					fmt.Fprintf(os.Stderr, "%s:%d: Line is now longer than 80 characters\n", path, lineNo+i+1)
262				}
263				writeLine(&out, newLine)
264			}
265
266		}
267		lineNo += len(group.lines)
268	}
269	return out.Bytes()
270}
271
272func main() {
273	for _, arg := range os.Args[1:] {
274		in, err := os.ReadFile(arg)
275		if err != nil {
276			panic(err)
277		}
278		if err := os.WriteFile(arg, convertComments(arg, in), 0666); err != nil {
279			panic(err)
280		}
281	}
282}
283