aboutsummaryrefslogtreecommitdiff
path: root/internal/cond
diff options
context:
space:
mode:
Diffstat (limited to 'internal/cond')
-rw-r--r--internal/cond/compile.go22
-rw-r--r--internal/cond/compile_test.go17
-rw-r--r--internal/cond/types.go12
3 files changed, 51 insertions, 0 deletions
diff --git a/internal/cond/compile.go b/internal/cond/compile.go
index 4fdc75f..590d61a 100644
--- a/internal/cond/compile.go
+++ b/internal/cond/compile.go
@@ -362,6 +362,28 @@ func (c *compiler) compileMatched(n *sexp.Node) *node {
return &node{kind: kMatched, pos: n.Pos, label: "matched", cost: costCheap}
}
+// collectNameGroups walks n and its children — and and or included, not
+// excluded — appending the capture-group count of every pattern of every
+// kName node reachable without crossing a not, in compile order. B1: a
+// name test under a not never supplies Result.Captures (eval.go's negated
+// tracking takes captures only when !negated), so it must not count toward
+// checkCaptures' "does some name test in this rule have enough groups"
+// either - a rule combining a capturing name test with an unrelated
+// (not (name ...)) must still pass.
+func collectNameGroups(n *node, out *[]int) {
+ if n == nil || n.kind == kNot {
+ return
+ }
+ if n.kind == kName {
+ for _, p := range n.patterns {
+ *out = append(*out, p.re.NumSubexp())
+ }
+ }
+ for _, ch := range n.children {
+ collectNameGroups(ch, out)
+ }
+}
+
// quotedExampleErr records the shared "takes X in quotes: write (test
// "arg")" diagnostic used by name, path, content and duplicate.
func (c *compiler) quotedExampleErr(a *sexp.Node, test, noun string) {
diff --git a/internal/cond/compile_test.go b/internal/cond/compile_test.go
index e68a4d8..2a07ad6 100644
--- a/internal/cond/compile_test.go
+++ b/internal/cond/compile_test.go
@@ -124,6 +124,23 @@ func TestEmptyChildrenGuard(t *testing.T) {
}
}
+// TestNameGroups: B1b - NameGroups() reports the capture-group count of
+// every name test reachable without crossing a not, and skips one reachable
+// only under a not (B1): here the outer (name ...) has two groups and the
+// (not (name ...)) one has one, but the result must carry only the outer's
+// count.
+func TestNameGroups(t *testing.T) {
+ c, errs := Compile("d.conf", nodes(t, `(and (name "inv-(\d+)-(\d+)") (not (name "draft-(\d+)")))`), Options{})
+ if len(errs) > 0 {
+ t.Fatal(errs)
+ }
+ got := c.NameGroups()
+ want := []int{2}
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("NameGroups() = %v, want %v", got, want)
+ }
+}
+
func TestGroupsMatchSpec(t *testing.T) {
want := map[string]string{
"image": "jpg jpeg png gif webp bmp tif tiff heic heif avif svg ico raw cr2 nef arw dng",
diff --git a/internal/cond/types.go b/internal/cond/types.go
index 81c2f45..9455f63 100644
--- a/internal/cond/types.go
+++ b/internal/cond/types.go
@@ -93,6 +93,18 @@ type node struct {
dirs []string
}
+// NameGroups returns the number of capture groups of every name test in the
+// condition that can ever supply captures, in compile order: a name test
+// nested inside and/or counts however deep, but one inside a not does not
+// (B1) - it can never be the source of Result.Captures, so it must not be
+// asked to justify a rule's use of {N} either. Empty when the rule has no
+// such name test.
+func (c *Cond) NameGroups() []int {
+ var out []int
+ collectNameGroups(c.root, &out)
+ return out
+}
+
// groups maps a (type ...) group name to the extensions it expands to,
// spec Appendix A.
var groups = map[string][]string{