@@ -2,6 +2,70 @@ import {regex} from 'regex';
22import { recursion } from '../src/index.js' ;
33
44describe ( 'recursion' , ( ) => {
5+ it ( 'should throw for invalid and unsupported recursion depths' , ( ) => {
6+ const values = [ '-2' , '0' , '1' , '+2' , '2.5' , '101' , 'a' , null ] ;
7+ for ( const value of values ) {
8+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) ( { raw : [ `a(?R=${ value } )?b` ] } ) ) . toThrow ( ) ;
9+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) ( { raw : [ `(?<r>a\\g<r&R=${ value } >?b)` ] } ) ) . toThrow ( ) ;
10+ }
11+ } ) ;
12+
13+ it ( 'should allow recursion depths 2-100' , ( ) => {
14+ const values = [ '2' , '100' ] ;
15+ for ( const value of values ) {
16+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) ( { raw : [ `a(?R=${ value } )?b` ] } ) ) . not . toThrow ( ) ;
17+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) ( { raw : [ `(?<r>a\\g<r&R=${ value } >?b)` ] } ) ) . not . toThrow ( ) ;
18+ }
19+ } ) ;
20+
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+ // TODO: Has a bug and lets invalid JS syntax through
37+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<r1>a\g<r1&R=2>?)(?<r2>a\g<r2&R=2>?)` ) . toThrow ( ) ;
38+ } ) ;
39+
40+ it ( 'should throw for indirect recursion' , ( ) => {
41+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>(?<b>a\g<a&R=2>?)\g<b&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 ] } ) `(?<a>\g<b&R=2>)(?<b>a\g<a&R=2>?)` ) . toThrow ( ) ;
44+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `\g<a&R=2>(?<a>\g<b&R=2>)(?<b>a\g<a&R=2>?)` ) . toThrow ( ) ;
45+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<a>\g<b&R=2>)(?<b>\g<c&R=2>)(?<c>a\g<a&R=2>?)` ) . toThrow ( ) ;
46+ } ) ;
47+
48+ it ( 'should not adjust named backreferences referring outside of the recursed expression' , ( ) => {
49+ 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>$` ) ;
50+ } ) ;
51+
52+ // Just documenting current behavior; this could be supported in the future
53+ it ( 'should throw for numbered backreferences in interpolated regexes when using recursion' , ( ) => {
54+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `a(?R=2)?b${ / ( ) \1/ } ` ) . toThrow ( ) ;
55+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<n>a|\g<n&R=2>${ / ( ) \1/ } )` ) . toThrow ( ) ;
56+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<n>a|\g<n&R=2>)${ / ( ) \1/ } ` ) . toThrow ( ) ;
57+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `${ / ( ) \1/ } a(?R=2)?b` ) . toThrow ( ) ;
58+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<n>${ / ( ) \1/ } a|\g<n&R=2>)` ) . toThrow ( ) ;
59+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `${ / ( ) \1/ } (?<n>a|\g<n&R=2>)` ) . toThrow ( ) ;
60+ } ) ;
61+
62+ it ( 'should throw for subroutine definition groups when using recursion' , ( ) => {
63+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `a(?R=2)?b(?(DEFINE))` ) . toThrow ( ) ;
64+ expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<n>a|\g<n&R=2>)(?(DEFINE))` ) . toThrow ( ) ;
65+ } ) ;
66+ } ) ;
67+
68+ describe ( 'readme examples' , ( ) => {
569 it ( 'should match an equal number of two different subpatterns' , ( ) => {
670 expect ( regex ( { plugins : [ recursion ] } ) `a(?R=50)?b` . exec ( 'test aaaaaabbb' ) [ 0 ] ) . toBe ( 'aaabbb' ) ;
771 expect ( 'aAbb' ) . toMatch ( regex ( { flags : 'i' , plugins : [ recursion ] } ) `a(?R=2)?b` ) ;
@@ -50,23 +114,4 @@ describe('recursion', () => {
50114 \b` ;
51115 expect ( 'Racecar, ABBA, and redivided' . match ( palindromeWords ) ) . toEqual ( [ 'Racecar' , 'ABBA' ] ) ;
52116 } ) ;
53-
54- it ( 'should not adjust named backreferences referring outside of the recursed expression' , ( ) => {
55- 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>$` ) ;
56- } ) ;
57-
58- // Just documenting current behavior; this could be supported in the future
59- it ( 'should not allow numbered backreferences in interpolated regexes when using recursion' , ( ) => {
60- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `a(?R=2)?b${ / ( ) \1/ } ` ) . toThrow ( ) ;
61- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<n>a|\g<n&R=2>${ / ( ) \1/ } )` ) . toThrow ( ) ;
62- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<n>a|\g<n&R=2>)${ / ( ) \1/ } ` ) . toThrow ( ) ;
63- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `${ / ( ) \1/ } a(?R=2)?b` ) . toThrow ( ) ;
64- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<n>${ / ( ) \1/ } a|\g<n&R=2>)` ) . toThrow ( ) ;
65- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `${ / ( ) \1/ } (?<n>a|\g<n&R=2>)` ) . toThrow ( ) ;
66- } ) ;
67-
68- it ( 'should not allow subroutine definition groups when using recursion' , ( ) => {
69- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `a(?R=2)?b(?(DEFINE))` ) . toThrow ( ) ;
70- expect ( ( ) => regex ( { plugins : [ recursion ] } ) `(?<n>a|\g<n&R=2>)(?(DEFINE))` ) . toThrow ( ) ;
71- } ) ;
72117} ) ;
0 commit comments