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  }