github.com/errata-ai/vale/v3@v3.4.2/internal/check/scope.go (about)

     1  package check
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/errata-ai/vale/v3/internal/core"
     7  	"github.com/errata-ai/vale/v3/internal/nlp"
     8  )
     9  
    10  var allowedScopes = []string{
    11  	"text",
    12  	"heading",
    13  	"heading.h1",
    14  	"heading.h2",
    15  	"heading.h3",
    16  	"heading.h4",
    17  	"heading.h5",
    18  	"heading.h6",
    19  	"table",
    20  	"table.header",
    21  	"table.cell",
    22  	"table.caption",
    23  	"figure.caption",
    24  	"list",
    25  	"paragraph",
    26  	"sentence",
    27  	"alt",
    28  	"title",
    29  	"blockquote",
    30  	"summary",
    31  	"raw",
    32  }
    33  
    34  // A Selector represents a named section of text.
    35  type Selector struct {
    36  	Value   []string // e.g., text.comment.line.py
    37  	Negated bool
    38  }
    39  
    40  type Scope struct {
    41  	Selectors map[string][]Selector
    42  }
    43  
    44  func NewSelector(value []string) Selector {
    45  	negated := false
    46  
    47  	parts := []string{}
    48  	for i, m := range value {
    49  		m = strings.TrimSpace(m)
    50  		if i == 0 && strings.HasPrefix(m, "~") {
    51  			m = strings.TrimPrefix(m, "~")
    52  			negated = true
    53  		}
    54  		parts = append(parts, m)
    55  	}
    56  
    57  	return Selector{Value: parts, Negated: negated}
    58  }
    59  
    60  func NewScope(value []string) Scope {
    61  	scope := map[string][]Selector{}
    62  	for _, v := range value {
    63  		selectors := []Selector{}
    64  		for _, part := range strings.Split(v, "&") {
    65  			selectors = append(selectors, NewSelector(strings.Split(part, ".")))
    66  		}
    67  		scope[v] = selectors
    68  	}
    69  	return Scope{Selectors: scope}
    70  }
    71  
    72  // Macthes the scope `s` matches `s2`.
    73  func (s Scope) Matches(blk nlp.Block) bool {
    74  	candidate := NewSelector(strings.Split(blk.Scope, "."))
    75  	parent := NewSelector(strings.Split(blk.Parent, "."))
    76  
    77  	for _, sel := range s.Selectors {
    78  		if s.partMatches(candidate, parent, sel) {
    79  			return true
    80  		}
    81  	}
    82  
    83  	return false
    84  }
    85  
    86  func (s Scope) partMatches(target, parent Selector, options []Selector) bool {
    87  	for _, part := range options {
    88  		tm := target.Contains(part)
    89  		pm := parent.Contains(part)
    90  		if part.Negated && !pm {
    91  			if target.Has("raw") || target.Has("summary") {
    92  				// This can't apply to sized scopes.
    93  				return false
    94  			}
    95  		} else if (!part.Negated && !tm) || (part.Negated && pm) {
    96  			return false
    97  		}
    98  	}
    99  	return true
   100  }
   101  
   102  // Sections splits a Selector into its parts -- e.g., text.comment.line.py ->
   103  // []string{"text", "comment", "line", "py"}.
   104  func (s *Selector) Sections() []string {
   105  	parts := []string{}
   106  	for _, m := range s.Value {
   107  		parts = append(parts, strings.Split(m, ".")...)
   108  	}
   109  	return parts
   110  }
   111  
   112  // Contains determines if all if sel's sections are in s.
   113  func (s *Selector) Contains(sel Selector) bool {
   114  	return core.AllStringsInSlice(sel.Sections(), s.Sections())
   115  }
   116  
   117  // ContainsString determines if all if sel's sections are in s.
   118  func (s *Selector) ContainsString(scope []string) bool {
   119  	for _, option := range scope {
   120  		sel := Selector{Value: []string{option}}
   121  		if !s.Contains(sel) {
   122  			return false
   123  		}
   124  	}
   125  	return true
   126  }
   127  
   128  // Equal determines if sel == s.
   129  func (s *Selector) Equal(sel Selector) bool {
   130  	if len(s.Value) == len(sel.Value) {
   131  		for i, v := range s.Value {
   132  			if sel.Value[i] != v {
   133  				return false
   134  			}
   135  		}
   136  		return true
   137  	}
   138  	return false
   139  }
   140  
   141  // Has determines if s has a part equal to scope.
   142  func (s *Selector) Has(scope string) bool {
   143  	return core.StringInSlice(scope, s.Sections())
   144  }