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 }