|
- // Copyright (c) 2017, Google Inc.
- //
- // Permission to use, copy, modify, and/or distribute this software for any
- // purpose with or without fee is hereby granted, provided that the above
- // copyright notice and this permission notice appear in all copies.
- //
- // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
- // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
- // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
- package main
-
- import (
- "bytes"
- "fmt"
- "io/ioutil"
- "os"
- "strings"
- )
-
- // convert_comments.go converts C-style block comments to C++-style line
- // comments. A block comment is converted if all of the following are true:
- //
- // * The comment begins after the first blank line, to leave the license
- // blocks alone.
- //
- // * There are no characters between the '*/' and the end of the line.
- //
- // * Either one of the following are true:
- //
- // - The comment fits on one line.
- //
- // - Each line the comment spans begins with N spaces, followed by '/*' for
- // the initial line or ' *' for subsequent lines, where N is the same for
- // each line.
- //
- // This tool is a heuristic. While it gets almost all cases correct, the final
- // output should still be looked over and fixed up as needed.
-
- // allSpaces returns true if |s| consists entirely of spaces.
- func allSpaces(s string) bool {
- return strings.IndexFunc(s, func(r rune) bool { return r != ' ' }) == -1
- }
-
- // isContinuation returns true if |s| is a continuation line for a multi-line
- // comment indented to the specified column.
- func isContinuation(s string, column int) bool {
- if len(s) < column+2 {
- return false
- }
- if !allSpaces(s[:column]) {
- return false
- }
- return s[column:column+2] == " *"
- }
-
- // indexFrom behaves like strings.Index but only reports matches starting at
- // |idx|.
- func indexFrom(s, sep string, idx int) int {
- ret := strings.Index(s[idx:], sep)
- if ret < 0 {
- return -1
- }
- return idx + ret
- }
-
- // A lineGroup is a contiguous group of lines with an eligible comment at the
- // same column. Any trailing '*/'s will already be removed.
- type lineGroup struct {
- // column is the column where the eligible comment begins. line[column]
- // and line[column+1] will both be replaced with '/'. It is -1 if this
- // group is not to be converted.
- column int
- lines []string
- }
-
- func addLine(groups *[]lineGroup, line string, column int) {
- if len(*groups) == 0 || (*groups)[len(*groups)-1].column != column {
- *groups = append(*groups, lineGroup{column, nil})
- }
- (*groups)[len(*groups)-1].lines = append((*groups)[len(*groups)-1].lines, line)
- }
-
- // writeLine writes |line| to |out|, followed by a newline.
- func writeLine(out *bytes.Buffer, line string) {
- out.WriteString(line)
- out.WriteByte('\n')
- }
-
- func convertComments(path string, in []byte) []byte {
- lines := strings.Split(string(in), "\n")
-
- // Account for the trailing newline.
- if len(lines) > 0 && len(lines[len(lines)-1]) == 0 {
- lines = lines[:len(lines)-1]
- }
-
- // First pass: identify all comments to be converted. Group them into
- // lineGroups with the same column.
- var groups []lineGroup
-
- // Find the license block separator.
- for len(lines) > 0 {
- line := lines[0]
- lines = lines[1:]
- addLine(&groups, line, -1)
- if len(line) == 0 {
- break
- }
- }
-
- // inComment is true if we are in the middle of a comment.
- var inComment bool
- // comment is the currently buffered multi-line comment to convert. If
- // |inComment| is true and it is nil, the current multi-line comment is
- // not convertable and we copy lines to |out| as-is.
- var comment []string
- // column is the column offset of |comment|.
- var column int
- for len(lines) > 0 {
- line := lines[0]
- lines = lines[1:]
-
- var idx int
- if inComment {
- // Stop buffering if this comment isn't eligible.
- if comment != nil && !isContinuation(line, column) {
- for _, l := range comment {
- addLine(&groups, l, -1)
- }
- comment = nil
- }
-
- // Look for the end of the current comment.
- idx = strings.Index(line, "*/")
- if idx < 0 {
- if comment != nil {
- comment = append(comment, line)
- } else {
- addLine(&groups, line, -1)
- }
- continue
- }
-
- inComment = false
- if comment != nil {
- if idx == len(line)-2 {
- // This is a convertable multi-line comment.
- if idx >= column+2 {
- // |idx| may be equal to
- // |column| + 1, if the line is
- // a '*/' on its own. In that
- // case, we discard the line.
- comment = append(comment, line[:idx])
- }
- for _, l := range comment {
- addLine(&groups, l, column)
- }
- comment = nil
- continue
- }
-
- // Flush the buffered comment unmodified.
- for _, l := range comment {
- addLine(&groups, l, -1)
- }
- comment = nil
- }
- idx += 2
- }
-
- // Parse starting from |idx|, looking for either a convertable
- // line comment or a multi-line comment.
- for {
- idx = indexFrom(line, "/*", idx)
- if idx < 0 {
- addLine(&groups, line, -1)
- break
- }
-
- endIdx := indexFrom(line, "*/", idx)
- if endIdx < 0 {
- // The comment is, so far, eligible for conversion.
- inComment = true
- column = idx
- comment = []string{line}
- break
- }
-
- if endIdx != len(line)-2 {
- // Continue parsing for more comments in this line.
- idx = endIdx + 2
- continue
- }
-
- addLine(&groups, line[:endIdx], idx)
- break
- }
- }
-
- // Second pass: convert the lineGroups, adjusting spacing as needed.
- var out bytes.Buffer
- var lineNo int
- for _, group := range groups {
- if group.column < 0 {
- for _, line := range group.lines {
- writeLine(&out, line)
- }
- } else {
- // Google C++ style prefers two spaces before a comment
- // if it is on the same line as code, but clang-format
- // has been placing one space for block comments. All
- // comments within a group should be adjusted by the
- // same amount.
- var adjust string
- for _, line := range group.lines {
- if !allSpaces(line[:group.column]) && line[group.column-1] != '(' {
- if line[group.column-1] != ' ' {
- if len(adjust) < 2 {
- adjust = " "
- }
- } else if line[group.column-2] != ' ' {
- if len(adjust) < 1 {
- adjust = " "
- }
- }
- }
- }
-
- for i, line := range group.lines {
- newLine := fmt.Sprintf("%s%s//%s", line[:group.column], adjust, strings.TrimRight(line[group.column+2:], " "))
- if len(newLine) > 80 {
- fmt.Fprintf(os.Stderr, "%s:%d: Line is now longer than 80 characters\n", path, lineNo+i+1)
- }
- writeLine(&out, newLine)
- }
-
- }
- lineNo += len(group.lines)
- }
- return out.Bytes()
- }
-
- func main() {
- for _, arg := range os.Args[1:] {
- in, err := ioutil.ReadFile(arg)
- if err != nil {
- panic(err)
- }
- if err := ioutil.WriteFile(arg, convertComments(arg, in), 0666); err != nil {
- panic(err)
- }
- }
- }
|