github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/internal/addon/rules/fieldNamesLowerSnakeCaseRule.go (about) 1 package rules 2 3 import ( 4 "github.com/yoheimuta/go-protoparser/v4/lexer" 5 "github.com/yoheimuta/go-protoparser/v4/lexer/scanner" 6 "github.com/yoheimuta/go-protoparser/v4/parser" 7 "github.com/yoheimuta/protolint/linter/autodisable" 8 "github.com/yoheimuta/protolint/linter/fixer" 9 "github.com/yoheimuta/protolint/linter/rule" 10 11 "github.com/yoheimuta/protolint/linter/report" 12 "github.com/yoheimuta/protolint/linter/strs" 13 "github.com/yoheimuta/protolint/linter/visitor" 14 ) 15 16 // FieldNamesLowerSnakeCaseRule verifies that all field names are underscore_separated_names. 17 // See https://developers.google.com/protocol-buffers/docs/style#message-and-field-names. 18 type FieldNamesLowerSnakeCaseRule struct { 19 RuleWithSeverity 20 fixMode bool 21 autoDisableType autodisable.PlacementType 22 } 23 24 // NewFieldNamesLowerSnakeCaseRule creates a new FieldNamesLowerSnakeCaseRule. 25 func NewFieldNamesLowerSnakeCaseRule( 26 severity rule.Severity, 27 fixMode bool, 28 autoDisableType autodisable.PlacementType, 29 ) FieldNamesLowerSnakeCaseRule { 30 if autoDisableType != autodisable.Noop { 31 fixMode = false 32 } 33 return FieldNamesLowerSnakeCaseRule{ 34 RuleWithSeverity: RuleWithSeverity{severity: severity}, 35 fixMode: fixMode, 36 autoDisableType: autoDisableType, 37 } 38 } 39 40 // ID returns the ID of this rule. 41 func (r FieldNamesLowerSnakeCaseRule) ID() string { 42 return "FIELD_NAMES_LOWER_SNAKE_CASE" 43 } 44 45 // Purpose returns the purpose of this rule. 46 func (r FieldNamesLowerSnakeCaseRule) Purpose() string { 47 return "Verifies that all field names are underscore_separated_names." 48 } 49 50 // IsOfficial decides whether or not this rule belongs to the official guide. 51 func (r FieldNamesLowerSnakeCaseRule) IsOfficial() bool { 52 return true 53 } 54 55 // Apply applies the rule to the proto. 56 func (r FieldNamesLowerSnakeCaseRule) Apply(proto *parser.Proto) ([]report.Failure, error) { 57 base, err := visitor.NewBaseFixableVisitor(r.ID(), r.fixMode, proto, string(r.Severity())) 58 if err != nil { 59 return nil, err 60 } 61 62 v := &fieldNamesLowerSnakeCaseVisitor{ 63 BaseFixableVisitor: base, 64 } 65 return visitor.RunVisitorAutoDisable(v, proto, r.ID(), r.autoDisableType) 66 } 67 68 type fieldNamesLowerSnakeCaseVisitor struct { 69 *visitor.BaseFixableVisitor 70 } 71 72 // VisitField checks the field. 73 func (v *fieldNamesLowerSnakeCaseVisitor) VisitField(field *parser.Field) bool { 74 name := field.FieldName 75 if !strs.IsLowerSnakeCase(name) { 76 expected := strs.ToLowerSnakeCase(name) 77 v.AddFailuref(field.Meta.Pos, "Field name %q must be underscore_separated_names like %q", name, expected) 78 79 err := v.Fixer.SearchAndReplace(field.Meta.Pos, func(lex *lexer.Lexer) fixer.TextEdit { 80 lex.NextKeyword() 81 switch lex.Token { 82 case scanner.TREPEATED, scanner.TREQUIRED, scanner.TOPTIONAL: 83 default: 84 lex.UnNext() 85 } 86 parseType(lex) 87 lex.Next() 88 return fixer.TextEdit{ 89 Pos: lex.Pos.Offset, 90 End: lex.Pos.Offset + len(lex.Text) - 1, 91 NewText: []byte(expected), 92 } 93 }) 94 if err != nil { 95 panic(err) 96 } 97 } 98 return false 99 } 100 101 // VisitMapField checks the map field. 102 func (v *fieldNamesLowerSnakeCaseVisitor) VisitMapField(field *parser.MapField) bool { 103 name := field.MapName 104 if !strs.IsLowerSnakeCase(name) { 105 expected := strs.ToLowerSnakeCase(name) 106 v.AddFailuref(field.Meta.Pos, "Field name %q must be underscore_separated_names like %q", name, expected) 107 108 err := v.Fixer.SearchAndReplace(field.Meta.Pos, func(lex *lexer.Lexer) fixer.TextEdit { 109 lex.NextKeyword() 110 lex.Next() 111 lex.Next() 112 lex.Next() 113 parseType(lex) 114 lex.Next() 115 lex.Next() 116 return fixer.TextEdit{ 117 Pos: lex.Pos.Offset, 118 End: lex.Pos.Offset + len(lex.Text) - 1, 119 NewText: []byte(expected), 120 } 121 }) 122 if err != nil { 123 panic(err) 124 } 125 } 126 return false 127 } 128 129 // VisitOneofField checks the oneof field. 130 func (v *fieldNamesLowerSnakeCaseVisitor) VisitOneofField(field *parser.OneofField) bool { 131 name := field.FieldName 132 if !strs.IsLowerSnakeCase(name) { 133 expected := strs.ToLowerSnakeCase(name) 134 v.AddFailuref(field.Meta.Pos, "Field name %q must be underscore_separated_names like %q", name, expected) 135 136 err := v.Fixer.SearchAndReplace(field.Meta.Pos, func(lex *lexer.Lexer) fixer.TextEdit { 137 parseType(lex) 138 lex.Next() 139 return fixer.TextEdit{ 140 Pos: lex.Pos.Offset, 141 End: lex.Pos.Offset + len(lex.Text) - 1, 142 NewText: []byte(expected), 143 } 144 }) 145 if err != nil { 146 panic(err) 147 } 148 } 149 return false 150 } 151 152 // Below codes are copied from go-protoparser. 153 var typeConstants = map[string]struct{}{ 154 "double": {}, 155 "float": {}, 156 "int32": {}, 157 "int64": {}, 158 "uint32": {}, 159 "uint64": {}, 160 "sint32": {}, 161 "sint64": {}, 162 "fixed32": {}, 163 "fixed64": {}, 164 "sfixed32": {}, 165 "sfixed64": {}, 166 "bool": {}, 167 "string": {}, 168 "bytes": {}, 169 } 170 171 func parseType(lex *lexer.Lexer) { 172 lex.Next() 173 if _, ok := typeConstants[lex.Text]; ok { 174 return 175 } 176 lex.UnNext() 177 178 _, _, err := lex.ReadMessageType() 179 if err != nil { 180 panic(err) 181 } 182 }