github.com/blend/go-sdk@v1.20220411.3/profanity/rule_spec.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package profanity 9 10 import ( 11 "fmt" 12 "strings" 13 14 "github.com/blend/go-sdk/ex" 15 "github.com/blend/go-sdk/validate" 16 ) 17 18 var ( 19 _ Rule = (*RuleSpec)(nil) 20 ) 21 22 // Errors 23 const ( 24 ErrRuleSpecMultipleRules ex.Class = "rule spec invalid; multiple rule types specified" 25 ErrRuleSpecRuleMissing ex.Class = "rule spec invalid; at least one rule type is required" 26 ) 27 28 // RuleSpec is a serialized rule. 29 type RuleSpec struct { 30 // ID is a unique identifier for the rule. 31 ID string `yaml:"id"` 32 // SourceFile is the rules file path the rule came from. 33 SourceFile string `yaml:"-"` 34 // Description is a descriptive message for the rule. 35 Description string `yaml:"description,omitempty"` 36 // Files is the glob filter for inclusion and exclusion' 37 // for this specific rule spec. 38 Files GlobFilter `yaml:"files,omitempty"` 39 // RuleSpecRules are the rules for the rule spec. 40 RuleSpecRules `yaml:",inline"` 41 } 42 43 // Validate validates the RuleSpec. 44 func (r RuleSpec) Validate() error { 45 if err := validate.String(&r.ID).Required()(); err != nil { 46 return validate.Error(validate.ErrCause(err), r) 47 } 48 if err := r.Files.Validate(); err != nil { 49 return validate.Error(validate.ErrCause(err), r) 50 } 51 if err := r.RuleSpecRules.Validate(); err != nil { 52 return validate.Error(validate.ErrCause(err), r) 53 } 54 return nil 55 56 } 57 58 // String returns a string representation of the rule. 59 func (r RuleSpec) String() string { 60 var tokens []string 61 62 if len(r.ID) > 0 { 63 tokens = append(tokens, r.ID) 64 } 65 if len(r.Description) > 0 { 66 tokens = append(tokens, "`"+r.Description+"`") 67 } 68 tokens = append(tokens, r.Files.String()) 69 tokens = append(tokens, r.RuleSpecRules.String()) 70 return fmt.Sprintf("[%s]", strings.Join(tokens, " ")) 71 } 72 73 // RuleSpecRules are the specific rules for a given RuleSpec. 74 // 75 // The usage of this should be that only _one_ of these rules 76 // should be set for a given rule spec. 77 type RuleSpecRules struct { 78 // Contains implies we should fail if a file contains a given string. 79 Contents *Contents `yaml:"contents,omitempty"` 80 // GoImportsContain enforces that a given list of imports are used. 81 GoImports *GoImports `yaml:"goImports,omitempty"` 82 // GoCalls enforces that a given list of imports are used. 83 GoCalls *GoCalls `yaml:"goCalls,omitempty"` 84 } 85 86 // Rules returns the rules from the spec. 87 // 88 // Note: you should add new rule types here and on the type itself. 89 func (r RuleSpecRules) Rules() (output []Rule) { 90 if r.Contents != nil { 91 output = append(output, r.Contents) 92 } 93 if r.GoImports != nil { 94 output = append(output, r.GoImports) 95 } 96 if r.GoCalls != nil { 97 output = append(output, r.GoCalls) 98 } 99 return 100 } 101 102 // Rule returns the active rule from the spec. 103 func (r RuleSpecRules) Rule() Rule { 104 if rules := r.Rules(); len(rules) > 0 { 105 return rules[0] 106 } 107 return nil 108 } 109 110 // Check applies the rule. 111 func (r RuleSpecRules) Check(filename string, contents []byte) (result RuleResult) { 112 if result = r.Rule().Check(filename, contents); !result.OK { 113 return 114 } 115 result = RuleResult{ 116 OK: true, 117 File: filename, 118 } 119 return 120 } 121 122 // Validate validates the rule spec rules. 123 func (r RuleSpecRules) Validate() error { 124 if len(r.Rules()) > 1 { 125 return validate.Error(ErrRuleSpecMultipleRules, nil) 126 } 127 if len(r.Rules()) == 0 { 128 return validate.Error(ErrRuleSpecRuleMissing, nil) 129 } 130 if typed, ok := r.Rule().(interface { 131 Validate() error 132 }); ok { 133 if err := typed.Validate(); err != nil { 134 return err 135 } 136 } 137 return nil 138 } 139 140 // String implements fmt.Stringer. 141 func (r RuleSpecRules) String() string { 142 var tokens []string 143 for _, rule := range r.Rules() { 144 if typed, ok := rule.(fmt.Stringer); ok { 145 tokens = append(tokens, typed.String()) 146 } 147 } 148 return strings.Join(tokens, " ") 149 }