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  }