github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/internal/addon/rules/fieldNamesExcludePrepositionsRule.go (about)

     1  package rules
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/yoheimuta/go-protoparser/v4/parser"
     7  
     8  	"github.com/yoheimuta/protolint/internal/stringsutil"
     9  	"github.com/yoheimuta/protolint/linter/report"
    10  	"github.com/yoheimuta/protolint/linter/rule"
    11  	"github.com/yoheimuta/protolint/linter/strs"
    12  	"github.com/yoheimuta/protolint/linter/visitor"
    13  )
    14  
    15  // Default values are a conservative list picked out from all preposition candidates.
    16  // See https://www.talkenglish.com/vocabulary/top-50-prepositions.aspx
    17  var defaultPrepositions = []string{
    18  	"of",
    19  	"with",
    20  	"at",
    21  	"from",
    22  	"into",
    23  	"during",
    24  	"including",
    25  	"until",
    26  	"against",
    27  	"among",
    28  	"throughout",
    29  	"despite",
    30  	"towards",
    31  	"upon",
    32  	"concerning",
    33  
    34  	"to",
    35  	"in",
    36  	"for",
    37  	"on",
    38  	"by",
    39  	"about",
    40  }
    41  
    42  // FieldNamesExcludePrepositionsRule verifies that all field names don't include prepositions (e.g. "for", "during", "at").
    43  // It is assumed that the field names are underscore_separated_names.
    44  // See https://cloud.google.com/apis/design/naming_convention#field_names.
    45  type FieldNamesExcludePrepositionsRule struct {
    46  	RuleWithSeverity
    47  	prepositions []string
    48  	excludes     []string
    49  }
    50  
    51  // NewFieldNamesExcludePrepositionsRule creates a new FieldNamesExcludePrepositionsRule.
    52  func NewFieldNamesExcludePrepositionsRule(
    53  	severity rule.Severity,
    54  	prepositions []string,
    55  	excludes []string,
    56  ) FieldNamesExcludePrepositionsRule {
    57  	if len(prepositions) == 0 {
    58  		prepositions = defaultPrepositions
    59  	}
    60  	return FieldNamesExcludePrepositionsRule{
    61  		RuleWithSeverity: RuleWithSeverity{severity: severity},
    62  		prepositions:     prepositions,
    63  		excludes:         excludes,
    64  	}
    65  }
    66  
    67  // ID returns the ID of this rule.
    68  func (r FieldNamesExcludePrepositionsRule) ID() string {
    69  	return "FIELD_NAMES_EXCLUDE_PREPOSITIONS"
    70  }
    71  
    72  // Purpose returns the purpose of this rule.
    73  func (r FieldNamesExcludePrepositionsRule) Purpose() string {
    74  	return `Verifies that all field names don't include prepositions (e.g. "for", "during", "at").`
    75  }
    76  
    77  // IsOfficial decides whether or not this rule belongs to the official guide.
    78  func (r FieldNamesExcludePrepositionsRule) IsOfficial() bool {
    79  	return false
    80  }
    81  
    82  // Apply applies the rule to the proto.
    83  func (r FieldNamesExcludePrepositionsRule) Apply(proto *parser.Proto) ([]report.Failure, error) {
    84  	v := &fieldNamesExcludePrepositionsVisitor{
    85  		BaseAddVisitor: visitor.NewBaseAddVisitor(r.ID(), string(r.Severity())),
    86  		prepositions:   r.prepositions,
    87  		excludes:       r.excludes,
    88  	}
    89  	return visitor.RunVisitor(v, proto, r.ID())
    90  }
    91  
    92  type fieldNamesExcludePrepositionsVisitor struct {
    93  	*visitor.BaseAddVisitor
    94  	prepositions []string
    95  	excludes     []string
    96  }
    97  
    98  // VisitField checks the field.
    99  func (v *fieldNamesExcludePrepositionsVisitor) VisitField(field *parser.Field) bool {
   100  	name := field.FieldName
   101  	for _, e := range v.excludes {
   102  		name = strings.Replace(name, e, "", -1)
   103  	}
   104  
   105  	parts := strs.SplitSnakeCaseWord(name)
   106  	for _, p := range parts {
   107  		if stringsutil.ContainsStringInSlice(p, v.prepositions) {
   108  			v.AddFailuref(field.Meta.Pos, "Field name %q should not include a preposition %q", field.FieldName, p)
   109  		}
   110  	}
   111  	return false
   112  }
   113  
   114  // VisitMapField checks the map field.
   115  func (v *fieldNamesExcludePrepositionsVisitor) VisitMapField(field *parser.MapField) bool {
   116  	name := field.MapName
   117  	for _, e := range v.excludes {
   118  		name = strings.Replace(name, e, "", -1)
   119  	}
   120  
   121  	parts := strs.SplitSnakeCaseWord(name)
   122  	for _, p := range parts {
   123  		if stringsutil.ContainsStringInSlice(p, v.prepositions) {
   124  			v.AddFailuref(field.Meta.Pos, "Field name %q should not include a preposition %q", field.MapName, p)
   125  		}
   126  	}
   127  	return false
   128  }
   129  
   130  // VisitOneofField checks the oneof field.
   131  func (v *fieldNamesExcludePrepositionsVisitor) VisitOneofField(field *parser.OneofField) bool {
   132  	name := field.FieldName
   133  	for _, e := range v.excludes {
   134  		name = strings.Replace(name, e, "", -1)
   135  	}
   136  
   137  	parts := strs.SplitSnakeCaseWord(name)
   138  	for _, p := range parts {
   139  		if stringsutil.ContainsStringInSlice(p, v.prepositions) {
   140  			v.AddFailuref(field.Meta.Pos, "Field name %q should not include a preposition %q", field.FieldName, p)
   141  		}
   142  	}
   143  	return false
   144  }