github.com/nikron/prototool@v1.3.0/internal/format/first_pass_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 "sort" 25 26 "github.com/emicklei/proto" 27 "github.com/uber/prototool/internal/protostrs" 28 "github.com/uber/prototool/internal/text" 29 ) 30 31 var _ proto.Visitor = &firstPassVisitor{} 32 33 type firstPassVisitor struct { 34 *baseVisitor 35 36 Syntax *proto.Syntax 37 Package *proto.Package 38 Options []*proto.Option 39 Imports []*proto.Import 40 41 haveHitNonComment bool 42 43 filename string 44 fix bool 45 goPackageOption *proto.Option 46 javaMultipleFilesOption *proto.Option 47 javaOuterClassnameOption *proto.Option 48 javaPackageOption *proto.Option 49 } 50 51 func newFirstPassVisitor(filename string, fix bool) *firstPassVisitor { 52 return &firstPassVisitor{baseVisitor: newBaseVisitor(), filename: filename, fix: fix} 53 } 54 55 func (v *firstPassVisitor) Do() []*text.Failure { 56 if v.Syntax != nil { 57 v.PComment(v.Syntax.Comment) 58 if v.Syntax.Comment != nil { 59 // special case, we add a newline in between the first comment and syntax 60 // to separate licenses, file descriptions, etc. 61 v.P() 62 } 63 v.PWithInlineComment(v.Syntax.InlineComment, `syntax = "`, v.Syntax.Value, `";`) 64 v.P() 65 } 66 if v.Package != nil { 67 v.PComment(v.Package.Comment) 68 v.PWithInlineComment(v.Package.InlineComment, `package `, v.Package.Name, `;`) 69 v.P() 70 } 71 if v.fix && v.Package != nil { 72 if v.goPackageOption == nil { 73 v.goPackageOption = &proto.Option{Name: "go_package"} 74 } 75 if v.javaMultipleFilesOption == nil { 76 v.javaMultipleFilesOption = &proto.Option{Name: "java_multiple_files"} 77 } 78 if v.javaOuterClassnameOption == nil { 79 v.javaOuterClassnameOption = &proto.Option{Name: "java_outer_classname"} 80 } 81 if v.javaPackageOption == nil { 82 v.javaPackageOption = &proto.Option{Name: "java_package"} 83 } 84 v.goPackageOption.Constant = proto.Literal{ 85 Source: protostrs.GoPackage(v.Package.Name), 86 IsString: true, 87 } 88 v.javaMultipleFilesOption.Constant = proto.Literal{ 89 Source: "true", 90 } 91 v.javaOuterClassnameOption.Constant = proto.Literal{ 92 Source: protostrs.JavaOuterClassname(v.filename), 93 IsString: true, 94 } 95 v.javaPackageOption.Constant = proto.Literal{ 96 Source: protostrs.JavaPackage(v.Package.Name), 97 IsString: true, 98 } 99 v.Options = append( 100 v.Options, 101 v.goPackageOption, 102 v.javaMultipleFilesOption, 103 v.javaOuterClassnameOption, 104 v.javaPackageOption, 105 ) 106 } 107 if len(v.Options) > 0 { 108 v.POptions(v.Options...) 109 v.P() 110 } 111 if len(v.Imports) > 0 { 112 v.PImports(v.Imports) 113 v.P() 114 } 115 return v.Failures 116 } 117 118 func (v *firstPassVisitor) VisitMessage(element *proto.Message) { 119 v.haveHitNonComment = true 120 } 121 122 func (v *firstPassVisitor) VisitService(element *proto.Service) { 123 v.haveHitNonComment = true 124 } 125 126 func (v *firstPassVisitor) VisitSyntax(element *proto.Syntax) { 127 v.haveHitNonComment = true 128 if v.Syntax != nil { 129 v.AddFailure(element.Position, "duplicate syntax specified") 130 return 131 } 132 v.Syntax = element 133 } 134 135 func (v *firstPassVisitor) VisitPackage(element *proto.Package) { 136 v.haveHitNonComment = true 137 if v.Package != nil { 138 v.AddFailure(element.Position, "duplicate package specified") 139 return 140 } 141 v.Package = element 142 } 143 144 func (v *firstPassVisitor) VisitOption(element *proto.Option) { 145 // this will only hit file options since we don't do any 146 // visiting of children in this visitor 147 v.haveHitNonComment = true 148 if v.fix { 149 switch element.Name { 150 case "go_package": 151 v.goPackageOption = element 152 return 153 case "java_multiple_files": 154 v.javaMultipleFilesOption = element 155 return 156 case "java_outer_classname": 157 v.javaOuterClassnameOption = element 158 return 159 case "java_package": 160 v.javaPackageOption = element 161 return 162 } 163 } 164 v.Options = append(v.Options, element) 165 } 166 167 func (v *firstPassVisitor) VisitImport(element *proto.Import) { 168 v.haveHitNonComment = true 169 v.Imports = append(v.Imports, element) 170 } 171 172 func (v *firstPassVisitor) VisitNormalField(element *proto.NormalField) { 173 v.haveHitNonComment = true 174 } 175 176 func (v *firstPassVisitor) VisitEnumField(element *proto.EnumField) { 177 v.haveHitNonComment = true 178 } 179 180 func (v *firstPassVisitor) VisitEnum(element *proto.Enum) { 181 v.haveHitNonComment = true 182 } 183 184 func (v *firstPassVisitor) VisitComment(element *proto.Comment) { 185 // We only print file-level comments before syntax, package, file-level options, 186 // or package if they are at the top of the file 187 if !v.haveHitNonComment { 188 v.PComment(element) 189 v.P() 190 } 191 } 192 193 func (v *firstPassVisitor) VisitOneof(element *proto.Oneof) { 194 v.haveHitNonComment = true 195 } 196 197 func (v *firstPassVisitor) VisitOneofField(element *proto.OneOfField) { 198 v.haveHitNonComment = true 199 } 200 201 func (v *firstPassVisitor) VisitReserved(element *proto.Reserved) { 202 v.haveHitNonComment = true 203 } 204 205 func (v *firstPassVisitor) VisitRPC(element *proto.RPC) { 206 v.haveHitNonComment = true 207 } 208 209 func (v *firstPassVisitor) VisitMapField(element *proto.MapField) { 210 v.haveHitNonComment = true 211 } 212 213 func (v *firstPassVisitor) VisitGroup(element *proto.Group) { 214 v.haveHitNonComment = true 215 } 216 217 func (v *firstPassVisitor) VisitExtensions(element *proto.Extensions) { 218 v.haveHitNonComment = true 219 } 220 221 func (v *firstPassVisitor) PImports(imports []*proto.Import) { 222 if len(imports) == 0 { 223 return 224 } 225 sort.Slice(imports, func(i int, j int) bool { return imports[i].Filename < imports[j].Filename }) 226 for _, i := range imports { 227 v.PComment(i.Comment) 228 // kind can be "weak", "public", or empty 229 // if weak or public, just print it out but with a space afterwards 230 // otherwise do not print anything 231 // https://developers.google.com/protocol-buffers/docs/reference/proto3-spec#import_statement 232 kind := i.Kind 233 if kind != "" { 234 kind = kind + " " 235 } 236 v.PWithInlineComment(i.InlineComment, `import `, kind, `"`, i.Filename, `";`) 237 } 238 }