github.com/errata-ai/vale/v3@v3.4.2/internal/check/existence.go (about) 1 package check 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/errata-ai/regexp2" 8 9 "github.com/errata-ai/vale/v3/internal/core" 10 "github.com/errata-ai/vale/v3/internal/nlp" 11 ) 12 13 // Existence checks for the present of Tokens. 14 type Existence struct { 15 Definition `mapstructure:",squash"` 16 Raw []string 17 Tokens []string 18 // `exceptions` (`array`): An array of strings to be ignored. 19 Exceptions []string 20 exceptRe *regexp2.Regexp 21 pattern *regexp2.Regexp 22 Append bool 23 IgnoreCase bool 24 Nonword bool 25 Vocab bool 26 } 27 28 // NewExistence creates a new `Rule` that extends `Existence`. 29 func NewExistence(cfg *core.Config, generic baseCheck, path string) (Existence, error) { 30 rule := Existence{Vocab: true} 31 32 err := decodeRule(generic, &rule) 33 if err != nil { 34 return rule, readStructureError(err, path) 35 } 36 37 err = checkScopes(rule.Scope, path) 38 if err != nil { 39 return rule, err 40 } 41 42 re, err := updateExceptions(rule.Exceptions, cfg.AcceptedTokens, rule.Vocab) 43 if err != nil { 44 return rule, core.NewE201FromPosition(err.Error(), path, 1) 45 } 46 rule.exceptRe = re 47 48 regex := makeRegexp( 49 cfg.WordTemplate, 50 rule.IgnoreCase, 51 func() bool { return !rule.Nonword && len(rule.Tokens) > 0 }, 52 func() string { return strings.Join(rule.Raw, "") }, 53 rule.Append) 54 55 parsed := []string{} 56 for _, token := range rule.Tokens { 57 if strings.TrimSpace(token) != "" { 58 parsed = append(parsed, token) 59 } 60 } 61 regex = fmt.Sprintf(regex, strings.Join(parsed, "|")) 62 63 re, err = regexp2.CompileStd(regex) 64 if err != nil { 65 return rule, core.NewE201FromPosition(err.Error(), path, 1) 66 } 67 rule.pattern = re 68 69 return rule, nil 70 } 71 72 // Run executes the the `existence`-based rule. 73 // 74 // This is simplest of the available extension points: it looks for any matches 75 // of its internal `pattern` (calculated from `NewExistence`) against the 76 // provided text. 77 func (e Existence) Run(blk nlp.Block, _ *core.File) ([]core.Alert, error) { 78 alerts := []core.Alert{} 79 80 for _, loc := range e.pattern.FindAllStringIndex(blk.Text, -1) { 81 converted, err := re2Loc(blk.Text, loc) 82 if err != nil { 83 return alerts, err 84 } 85 86 observed := strings.TrimSpace(converted) 87 if !isMatch(e.exceptRe, observed) { 88 a, erra := makeAlert(e.Definition, loc, blk.Text) 89 if erra != nil { 90 return alerts, erra 91 } 92 alerts = append(alerts, a) 93 } 94 } 95 96 return alerts, nil 97 } 98 99 // Fields provides access to the internal rule definition. 100 func (e Existence) Fields() Definition { 101 return e.Definition 102 } 103 104 // Pattern is the internal regex pattern used by this rule. 105 func (e Existence) Pattern() string { 106 return e.pattern.String() 107 }