github.com/errata-ai/vale/v3@v3.4.2/internal/check/conditional.go (about) 1 package check 2 3 import ( 4 "github.com/errata-ai/regexp2" 5 6 "github.com/errata-ai/vale/v3/internal/core" 7 "github.com/errata-ai/vale/v3/internal/nlp" 8 ) 9 10 // Conditional ensures that the present of First ensures the present of Second. 11 type Conditional struct { 12 Definition `mapstructure:",squash"` 13 Exceptions []string 14 patterns []*regexp2.Regexp 15 First string 16 Second string 17 exceptRe *regexp2.Regexp 18 Ignorecase bool 19 Vocab bool 20 } 21 22 // NewConditional creates a new `conditional`-based rule. 23 func NewConditional(cfg *core.Config, generic baseCheck, path string) (Conditional, error) { 24 var expression []*regexp2.Regexp 25 rule := Conditional{Vocab: true} 26 27 err := decodeRule(generic, &rule) 28 if err != nil { 29 return rule, readStructureError(err, path) 30 } 31 32 err = checkScopes(rule.Scope, path) 33 if err != nil { 34 return rule, err 35 } 36 37 re, err := updateExceptions(rule.Exceptions, cfg.AcceptedTokens, rule.Vocab) 38 if err != nil { 39 return rule, core.NewE201FromPosition(err.Error(), path, 1) 40 } 41 rule.exceptRe = re 42 43 re, err = regexp2.CompileStd(rule.Second) 44 if err != nil { 45 return rule, core.NewE201FromPosition(err.Error(), path, 1) 46 } 47 expression = append(expression, re) 48 49 re, err = regexp2.CompileStd(rule.First) 50 if err != nil { 51 return rule, core.NewE201FromPosition(err.Error(), path, 1) 52 } 53 expression = append(expression, re) 54 55 // TODO: How do we support multiple patterns? 56 rule.patterns = expression 57 return rule, nil 58 } 59 60 // Run evaluates the given conditional statement. 61 func (c Conditional) Run(blk nlp.Block, f *core.File) ([]core.Alert, error) { 62 alerts := []core.Alert{} 63 64 txt := blk.Text 65 // We first look for the consequent of the conditional statement. 66 // For example, if we're ensuring that abbreviations have been defined 67 // parenthetically, we'd have something like: 68 // 69 // "WHO" [antecedent], "World Health Organization (WHO)" [consequent] 70 // 71 // In other words: if "WHO" exists, it must also have a definition -- which 72 // we're currently looking for. 73 matches := c.patterns[0].FindAllStringSubmatch(txt, -1) 74 for _, mat := range matches { 75 if len(mat) > 1 { 76 // If we find one, we store it in a slice associated with this 77 // particular file. 78 for _, m := range mat[1:] { 79 if len(m) > 0 { 80 f.Sequences = append(f.Sequences, m) 81 } 82 } 83 } 84 } 85 86 // Now we look for the antecedent. 87 locs := c.patterns[1].FindAllStringIndex(txt, -1) 88 for _, loc := range locs { 89 s, err := re2Loc(txt, loc) 90 if err != nil { 91 return alerts, err 92 } 93 94 if !core.StringInSlice(s, f.Sequences) && !isMatch(c.exceptRe, s) { 95 // If we've found one (e.g., "WHO") and we haven't marked it as 96 // being defined previously, send an Alert. 97 a, erra := makeAlert(c.Definition, loc, txt) 98 if erra != nil { 99 return alerts, erra 100 } 101 alerts = append(alerts, a) 102 } 103 } 104 105 return alerts, nil 106 } 107 108 // Fields provides access to the internal rule definition. 109 func (c Conditional) Fields() Definition { 110 return c.Definition 111 } 112 113 // Pattern is the internal regex pattern used by this rule. 114 func (c Conditional) Pattern() string { 115 return "" 116 }