github.com/jwilson-ts/prototool@v1.3.0/internal/format/main_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 "strings" 26 27 "github.com/emicklei/proto" 28 "github.com/uber/prototool/internal/text" 29 ) 30 31 var _ proto.Visitor = &mainVisitor{} 32 33 type mainVisitor struct { 34 *baseVisitor 35 36 isProto2 bool 37 haveHitNonComment bool 38 parent proto.Visitee 39 } 40 41 func newMainVisitor(isProto2 bool) *mainVisitor { 42 return &mainVisitor{isProto2: isProto2, baseVisitor: newBaseVisitor()} 43 } 44 45 func (v *mainVisitor) Do() []*text.Failure { 46 return v.Failures 47 } 48 49 func (v *mainVisitor) VisitMessage(element *proto.Message) { 50 v.haveHitNonComment = true 51 v.PComment(element.Comment) 52 prefix := "message " 53 if element.IsExtend { 54 prefix = "extend " 55 } 56 if len(element.Elements) == 0 { 57 v.P(prefix, element.Name, " {}") 58 v.P() 59 return 60 } 61 v.P(prefix, element.Name, " {") 62 v.In() 63 originalParent := v.parent 64 v.parent = element 65 for _, child := range element.Elements { 66 child.Accept(v) 67 } 68 v.parent = originalParent 69 v.Out() 70 v.P("}") 71 if v.parent == nil { 72 v.P() 73 } 74 } 75 76 func (v *mainVisitor) VisitService(element *proto.Service) { 77 v.haveHitNonComment = true 78 v.PComment(element.Comment) 79 if len(element.Elements) == 0 { 80 v.P("service ", element.Name, " {}") 81 v.P() 82 return 83 } 84 v.P("service ", element.Name, " {") 85 v.In() 86 originalParent := v.parent 87 v.parent = element 88 for _, child := range element.Elements { 89 child.Accept(v) 90 } 91 v.parent = originalParent 92 v.Out() 93 v.P("}") 94 v.P() 95 } 96 97 func (v *mainVisitor) VisitSyntax(element *proto.Syntax) { 98 // done in first pass visitor 99 v.haveHitNonComment = true 100 } 101 102 func (v *mainVisitor) VisitPackage(element *proto.Package) { 103 // done in first pass visitor 104 v.haveHitNonComment = true 105 } 106 107 func (v *mainVisitor) VisitOption(element *proto.Option) { 108 v.haveHitNonComment = true 109 // file options done in first pass visitor 110 if v.parent == nil { 111 return 112 } 113 switch v.parent.(type) { 114 case (*proto.Enum): 115 v.POptions(element) 116 case (*proto.Message): 117 v.POptions(element) 118 case (*proto.Oneof): 119 v.POptions(element) 120 case (*proto.Service): 121 v.POptions(element) 122 default: 123 v.AddFailure(element.Position, "unhandled child option") 124 } 125 } 126 127 func (v *mainVisitor) VisitImport(element *proto.Import) { 128 // done in first pass visitor 129 v.haveHitNonComment = true 130 } 131 132 func (v *mainVisitor) VisitNormalField(element *proto.NormalField) { 133 v.haveHitNonComment = true 134 prefix := "" 135 if element.Repeated { 136 prefix = "repeated " 137 } 138 if v.isProto2 { 139 // technically these are only set if the file is proto2 140 // but doing this just to make sure 141 if element.Required { 142 prefix = "required " 143 } else { 144 prefix = "optional " 145 } 146 } 147 v.PField(prefix, element.Type, element.Field) 148 } 149 150 func (v *mainVisitor) VisitEnumField(element *proto.EnumField) { 151 v.haveHitNonComment = true 152 v.PEnumField(element) 153 } 154 155 func (v *mainVisitor) VisitEnum(element *proto.Enum) { 156 v.haveHitNonComment = true 157 v.PComment(element.Comment) 158 if len(element.Elements) == 0 { 159 v.P("enum ", element.Name, " {}") 160 v.P() 161 return 162 } 163 v.P("enum ", element.Name, " {") 164 v.In() 165 originalParent := v.parent 166 v.parent = element 167 for _, child := range element.Elements { 168 child.Accept(v) 169 } 170 v.parent = originalParent 171 v.Out() 172 v.P("}") 173 if v.parent == nil { 174 v.P() 175 } 176 } 177 178 func (v *mainVisitor) VisitComment(element *proto.Comment) { 179 if v.haveHitNonComment { 180 v.PComment(element) 181 v.P() 182 } 183 } 184 185 func (v *mainVisitor) VisitOneof(element *proto.Oneof) { 186 v.haveHitNonComment = true 187 v.PComment(element.Comment) 188 if len(element.Elements) == 0 { 189 // TODO: is this even legal? 190 v.P("oneof ", element.Name, " {}") 191 return 192 } 193 v.P("oneof ", element.Name, " {") 194 v.In() 195 originalParent := v.parent 196 v.parent = element 197 for _, child := range element.Elements { 198 child.Accept(v) 199 } 200 v.parent = originalParent 201 v.Out() 202 v.P("}") 203 } 204 205 func (v *mainVisitor) VisitOneofField(element *proto.OneOfField) { 206 v.haveHitNonComment = true 207 v.PField("", element.Type, element.Field) 208 } 209 210 func (v *mainVisitor) VisitReserved(element *proto.Reserved) { 211 v.haveHitNonComment = true 212 if len(element.Ranges) > 0 && len(element.FieldNames) > 0 { 213 v.AddFailure(element.Position, "reserved had both integer ranges and field names which is unexpected") 214 return 215 } 216 v.PComment(element.Comment) 217 if len(element.Ranges) > 0 { 218 rangeStrings := make([]string, len(element.Ranges)) 219 for i, r := range element.Ranges { 220 rangeStrings[i] = r.SourceRepresentation() 221 } 222 v.PWithInlineComment(element.InlineComment, "reserved ", strings.Join(rangeStrings, ", "), ";") 223 return 224 } 225 if len(element.FieldNames) > 0 { 226 fieldNameStrings := make([]string, len(element.FieldNames)) 227 for i, fieldName := range element.FieldNames { 228 fieldNameStrings[i] = `"` + fieldName + `"` 229 } 230 v.PWithInlineComment(element.InlineComment, "reserved ", strings.Join(fieldNameStrings, ", "), ";") 231 } 232 } 233 234 func (v *mainVisitor) VisitRPC(element *proto.RPC) { 235 v.haveHitNonComment = true 236 v.PComment(element.Comment) 237 requestStream := "" 238 if element.StreamsRequest { 239 requestStream = "stream " 240 } 241 responseStream := "" 242 if element.StreamsReturns { 243 responseStream = "stream " 244 } 245 if len(element.Options) == 0 { 246 v.PWithInlineComment(element.InlineComment, "rpc ", element.Name, "(", requestStream, element.RequestType, ") returns (", responseStream, element.ReturnsType, ");") 247 return 248 } 249 v.P("rpc ", element.Name, "(", requestStream, element.RequestType, ") returns (", responseStream, element.ReturnsType, ") {") 250 v.In() 251 v.POptions(element.Options...) 252 v.Out() 253 v.PWithInlineComment(element.InlineComment, "}") 254 } 255 256 func (v *mainVisitor) VisitMapField(element *proto.MapField) { 257 v.haveHitNonComment = true 258 v.PField("", fmt.Sprintf("map<%s, %s>", element.KeyType, element.Type), element.Field) 259 } 260 261 func (v *mainVisitor) VisitGroup(element *proto.Group) { 262 v.haveHitNonComment = true 263 v.PComment(element.Comment) 264 prefix := "" 265 // TODO: required and repeated not handled yet, add when handled 266 if element.Optional { 267 prefix = "optional " 268 } 269 if len(element.Elements) == 0 { 270 v.P(prefix, "group ", element.Name, " = ", element.Sequence, " {}") 271 return 272 } 273 v.P(prefix, "group ", element.Name, " = ", element.Sequence, " {") 274 v.In() 275 originalParent := v.parent 276 v.parent = element 277 for _, child := range element.Elements { 278 child.Accept(v) 279 } 280 v.parent = originalParent 281 v.Out() 282 v.P("}") 283 } 284 285 func (v *mainVisitor) VisitExtensions(element *proto.Extensions) { 286 v.haveHitNonComment = true 287 v.PComment(element.Comment) 288 rangeStrings := make([]string, len(element.Ranges)) 289 for i, r := range element.Ranges { 290 rangeStrings[i] = r.SourceRepresentation() 291 } 292 v.PWithInlineComment(element.InlineComment, "extensions ", strings.Join(rangeStrings, ", "), ";") 293 }