Skip to content

Commit

Permalink
Showing 6 changed files with 132 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -171,7 +171,7 @@ See Fixable columns below.
| Yes | ✅ | INDENT | Enforces a consistent indentation style. The default style is 2 spaces. Inserting appropriate new lines is also forced by default. You can configure the detail with `.protolint.yaml`. |
| Yes | ✅ | PROTO3_FIELDS_AVOID_REQUIRED | Verifies that all fields should avoid required for proto3. |
| Yes | _ | PROTO3_GROUPS_AVOID | Verifies that all groups should be avoided for proto3. |
| Yes | _ | REPEATED_FIELD_NAMES_PLURALIZED | Verifies that repeated field names are pluralized names. |
| Yes | | REPEATED_FIELD_NAMES_PLURALIZED | Verifies that repeated field names are pluralized names. |
| Yes | ✅ | QUOTE_CONSISTENT | Verifies that the use of quote for strings is consistent. The default is double quoted. You can configure the specific quote with `.protolint.yaml`. |
| No | _ | SERVICE_NAMES_END_WITH | Enforces a consistent suffix for service names. You can configure the specific suffix with `.protolint.yaml`. |
| No | _ | FIELD_NAMES_EXCLUDE_PREPOSITIONS | Verifies that all field names don't include prepositions (e.g. "for", "during", "at"). You can configure the specific prepositions and excluded keywords with `.protolint.yaml`. |
18 changes: 18 additions & 0 deletions _testdata/rules/repeatedFieldNamesPluralized/invalid.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
syntax = "proto3";

message first_outer {
// inner is an inner message.
message first_inner { // Level 2
repeated inner innerMessage = 2;
}
repeated string My_map = 4;
group Result = 8 {
repeated string url = 9;
int64 amount = 10;
}
}

message second_outer {
repeated google.protobuf.Empty OneofEmpty = 20;
string oneof_String = 21;
}
18 changes: 18 additions & 0 deletions _testdata/rules/repeatedFieldNamesPluralized/pluralized.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
syntax = "proto3";

message first_outer {
// inner is an inner message.
message first_inner { // Level 2
repeated inner innerMessages = 2;
}
repeated string My_maps = 4;
group Result = 8 {
repeated string urls = 9;
int64 amount = 10;
}
}

message second_outer {
repeated google.protobuf.Empty OneofEmpties = 20;
string oneof_String = 21;
}
63 changes: 56 additions & 7 deletions internal/addon/rules/repeatedFieldNamesPluralizedRule.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package rules

