github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/internal/addon/rules/fileNamesLowerSnakeCaseRule.go (about) 1 package rules 2 3 import ( 4 "os" 5 "path/filepath" 6 "strings" 7 8 "github.com/yoheimuta/protolint/internal/stringsutil" 9 10 "github.com/yoheimuta/go-protoparser/v4/parser" 11 12 "github.com/yoheimuta/protolint/linter/report" 13 "github.com/yoheimuta/protolint/linter/rule" 14 "github.com/yoheimuta/protolint/linter/strs" 15 "github.com/yoheimuta/protolint/linter/visitor" 16 ) 17 18 // FileNamesLowerSnakeCaseRule verifies that all file names are lower_snake_case.proto. 19 // See https://developers.google.com/protocol-buffers/docs/style#file-structure. 20 type FileNamesLowerSnakeCaseRule struct { 21 RuleWithSeverity 22 excluded []string 23 fixMode bool 24 } 25 26 // NewFileNamesLowerSnakeCaseRule creates a new FileNamesLowerSnakeCaseRule. 27 func NewFileNamesLowerSnakeCaseRule( 28 severity rule.Severity, 29 excluded []string, 30 fixMode bool, 31 ) FileNamesLowerSnakeCaseRule { 32 return FileNamesLowerSnakeCaseRule{ 33 RuleWithSeverity: RuleWithSeverity{severity: severity}, 34 excluded: excluded, 35 fixMode: fixMode, 36 } 37 } 38 39 // ID returns the ID of this rule. 40 func (r FileNamesLowerSnakeCaseRule) ID() string { 41 return "FILE_NAMES_LOWER_SNAKE_CASE" 42 } 43 44 // Purpose returns the purpose of this rule. 45 func (r FileNamesLowerSnakeCaseRule) Purpose() string { 46 return "Verifies that all file names are lower_snake_case.proto." 47 } 48 49 // IsOfficial decides whether or not this rule belongs to the official guide. 50 func (r FileNamesLowerSnakeCaseRule) IsOfficial() bool { 51 return true 52 } 53 54 // Apply applies the rule to the proto. 55 func (r FileNamesLowerSnakeCaseRule) Apply(proto *parser.Proto) ([]report.Failure, error) { 56 v := &fileNamesLowerSnakeCaseVisitor{ 57 BaseAddVisitor: visitor.NewBaseAddVisitor(r.ID(), string(r.Severity())), 58 excluded: r.excluded, 59 fixMode: r.fixMode, 60 } 61 return visitor.RunVisitor(v, proto, r.ID()) 62 } 63 64 type fileNamesLowerSnakeCaseVisitor struct { 65 *visitor.BaseAddVisitor 66 excluded []string 67 fixMode bool 68 } 69 70 // OnStart checks the file. 71 func (v *fileNamesLowerSnakeCaseVisitor) OnStart(proto *parser.Proto) error { 72 path := proto.Meta.Filename 73 if stringsutil.ContainsStringInSlice(path, v.excluded) { 74 return nil 75 } 76 77 filename := filepath.Base(path) 78 ext := filepath.Ext(filename) 79 base := strings.TrimSuffix(filename, ext) 80 if ext != ".proto" || !strs.IsLowerSnakeCase(base) { 81 expected := strs.ToLowerSnakeCase(base) 82 expected += ".proto" 83 v.AddFailurefWithProtoMeta(proto.Meta, "File name %q should be lower_snake_case.proto like %q.", filename, expected) 84 85 if v.fixMode { 86 dir := filepath.Dir(path) 87 newPath := filepath.Join(dir, expected) 88 if _, err := os.Stat(newPath); !os.IsNotExist(err) { 89 v.AddFailurefWithProtoMeta(proto.Meta, "Failed to rename %q because %q already exists.", filename, expected) 90 return nil 91 } 92 err := os.Rename(path, newPath) 93 if err != nil { 94 return err 95 } 96 97 // Notify the upstream this new filename by updating the proto. 98 proto.Meta.Filename = newPath 99 } 100 } 101 return nil 102 }