github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/internal/addon/rules/orderRule.go (about) 1 package rules 2 3 import ( 4 "bytes" 5 6 "github.com/yoheimuta/go-protoparser/v4/parser" 7 "github.com/yoheimuta/go-protoparser/v4/parser/meta" 8 "github.com/yoheimuta/protolint/linter/report" 9 "github.com/yoheimuta/protolint/linter/rule" 10 "github.com/yoheimuta/protolint/linter/visitor" 11 ) 12 13 // OrderRule verifies that all files should be ordered in the following manner: 14 // 1. Syntax 15 // 2. Package 16 // 3. Imports (sorted) 17 // 4. File options 18 // 5. Everything else 19 // See https://developers.google.com/protocol-buffers/docs/style#file-structure. 20 type OrderRule struct { 21 RuleWithSeverity 22 fixMode bool 23 } 24 25 // NewOrderRule creates a new OrderRule. 26 func NewOrderRule( 27 severity rule.Severity, 28 fixMode bool, 29 ) OrderRule { 30 return OrderRule{ 31 RuleWithSeverity: RuleWithSeverity{severity: severity}, 32 fixMode: fixMode, 33 } 34 } 35 36 // ID returns the ID of this rule. 37 func (r OrderRule) ID() string { 38 return "ORDER" 39 } 40 41 // Purpose returns the purpose of this rule. 42 func (r OrderRule) Purpose() string { 43 return "Verifies that all files should be ordered in the specific manner." 44 } 45 46 // IsOfficial decides whether or not this rule belongs to the official guide. 47 func (r OrderRule) IsOfficial() bool { 48 return true 49 } 50 51 // Apply applies the rule to the proto. 52 func (r OrderRule) Apply(proto *parser.Proto) ([]report.Failure, error) { 53 base, err := visitor.NewBaseFixableVisitor(r.ID(), r.fixMode, proto, string(r.Severity())) 54 if err != nil { 55 return nil, err 56 } 57 58 v := &orderVisitor{ 59 BaseFixableVisitor: base, 60 state: initialOrderState, 61 machine: newOrderStateTransition(), 62 } 63 return visitor.RunVisitor(v, proto, r.ID()) 64 } 65 66 type orderVisitor struct { 67 *visitor.BaseFixableVisitor 68 state orderState 69 machine orderStateTransition 70 71 formatter formatter 72 } 73 74 func (v *orderVisitor) Finally() error { 75 if 0 < len(v.Failures()) { 76 shouldFixed := true 77 v.Fixer.ReplaceContent(func(content []byte) []byte { 78 newContent := v.formatter.format(content) 79 if bytes.Equal(content, newContent) { 80 shouldFixed = false 81 } 82 return newContent 83 }) 84 85 // TODO: BaseFixableVisitor.Finally should run the base Finally first, and then the fixing later. 86 if shouldFixed { 87 return v.BaseFixableVisitor.Finally() 88 } 89 } 90 return nil 91 } 92 93 func (v *orderVisitor) VisitSyntax(s *parser.Syntax) bool { 94 next := v.machine.transit(v.state, syntaxVisitEvent) 95 if next == invalidOrderState { 96 v.AddFailuref(s.Meta.Pos, "Syntax should be located at the top. Check if the file is ordered in the correct manner.") 97 } 98 v.state = syntaxOrderState 99 v.formatter.syntax = s 100 return false 101 } 102 103 func (v *orderVisitor) VisitPackage(p *parser.Package) bool { 104 next := v.machine.transit(v.state, packageVisitEvent) 105 if next == invalidOrderState { 106 v.AddFailuref(p.Meta.Pos, "The order of Package is invalid. Check if the file is ordered in the correct manner.") 107 } 108 v.state = packageOrderState 109 v.formatter.pkg = p 110 return false 111 } 112 113 func (v *orderVisitor) VisitImport(i *parser.Import) bool { 114 next := v.machine.transit(v.state, importsVisitEvent) 115 if next == invalidOrderState { 116 v.AddFailuref(i.Meta.Pos, "The order of Import is invalid. Check if the file is ordered in the correct manner.") 117 } 118 v.state = importsOrderState 119 v.formatter.addImports(i) 120 return false 121 } 122 123 func (v *orderVisitor) VisitOption(o *parser.Option) bool { 124 next := v.machine.transit(v.state, fileOptionsVisitEvent) 125 if next == invalidOrderState { 126 v.AddFailuref(o.Meta.Pos, "The order of Option is invalid. Check if the file is ordered in the correct manner.") 127 } 128 v.state = fileOptionsOrderState 129 v.formatter.addOptions(o) 130 return false 131 } 132 133 func (v *orderVisitor) VisitMessage(m *parser.Message) bool { 134 v.state = everythingElseOrderState 135 v.formatter.addMisc(m) 136 return false 137 } 138 139 func (v *orderVisitor) VisitEnum(e *parser.Enum) bool { 140 v.state = everythingElseOrderState 141 v.formatter.addMisc(e) 142 return false 143 } 144 145 func (v *orderVisitor) VisitService(s *parser.Service) bool { 146 v.state = everythingElseOrderState 147 v.formatter.addMisc(s) 148 return false 149 } 150 151 func (v *orderVisitor) VisitExtend(e *parser.Extend) bool { 152 v.state = everythingElseOrderState 153 v.formatter.addMisc(e) 154 return false 155 } 156 157 func (v *orderVisitor) VisitComment(c *parser.Comment) { 158 v.formatter.addComment(c) 159 } 160 161 // State Checker 162 type orderState int 163 164 const ( 165 invalidOrderState orderState = iota 166 initialOrderState 167 syntaxOrderState 168 packageOrderState 169 importsOrderState 170 fileOptionsOrderState 171 everythingElseOrderState 172 ) 173 174 type orderEvent int 175 176 const ( 177 syntaxVisitEvent orderEvent = iota 178 packageVisitEvent 179 importsVisitEvent 180 fileOptionsVisitEvent 181 ) 182 183 type orderInput struct { 184 state orderState 185 event orderEvent 186 } 187 188 type orderStateTransition struct { 189 f map[orderInput]orderState 190 } 191 192 func newOrderStateTransition() orderStateTransition { 193 return orderStateTransition{ 194 f: map[orderInput]orderState{ 195 { 196 state: initialOrderState, 197 event: syntaxVisitEvent, 198 }: syntaxOrderState, 199 200 { 201 state: initialOrderState, 202 event: packageVisitEvent, 203 }: packageOrderState, 204 { 205 state: syntaxOrderState, 206 event: packageVisitEvent, 207 }: packageOrderState, 208 209 { 210 state: initialOrderState, 211 event: importsVisitEvent, 212 }: importsOrderState, 213 { 214 state: syntaxOrderState, 215 event: importsVisitEvent, 216 }: importsOrderState, 217 { 218 state: packageOrderState, 219 event: importsVisitEvent, 220 }: importsOrderState, 221 { 222 state: importsOrderState, 223 event: importsVisitEvent, 224 }: importsOrderState, 225 226 { 227 state: initialOrderState, 228 event: fileOptionsVisitEvent, 229 }: fileOptionsOrderState, 230 { 231 state: syntaxOrderState, 232 event: fileOptionsVisitEvent, 233 }: fileOptionsOrderState, 234 { 235 state: packageOrderState, 236 event: fileOptionsVisitEvent, 237 }: fileOptionsOrderState, 238 { 239 state: importsOrderState, 240 event: fileOptionsVisitEvent, 241 }: fileOptionsOrderState, 242 { 243 state: fileOptionsOrderState, 244 event: fileOptionsVisitEvent, 245 }: fileOptionsOrderState, 246 }, 247 } 248 } 249 250 func (t orderStateTransition) transit( 251 state orderState, 252 event orderEvent, 253 ) orderState { 254 out, ok := t.f[orderInput{state: state, event: event}] 255 if !ok { 256 return invalidOrderState 257 } 258 return out 259 } 260 261 // Formatter 262 type indexedVisitee struct { 263 index int 264 visitee parser.Visitee 265 } 266 267 // NOTE: This check is not used at the moment. 268 // If no one requests to put the same element in a row as much as possible, 269 // we should delete this wrap struct, indexedVisitee. 270 func (i indexedVisitee) isContiguous(a indexedVisitee) bool { 271 return i.index-a.index == 1 272 } 273 274 type formatter struct { 275 syntax *parser.Syntax 276 pkg *parser.Package 277 imports []indexedVisitee 278 options []indexedVisitee 279 misc []indexedVisitee 280 comments []indexedVisitee 281 } 282 283 func (f formatter) index() int { 284 idx := 0 285 if f.syntax != nil { 286 idx = 1 287 } 288 if f.pkg != nil { 289 idx++ 290 } 291 return idx + len(f.imports) + len(f.options) + len(f.misc) 292 } 293 294 func (f *formatter) addImports(t *parser.Import) { 295 f.imports = append(f.imports, indexedVisitee{f.index(), t}) 296 } 297 298 func (f *formatter) addOptions(t *parser.Option) { 299 f.options = append(f.options, indexedVisitee{f.index(), t}) 300 } 301 302 func (f *formatter) addMisc(t parser.Visitee) { 303 f.misc = append(f.misc, indexedVisitee{f.index(), t}) 304 } 305 306 func (f *formatter) addComment(t parser.Visitee) { 307 f.comments = append(f.comments, indexedVisitee{f.index(), t}) 308 } 309 310 type line struct { 311 startPos meta.Position 312 endPos meta.Position 313 } 314 315 func newLine(meta meta.Meta, comments []*parser.Comment, inline *parser.Comment) line { 316 var l line 317 l.startPos = meta.Pos 318 if 0 < len(comments) { 319 l.startPos = comments[0].Meta.Pos 320 } 321 l.endPos = meta.LastPos 322 if inline != nil { 323 l.endPos = inline.Meta.LastPos 324 } 325 return l 326 } 327 328 func newVisiteeLine(elm parser.Visitee) line { 329 switch e := elm.(type) { 330 case *parser.Syntax: 331 return newLine(e.Meta, e.Comments, e.InlineComment) 332 case *parser.Package: 333 return newLine(e.Meta, e.Comments, e.InlineComment) 334 case *parser.Import: 335 return newLine(e.Meta, e.Comments, e.InlineComment) 336 case *parser.Option: 337 return newLine(e.Meta, e.Comments, e.InlineComment) 338 case *parser.Message: 339 return newLine(e.Meta, e.Comments, e.InlineComment) 340 case *parser.Extend: 341 return newLine(e.Meta, e.Comments, e.InlineComment) 342 case *parser.Enum: 343 return newLine(e.Meta, e.Comments, e.InlineComment) 344 case *parser.Service: 345 return newLine(e.Meta, e.Comments, e.InlineComment) 346 case *parser.Comment: 347 return newLine(e.Meta, []*parser.Comment{}, nil) 348 } 349 return line{} 350 } 351 352 func (l line) hasEmptyLine(prev line) bool { 353 return 1 < l.startPos.Line-prev.endPos.Line 354 } 355 356 type writer struct { 357 content []byte 358 newContent []byte 359 } 360 361 func (w *writer) write(l line) { 362 w.newContent = append(w.newContent, w.content[l.startPos.Offset:l.endPos.Offset+1]...) 363 } 364 365 func (w *writer) writeN(l line) { 366 w.write(l) 367 w.newContent = append(w.newContent, "\n"...) 368 } 369 370 func (w *writer) writeNN(l line) { 371 w.write(l) 372 w.newContent = append(w.newContent, "\n\n"...) 373 } 374 375 func (w *writer) writeOnlyN() { 376 w.newContent = append(w.newContent, "\n"...) 377 } 378 379 func (w *writer) removeLastRedundantN() { 380 if bytes.Equal(w.newContent[len(w.newContent)-2:len(w.newContent)], []byte("\n\n")) { 381 w.newContent = w.newContent[0 : len(w.newContent)-1] 382 } 383 } 384 385 func (f formatter) format(content []byte) []byte { 386 w := writer{content: content} 387 388 sl := newVisiteeLine(f.syntax) 389 w.writeNN(sl) 390 391 if f.pkg != nil { 392 pl := newVisiteeLine(f.pkg) 393 w.writeNN(pl) 394 } 395 396 visitees := [][]indexedVisitee{f.imports, f.options, f.misc, f.comments} 397 for i, vs := range visitees { 398 var ls []line 399 for _, elm := range vs { 400 ls = append(ls, newVisiteeLine(elm.visitee)) 401 } 402 403 for i, l := range ls { 404 // There are any empty lines between both ls 405 if 0 < i && l.hasEmptyLine(ls[i-1]) { 406 w.writeOnlyN() 407 } 408 w.writeN(l) 409 } 410 411 if 0 < len(ls) && i < len(visitees)-1 { 412 w.writeOnlyN() 413 } 414 } 415 416 w.removeLastRedundantN() 417 418 return w.newContent 419 }