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  }