github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/internal/addon/rules/repeatedFieldNamesPluralizedRule.go (about) 1 package rules 2 3 import ( 4 "strings" 5 6 "github.com/yoheimuta/go-protoparser/v4/lexer" 7 "github.com/yoheimuta/go-protoparser/v4/lexer/scanner" 8 "github.com/yoheimuta/go-protoparser/v4/parser" 9 "github.com/yoheimuta/protolint/linter/autodisable" 10 "github.com/yoheimuta/protolint/linter/fixer" 11 "github.com/yoheimuta/protolint/linter/report" 12 "github.com/yoheimuta/protolint/linter/rule" 13 "github.com/yoheimuta/protolint/linter/strs" 14 "github.com/yoheimuta/protolint/linter/visitor" 15 ) 16 17 // RepeatedFieldNamesPluralizedRule verifies that repeated field names are pluralized names. 18 // See https://developers.google.com/protocol-buffers/docs/style#repeated-fields. 19 type RepeatedFieldNamesPluralizedRule struct { 20 RuleWithSeverity 21 pluralRules map[string]string 22 singularRules map[string]string 23 uncountableRules []string 24 irregularRules map[string]string 25 fixMode bool 26 autoDisableType autodisable.PlacementType 27 } 28 29 // NewRepeatedFieldNamesPluralizedRule creates a new RepeatedFieldNamesPluralizedRule. 30 func NewRepeatedFieldNamesPluralizedRule( 31 severity rule.Severity, 32 pluralRules map[string]string, 33 singularRules map[string]string, 34 uncountableRules []string, 35 irregularRules map[string]string, 36 fixMode bool, 37 autoDisableType autodisable.PlacementType, 38 ) RepeatedFieldNamesPluralizedRule { 39 if autoDisableType != autodisable.Noop { 40 fixMode = false 41 } 42 return RepeatedFieldNamesPluralizedRule{ 43 RuleWithSeverity: RuleWithSeverity{severity: severity}, 44 pluralRules: pluralRules, 45 singularRules: singularRules, 46 uncountableRules: uncountableRules, 47 irregularRules: irregularRules, 48 fixMode: fixMode, 49 autoDisableType: autoDisableType, 50 } 51 } 52 53 // ID returns the ID of this rule. 54 func (r RepeatedFieldNamesPluralizedRule) ID() string { 55 return "REPEATED_FIELD_NAMES_PLURALIZED" 56 } 57 58 // Purpose returns the purpose of this rule. 59 func (r RepeatedFieldNamesPluralizedRule) Purpose() string { 60 return "Verifies that repeated field names are pluralized names." 61 } 62 63 // IsOfficial decides whether or not this rule belongs to the official guide. 64 func (r RepeatedFieldNamesPluralizedRule) IsOfficial() bool { 65 return true 66 } 67 68 // Apply applies the rule to the proto. 69 func (r RepeatedFieldNamesPluralizedRule) Apply(proto *parser.Proto) ([]report.Failure, error) { 70 c := strs.NewPluralizeClient() 71 for k, v := range r.pluralRules { 72 c.AddPluralRule(k, v) 73 } 74 for k, v := range r.singularRules { 75 c.AddSingularRule(k, v) 76 } 77 for _, w := range r.uncountableRules { 78 c.AddUncountableRule(w) 79 } 80 for k, v := range r.irregularRules { 81 c.AddIrregularRule(k, v) 82 } 83 84 base, err := visitor.NewBaseFixableVisitor(r.ID(), r.fixMode, proto, string(r.Severity())) 85 if err != nil { 86 return nil, err 87 } 88 89 v := &repeatedFieldNamesPluralizedVisitor{ 90 BaseFixableVisitor: base, 91 pluralizeClient: c, 92 } 93 return visitor.RunVisitorAutoDisable(v, proto, r.ID(), r.autoDisableType) 94 } 95 96 type repeatedFieldNamesPluralizedVisitor struct { 97 *visitor.BaseFixableVisitor 98 pluralizeClient *strs.PluralizeClient 99 } 100 101 // VisitField checks the field. 102 func (v *repeatedFieldNamesPluralizedVisitor) VisitField(field *parser.Field) bool { 103 got := field.FieldName 104 want := v.pluralizeClient.ToPlural(got) 105 if field.IsRepeated && strings.ToLower(got) != strings.ToLower(want) { 106 v.AddFailuref(field.Meta.Pos, "Repeated field name %q must be pluralized name %q", got, want) 107 108 err := v.Fixer.SearchAndReplace(field.Meta.Pos, func(lex *lexer.Lexer) fixer.TextEdit { 109 lex.NextKeyword() 110 switch lex.Token { 111 case scanner.TREPEATED, scanner.TREQUIRED, scanner.TOPTIONAL: 112 default: 113 lex.UnNext() 114 } 115 parseType(lex) 116 lex.Next() 117 return fixer.TextEdit{ 118 Pos: lex.Pos.Offset, 119 End: lex.Pos.Offset + len(lex.Text) - 1, 120 NewText: []byte(want), 121 } 122 }) 123 if err != nil { 124 panic(err) 125 } 126 } 127 return false 128 } 129 130 // VisitGroupField checks the group field. 131 func (v *repeatedFieldNamesPluralizedVisitor) VisitGroupField(field *parser.GroupField) bool { 132 got := field.GroupName 133 want := v.pluralizeClient.ToPlural(got) 134 if field.IsRepeated && strings.ToLower(got) != strings.ToLower(want) { 135 v.AddFailuref(field.Meta.Pos, "Repeated group name %q must be pluralized name %q", got, want) 136 137 err := v.Fixer.SearchAndReplace(field.Meta.Pos, func(lex *lexer.Lexer) fixer.TextEdit { 138 lex.NextKeyword() 139 switch lex.Token { 140 case scanner.TREPEATED, scanner.TREQUIRED, scanner.TOPTIONAL: 141 default: 142 lex.UnNext() 143 } 144 lex.NextKeyword() 145 lex.Next() 146 return fixer.TextEdit{ 147 Pos: lex.Pos.Offset, 148 End: lex.Pos.Offset + len(lex.Text) - 1, 149 NewText: []byte(want), 150 } 151 }) 152 if err != nil { 153 panic(err) 154 } 155 } 156 return true 157 }