@@ -3,7 +3,7 @@ import {recursion} from '../src/index.js';
33
44describe ( 'recursion' , ( ) => {
55 it ( 'should throw for invalid and unsupported recursion depths' , ( ) => {
6- const values = [ '-2' , '0' , '1' , '+2' , '2.5' , '101' , 'a' , null ] ;
6+ const values = [ '-2' , '0' , '1' , '02' , ' +2', '2.5' , '101' , 'a' , null ] ;
77 for ( const value of values ) {
88 expect ( ( ) => regex ( { plugins : [ recursion ] } ) ( { raw : [ `a(?R=${ value } )?b` ] } ) ) . toThrow ( ) ;
99 expect ( ( ) => regex ( { plugins : [ recursion ] } ) ( { raw : [ `(?<r>a\\g<r&R=${ value } >?b)` ] } ) ) . toThrow ( ) ;
@@ -18,38 +18,8 @@ describe('recursion', () => {
1818 }
1919 } ) ;
2020
21- it ( 'should match global recursion' , ( ) => {
22- expect ( regex ( { plugins : [ recursion ] } ) `a(?R=2)?b` . exec ( 'aabb' ) ?. [ 0 ] ) . toBe ( 'aabb' ) ;
23- } ) ;
24-
25- it ( 'should match direct recursion' , ( ) => {
26- expect ( 'aabb' ) . toMatch ( regex ( { plugins : [ recursion ] } ) `^(?<r>a\g<r&R=2>?b)$` ) ;
27- expect ( 'aab' ) . not . toMatch ( regex ( { plugins : [ recursion ] } ) `^(?<r>a\g<r&R=2>?b)$` ) ;
28- } ) ;
29-
30- it ( 'should throw for multiple direct, overlapping recursions' , ( ) => {
31- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `a(?R=2)?(?<r>a\g<r&R=2>?)` ) . toThrow ( ) ;
32- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<r>a\g<r&R=2>?\g<r&R=2>?)` ) . toThrow ( ) ;
33- } ) ;
34-
35- it ( 'should throw for multiple direct, nonoverlapping recursions' , ( ) => {
36- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<r1>a\g<r1&R=2>?)(?<r2>a\g<r2&R=2>?)` ) . toThrow ( ) ;
37- } ) ;
38-
39- it ( 'should throw for indirect recursion' , ( ) => {
40- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>(?<b>a\g<a&R=2>?)\g<b&R=2>)` ) . toThrow ( ) ;
41- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>\g<b&R=2>(?<b>a\g<a&R=2>?))` ) . toThrow ( ) ;
42- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>\g<b&R=2>)(?<b>a\g<a&R=2>?)` ) . toThrow ( ) ;
43- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `\g<a&R=2>(?<a>\g<b&R=2>)(?<b>a\g<a&R=2>?)` ) . toThrow ( ) ;
44- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>\g<b&R=2>)(?<b>\g<c&R=2>)(?<c>a\g<a&R=2>?)` ) . toThrow ( ) ;
45- } ) ;
46-
47- it ( 'should not adjust named backreferences referring outside of the recursed expression' , ( ) => {
48- expect ( 'aababbabcc' ) . toMatch ( regex ( { plugins : [ recursion ] } ) `^(?<a>a)\k<a>(?<r>(?<b>b)\k<a>\k<b>\k<c>\g<r&R=2>?)(?<c>c)\k<c>$` ) ;
49- } ) ;
50-
5121 // Just documenting current behavior; this could be supported in the future
52- it ( 'should throw for numbered backreferences in interpolated regexes when using recursion' , ( ) => {
22+ it ( 'should throw for numbered backrefs in interpolated regexes when using recursion' , ( ) => {
5323 expect ( ( ) => regex ( { plugins : [ recursion ] } ) `a(?R=2)?b${ / ( ) \1/ } ` ) . toThrow ( ) ;
5424 expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<n>a|\g<n&R=2>${ / ( ) \1/ } )` ) . toThrow ( ) ;
5525 expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<n>a|\g<n&R=2>)${ / ( ) \1/ } ` ) . toThrow ( ) ;
@@ -62,55 +32,127 @@ describe('recursion', () => {
6232 expect ( ( ) => regex ( { plugins : [ recursion ] } ) `a(?R=2)?b(?(DEFINE))` ) . toThrow ( ) ;
6333 expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<n>a|\g<n&R=2>)(?(DEFINE))` ) . toThrow ( ) ;
6434 } ) ;
65- } ) ;
66-
67- describe ( 'readme examples' , ( ) => {
68- it ( 'should match an equal number of two different subpatterns' , ( ) => {
69- expect ( regex ( { plugins : [ recursion ] } ) `a(?R=50)?b` . exec ( 'test aaaaaabbb' ) [ 0 ] ) . toBe ( 'aaabbb' ) ;
70- expect ( 'aAbb' ) . toMatch ( regex ( { flags : 'i' , plugins : [ recursion ] } ) `a(?R=2)?b` ) ;
71- } ) ;
72-
73- it ( 'should match an equal number of two different subpatterns, as the entire string' , ( ) => {
74- const re = regex ( { plugins : [ recursion ] } ) `^
75- (?<balanced>
76- a
77- # Recursively match just the specified group
78- \g<balanced&R=50>?
79- b
80- )
81- $` ;
82- expect ( re . test ( 'aaabbb' ) ) . toBeTrue ( ) ;
83- expect ( re . test ( 'aaabb' ) ) . toBeFalse ( ) ;
84- } ) ;
85-
86- it ( 'should match balanced parentheses' , ( ) => {
87- const parens = regex ( { flags : 'g' , plugins : [ recursion ] } ) `\(
88- ( [^\(\)] | (?R=50) )*
89- \)` ;
90- expect ( 'test ) (balanced ((parens))) () ((a)) ( (b)' . match ( parens ) ) . toEqual ( [ '(balanced ((parens)))' , '()' , '((a))' , '(b)' ] ) ;
91- } ) ;
92-
93- it ( 'should match balanced parentheses using an atomic group' , ( ) => {
94- const parens = regex ( { flags : 'g' , plugins : [ recursion ] } ) `\(
95- ( (?> [^\(\)]+ ) | (?R=50) )*
96- \)` ;
97- expect ( 'test ) (balanced ((parens))) () ((a)) ( (b)' . match ( parens ) ) . toEqual ( [ '(balanced ((parens)))' , '()' , '((a))' , '(b)' ] ) ;
98- } ) ;
99-
100- it ( 'should match palindromes' , ( ) => {
101- const palindromes = regex ( { flags : 'gi' , plugins : [ recursion ] } ) `(?<char>\w) ((?R=15)|\w?) \k<char>` ;
102- expect ( 'Racecar, ABBA, and redivided' . match ( palindromes ) ) . toEqual ( [ 'Racecar' , 'ABBA' , 'edivide' ] ) ;
103- } ) ;
10435
105- it ( 'should match palindromes as complete words' , ( ) => {
106- const palindromeWords = regex ( { flags : 'gi' , plugins : [ recursion ] } ) `\b
107- (?<palindrome>
108- (?<char> \w )
109- # Recurse, or match a lone unbalanced char in the center
110- ( \g<palindrome&R=15> | \w? )
111- \k<char>
112- )
113- \b` ;
114- expect ( 'Racecar, ABBA, and redivided' . match ( palindromeWords ) ) . toEqual ( [ 'Racecar' , 'ABBA' ] ) ;
36+ it ( 'should not modify escaped recursion operators' , ( ) => {
37+ expect ( 'a\\g<r&R=2>b' ) . toMatch ( regex ( { plugins : [ recursion ] } ) `^(?<r>a\\g<r&R=2>?b)$` ) ;
38+ expect ( 'a\\a\\bb' ) . toMatch ( regex ( { plugins : [ recursion ] } ) `^(?<r>a\\\g<r&R=2>?b)$` ) ;
39+ } ) ;
40+
41+ describe ( 'global' , ( ) => {
42+ it ( 'should match global recursion' , ( ) => {
43+ expect ( regex ( { plugins : [ recursion ] } ) `a(?R=2)?b` . exec ( 'aabb' ) ?. [ 0 ] ) . toBe ( 'aabb' ) ;
44+ } ) ;
45+
46+ it ( 'should have backrefs refer to their own recursion depth' , ( ) => {
47+ expect ( regex ( { plugins : [ recursion ] } ) `(?<w>\w)0(?R=2)?1\k<w>` . exec ( 'a0b01b1a' ) ?. [ 0 ] ) . toBe ( 'a0b01b1a' ) ;
48+ expect ( regex ( { plugins : [ recursion ] } ) `(?<w>\w)0(?R=2)?1\k<w>` . test ( 'a0b01a1b' ) ) . toBeFalse ( ) ;
49+ } ) ;
50+ } ) ;
51+
52+ describe ( 'subpattern by name' , ( ) => {
53+ it ( 'should match direct recursion' , ( ) => {
54+ expect ( 'aabb' ) . toMatch ( regex ( { plugins : [ recursion ] } ) `^(?<r>a\g<r&R=2>?b)$` ) ;
55+ expect ( 'aab' ) . not . toMatch ( regex ( { plugins : [ recursion ] } ) `^(?<r>a\g<r&R=2>?b)$` ) ;
56+ } ) ;
57+
58+ it ( 'should throw for multiple direct, overlapping recursions' , ( ) => {
59+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `a(?R=2)?(?<r>a\g<r&R=2>?)` ) . toThrow ( ) ;
60+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<r>a\g<r&R=2>?\g<r&R=2>?)` ) . toThrow ( ) ;
61+ } ) ;
62+
63+ it ( 'should throw for multiple direct, nonoverlapping recursions' , ( ) => {
64+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<r1>a\g<r1&R=2>?)(?<r2>a\g<r2&R=2>?)` ) . toThrow ( ) ;
65+ } ) ;
66+
67+ it ( 'should throw for indirect recursion' , ( ) => {
68+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>\g<b&R=2>)(?<b>a\g<a&R=2>?)` ) . toThrow ( ) ;
69+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `\g<a&R=2>(?<a>\g<b&R=2>)(?<b>a\g<a&R=2>?)` ) . toThrow ( ) ;
70+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>\g<b&R=2>)(?<b>\g<c&R=2>)(?<c>a\g<a&R=2>?)` ) . toThrow ( ) ;
71+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>(?<b>a\g<a&R=2>?)\g<b&R=2>)` ) . toThrow ( ) ;
72+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>\g<b&R=2>(?<b>a\g<a&R=2>?))` ) . toThrow ( ) ;
73+ } ) ;
74+
75+ it ( 'should have backrefs refer to their own recursion depth' , ( ) => {
76+ expect ( regex ( { plugins : [ recursion ] } ) `<(?<n>(?<w>\w)0\g<n&R=2>?1\k<w>)>` . exec ( '<a0b01b1a>' ) ?. [ 0 ] ) . toBe ( '<a0b01b1a>' ) ;
77+ expect ( regex ( { plugins : [ recursion ] } ) `<(?<n>(?<w>\w)0\g<n&R=2>?1\k<w>)>` . test ( '<a0b01a1b>' ) ) . toBeFalse ( ) ;
78+ } ) ;
79+
80+ it ( 'should not adjust named backrefs referring outside of the recursed subpattern' , ( ) => {
81+ expect ( 'aababbabcc' ) . toMatch ( regex ( { plugins : [ recursion ] } ) `^(?<a>a)\k<a>(?<r>(?<b>b)\k<a>\k<b>\k<c>\g<r&R=2>?)(?<c>c)\k<c>$` ) ;
82+ } ) ;
83+
84+ it ( 'should throw if referencing a non-ancestor group' , ( ) => {
85+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>)\g<a&R=2>?` ) . toThrow ( ) ;
86+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `\g<a&R=2>?(?<a>)` ) . toThrow ( ) ;
87+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>)(?<b>\g<a&R=2>?)` ) . toThrow ( ) ;
88+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<b>\g<a&R=2>?)(?<a>)` ) . toThrow ( ) ;
89+ } ) ;
90+ } ) ;
91+
92+ describe ( 'subpattern by number' , ( ) => {
93+ it ( 'should match direct recursion' , ( ) => {
94+ expect ( 'aabb' ) . toMatch ( regex ( { plugins : [ recursion ] } ) `^(?<r>a\g<1&R=2>?b)$` ) ;
95+ expect ( 'aab' ) . not . toMatch ( regex ( { plugins : [ recursion ] } ) `^(?<r>a\g<1&R=2>?b)$` ) ;
96+ expect ( 'aabb' ) . toMatch ( regex ( { plugins : [ recursion ] , disable : { n : true } } ) `^(a\g<1&R=2>?b)$` ) ;
97+ expect ( 'aab' ) . not . toMatch ( regex ( { plugins : [ recursion ] , disable : { n : true } } ) `^(a\g<1&R=2>?b)$` ) ;
98+ } ) ;
99+
100+ it ( 'should throw if referencing a non-ancestor group' , ( ) => {
101+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>)\g<1&R=2>?` ) . toThrow ( ) ;
102+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `\g<1&R=2>?(?<a>)` ) . toThrow ( ) ;
103+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>)(?<b>\g<1&R=2>?)` ) . toThrow ( ) ;
104+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<b>\g<2&R=2>?)(?<a>)` ) . toThrow ( ) ;
105+ } ) ;
106+ } ) ;
107+
108+ describe ( 'readme examples' , ( ) => {
109+ it ( 'should match an equal number of two different subpatterns' , ( ) => {
110+ expect ( regex ( { plugins : [ recursion ] } ) `a(?R=50)?b` . exec ( 'test aaaaaabbb' ) [ 0 ] ) . toBe ( 'aaabbb' ) ;
111+ expect ( 'aAbb' ) . toMatch ( regex ( { flags : 'i' , plugins : [ recursion ] } ) `a(?R=2)?b` ) ;
112+ } ) ;
113+
114+ it ( 'should match an equal number of two different subpatterns, as the entire string' , ( ) => {
115+ const re = regex ( { plugins : [ recursion ] } ) `^
116+ (?<balanced>
117+ a
118+ # Recursively match just the specified group
119+ \g<balanced&R=50>?
120+ b
121+ )
122+ $` ;
123+ expect ( re . test ( 'aaabbb' ) ) . toBeTrue ( ) ;
124+ expect ( re . test ( 'aaabb' ) ) . toBeFalse ( ) ;
125+ } ) ;
126+
127+ it ( 'should match balanced parentheses' , ( ) => {
128+ const parens = regex ( { flags : 'g' , plugins : [ recursion ] } ) `\(
129+ ( [^\(\)] | (?R=50) )*
130+ \)` ;
131+ expect ( 'test ) (balanced ((parens))) () ((a)) ( (b)' . match ( parens ) ) . toEqual ( [ '(balanced ((parens)))' , '()' , '((a))' , '(b)' ] ) ;
132+ } ) ;
133+
134+ it ( 'should match balanced parentheses using an atomic group' , ( ) => {
135+ const parens = regex ( { flags : 'g' , plugins : [ recursion ] } ) `\(
136+ ( (?> [^\(\)]+ ) | (?R=50) )*
137+ \)` ;
138+ expect ( 'test ) (balanced ((parens))) () ((a)) ( (b)' . match ( parens ) ) . toEqual ( [ '(balanced ((parens)))' , '()' , '((a))' , '(b)' ] ) ;
139+ } ) ;
140+
141+ it ( 'should match palindromes' , ( ) => {
142+ const palindromes = regex ( { flags : 'gi' , plugins : [ recursion ] } ) `(?<char>\w) ((?R=15)|\w?) \k<char>` ;
143+ expect ( 'Racecar, ABBA, and redivided' . match ( palindromes ) ) . toEqual ( [ 'Racecar' , 'ABBA' , 'edivide' ] ) ;
144+ } ) ;
145+
146+ it ( 'should match palindromes as complete words' , ( ) => {
147+ const palindromeWords = regex ( { flags : 'gi' , plugins : [ recursion ] } ) `\b
148+ (?<palindrome>
149+ (?<char> \w )
150+ # Recurse, or match a lone unbalanced char in the center
151+ ( \g<palindrome&R=15> | \w? )
152+ \k<char>
153+ )
154+ \b` ;
155+ expect ( 'Racecar, ABBA, and redivided' . match ( palindromeWords ) ) . toEqual ( [ 'Racecar' , 'ABBA' ] ) ;
156+ } ) ;
115157 } ) ;
116158} ) ;
0 commit comments