github.com/ovsinc/prototool@v1.3.0/internal/format/base_visitor.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package format 22 23 import ( 24 "fmt" 25 "sort" 26 "strings" 27 "text/scanner" 28 29 "github.com/emicklei/proto" 30 "github.com/uber/prototool/internal/text" 31 ) 32 33 type baseVisitor struct { 34 *printer 35 36 Failures []*text.Failure 37 } 38 39 func newBaseVisitor() *baseVisitor { 40 return &baseVisitor{printer: newPrinter()} 41 } 42 43 func (v *baseVisitor) AddFailure(position scanner.Position, format string, args ...interface{}) { 44 v.Failures = append(v.Failures, &text.Failure{ 45 Line: position.Line, 46 Column: position.Column, 47 Message: fmt.Sprintf(format, args...), 48 }) 49 } 50 51 func (v *baseVisitor) PWithInlineComment(inlineComment *proto.Comment, args ...interface{}) { 52 if inlineComment == nil || len(inlineComment.Lines) == 0 { 53 v.P(args...) 54 return 55 } 56 // https://github.com/emicklei/proto/commit/5a91db7561a4dedab311f36304fcf0512343a9b1 57 args = append(args, ` //`, cleanCommentLine(inlineComment.Lines[0])) 58 v.P(args...) 59 for _, line := range inlineComment.Lines[1:] { 60 v.P(`//`, cleanCommentLine(line)) 61 } 62 } 63 64 func (v *baseVisitor) PComment(comment *proto.Comment) { 65 if comment == nil || len(comment.Lines) == 0 { 66 return 67 } 68 // https://github.com/emicklei/proto/commit/5a91db7561a4dedab311f36304fcf0512343a9b1 69 // this is weird for now 70 // we always want non-c-style after formatting 71 for _, line := range comment.Lines { 72 v.P(`//`, cleanCommentLine(line)) 73 } 74 } 75 76 func (v *baseVisitor) POptions(options ...*proto.Option) { 77 v.pOptions(false, options...) 78 } 79 80 // fieldType can be "" in case of enum field 81 func (v *baseVisitor) pMessageOrEnumField(prefix string, fieldName string, fieldType string, fieldTag int, comment *proto.Comment, inlineComment *proto.Comment, options ...*proto.Option) { 82 if fieldType != "" { 83 fieldType = fieldType + " " 84 } 85 v.PComment(comment) 86 if len(options) == 0 { 87 v.PWithInlineComment(inlineComment, prefix, fieldType, fieldName, " = ", fieldTag, ";") 88 return 89 } 90 if len(options) == 1 { 91 o := options[0] 92 if isSingleValueLiteral(o.Constant) { 93 if source := o.Constant.SourceRepresentation(); source != "" { 94 v.PWithInlineComment(inlineComment, prefix, fieldType, fieldName, " = ", fieldTag, " [", o.Name, ` = `, source, "];") 95 return 96 } 97 } 98 } 99 v.P(prefix, fieldType, fieldName, " = ", fieldTag, " [") 100 v.In() 101 v.pOptions(true, options...) 102 v.Out() 103 v.PWithInlineComment(inlineComment, "];") 104 } 105 106 func (v *baseVisitor) pOptions(isFieldOption bool, options ...*proto.Option) { 107 if len(options) == 0 { 108 return 109 } 110 sort.Slice(options, func(i int, j int) bool { return options[i].Name < options[j].Name }) 111 prefix := "option " 112 if isFieldOption { 113 prefix = "" 114 } 115 for i, o := range options { 116 suffix := ";" 117 if isFieldOption { 118 if len(options) > 1 && i != len(options)-1 { 119 suffix = "," 120 } else { 121 suffix = "" 122 } 123 } 124 v.PComment(o.Comment) 125 if isSingleValueLiteral(o.Constant) { 126 // SourceRepresentation() returns an empty string if the literal is empty 127 // if empty, we do not want to print the key or empty value 128 if source := o.Constant.SourceRepresentation(); source != "" { 129 v.PWithInlineComment(o.InlineComment, prefix, o.Name, ` = `, source, suffix) 130 } 131 } else if len(o.Constant.Array) > 0 { // both Array and OrderedMap should not be set simultaneously, need more followup with emicklei/proto 132 v.Failures = append( 133 v.Failures, 134 text.NewFailuref(o.Position, "INVALID_PROTOBUF", "top-level options should never be arrays, this should not compile with protoc"), 135 ) 136 } else { // len(o.Constant.OrderedMap) > 0 137 v.P(prefix, o.Name, ` = {`) 138 v.In() 139 for _, namedLiteral := range o.Constant.OrderedMap { 140 v.pInnerLiteral(namedLiteral.Name, *namedLiteral.Literal, "") 141 } 142 v.Out() 143 v.PWithInlineComment(o.InlineComment, `}`, suffix) 144 } 145 } 146 } 147 148 // should only be called by pOptions 149 func (v *baseVisitor) pInnerLiteral(name string, literal proto.Literal, suffix string) { 150 prefix := "" 151 if name != "" { 152 prefix = name + ": " 153 } 154 if isSingleValueLiteral(literal) { 155 // SourceRepresentation() returns an empty string if the literal is empty 156 // if empty, we do not want to print the key or empty value 157 if source := literal.SourceRepresentation(); source != "" { 158 v.P(prefix, source, suffix) 159 } 160 } else if len(literal.Array) > 0 { // both Array and OrderedMap should not be set simultaneously, need more followup with emicklei/proto 161 v.P(prefix, `[`) 162 v.In() 163 for i, iLiteral := range literal.Array { 164 iSuffix := "" 165 if len(literal.Array) > 1 && i != len(literal.Array)-1 { 166 iSuffix = "," 167 } 168 v.pInnerLiteral("", *iLiteral, iSuffix) 169 } 170 v.Out() 171 v.P(`]`, suffix) 172 } else { // len(literal.OrderedMap) > 0 173 v.P(prefix, `{`) 174 v.In() 175 for _, namedLiteral := range literal.OrderedMap { 176 v.pInnerLiteral(namedLiteral.Name, *namedLiteral.Literal, "") 177 } 178 v.Out() 179 v.P(`}`, suffix) 180 } 181 } 182 183 func (v *baseVisitor) PField(prefix string, fieldType string, field *proto.Field) { 184 v.pMessageOrEnumField(prefix, field.Name, fieldType, field.Sequence, field.Comment, field.InlineComment, field.Options...) 185 } 186 187 func isSingleValueLiteral(literal proto.Literal) bool { 188 // TODO: this is a good example of the reasoning for https://github.com/uber/prototool/issues/1 189 return len(literal.Array) == 0 && len(literal.OrderedMap) == 0 190 } 191 192 func (v *baseVisitor) PEnumField(element *proto.EnumField) { 193 if element.ValueOption == nil { 194 v.pMessageOrEnumField("", element.Name, "", element.Integer, element.Comment, element.InlineComment) 195 return 196 } 197 v.pMessageOrEnumField("", element.Name, "", element.Integer, element.Comment, element.InlineComment, element.ValueOption) 198 } 199 200 func cleanCommentLine(line string) string { 201 // TODO: this is not great 202 return strings.TrimLeft(line, "/") 203 }