import (
"github.com/yoheimuta/go-protoparser/v4/lexer"
"github.com/yoheimuta/go-protoparser/v4/lexer/scanner"
"github.com/yoheimuta/go-protoparser/v4/parser"
"github.com/yoheimuta/protolint/linter/fixer"
"github.com/yoheimuta/protolint/linter/report"
"github.com/yoheimuta/protolint/linter/strs"
"github.com/yoheimuta/protolint/linter/visitor"
@@ -14,6 +17,7 @@ type RepeatedFieldNamesPluralizedRule struct {
singularRules map[string]string
uncountableRules []string
irregularRules map[string]string
fixMode bool
}

// NewRepeatedFieldNamesPluralizedRule creates a new RepeatedFieldNamesPluralizedRule.
@@ -22,12 +26,14 @@ func NewRepeatedFieldNamesPluralizedRule(
singularRules map[string]string,
uncountableRules []string,
irregularRules map[string]string,
fixMode bool,
) RepeatedFieldNamesPluralizedRule {
return RepeatedFieldNamesPluralizedRule{
pluralRules: pluralRules,
singularRules: singularRules,
uncountableRules: uncountableRules,
irregularRules: irregularRules,
fixMode: fixMode,
}
}

@@ -62,34 +68,77 @@ func (r RepeatedFieldNamesPluralizedRule) Apply(proto *parser.Proto) ([]report.F
c.AddIrregularRule(k, v)
}

v := &repeatedFieldNamesPluralizedCaseVisitor{
BaseAddVisitor: visitor.NewBaseAddVisitor(r.ID()),
pluralizeClient: c,
base, err := visitor.NewBaseFixableVisitor(r.ID(), r.fixMode, proto)
if err != nil {
return nil, err
}

v := &repeatedFieldNamesPluralizedVisitor{
BaseFixableVisitor: base,
pluralizeClient: c,
}
return visitor.RunVisitor(v, proto, r.ID())
}

type repeatedFieldNamesPluralizedCaseVisitor struct {
*visitor.BaseAddVisitor
type repeatedFieldNamesPluralizedVisitor struct {
*visitor.BaseFixableVisitor
pluralizeClient *strs.PluralizeClient
}

// VisitField checks the field.
func (v *repeatedFieldNamesPluralizedCaseVisitor) VisitField(field *parser.Field) bool {
func (v *repeatedFieldNamesPluralizedVisitor) VisitField(field *parser.Field) bool {
got := field.FieldName
want := v.pluralizeClient.ToPlural(got)
if field.IsRepeated && got != want {
v.AddFailuref(field.Meta.Pos, "Repeated field name %q must be pluralized name %q", got, want)

err := v.Fixer.SearchAndReplace(field.Meta.Pos, func(lex *lexer.Lexer) fixer.TextEdit {
lex.NextKeyword()
switch lex.Token {
case scanner.TREPEATED, scanner.TREQUIRED, scanner.TOPTIONAL:
default:
lex.UnNext()
}
parseType(lex)
lex.Next()
return fixer.TextEdit{
Pos: lex.Pos.Offset,
End: lex.Pos.Offset + len(lex.Text) - 1,
NewText: []byte(want),
}
})
if err != nil {
panic(err)
}
}
return false
}

// VisitGroupField checks the group field.
func (v *repeatedFieldNamesPluralizedCaseVisitor) VisitGroupField(field *parser.GroupField) bool {
func (v *repeatedFieldNamesPluralizedVisitor) VisitGroupField(field *parser.GroupField) bool {
got := field.GroupName
want := v.pluralizeClient.ToPlural(got)
if field.IsRepeated && got != want {
v.AddFailuref(field.Meta.Pos, "Repeated group name %q must be pluralized name %q", got, want)

err := v.Fixer.SearchAndReplace(field.Meta.Pos, func(lex *lexer.Lexer) fixer.TextEdit {
lex.NextKeyword()
switch lex.Token {
case scanner.TREPEATED, scanner.TREQUIRED, scanner.TOPTIONAL:
default:
lex.UnNext()
}
lex.NextKeyword()
lex.Next()
return fixer.TextEdit{
Pos: lex.Pos.Offset,
End: lex.Pos.Offset + len(lex.Text) - 1,
NewText: []byte(want),
}
})
if err != nil {
panic(err)
}
}
return true
}
38 changes: 38 additions & 0 deletions internal/addon/rules/repeatedFieldNamesPluralizedRule_test.go
Original file line number Diff line number Diff line change
@@ -186,6 +186,7 @@ func TestRepeatedFieldNamesPluralizedRule_Apply(t *testing.T) {
test.singularRules,
test.uncountableRules,
test.irregularRules,
false,
)

got, err := rule.Apply(test.inputProto)
@@ -199,3 +200,40 @@ func TestRepeatedFieldNamesPluralizedRule_Apply(t *testing.T) {
})
}
}

func TestRepeatedFieldNamesPluralizedRule_Apply_fix(t *testing.T) {
tests := []struct {
name string
pluralRules map[string]string
singularRules map[string]string
uncountableRules []string
irregularRules map[string]string
inputFilename string
wantFilename string
}{
{
name: "no fix for a correct proto",
inputFilename: "pluralized.proto",
wantFilename: "pluralized.proto",
},
{
name: "fix for an incorrect proto",
inputFilename: "invalid.proto",
wantFilename: "pluralized.proto",
},
}

for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
r := rules.NewRepeatedFieldNamesPluralizedRule(
test.pluralRules,
test.singularRules,
test.uncountableRules,
test.irregularRules,
true,
)
testApplyFix(t, r, test.inputFilename, test.wantFilename)
})
}
}
1 change: 1 addition & 0 deletions internal/cmd/subcmds/rules.go
Original file line number Diff line number Diff line change
@@ -102,6 +102,7 @@ func newAllInternalRules(
repeatedFieldNamesPluralized.SingularRules,
repeatedFieldNamesPluralized.UncountableRules,
repeatedFieldNamesPluralized.IrregularRules,
fixMode,
),

rules.NewMessageNamesUpperCamelCaseRule(fixMode),

0 comments on commit 4f0e33f

Please sign in to comment.