blob: 35f1164ec788adb5e922c7a95b81d23674991d5b [file] [edit]
// Test backreferences in MatchOnly mode (test() method).
// This tests the JIT implementation of backreferences when m_regs.output is not available.
function testMatchOnly(re, str, expected, description) {
let result = re.test(str);
if (result !== expected)
throw new Error(`${description || re.toString()}.test("${str}") = ${result}, expected ${expected}`);
}
// ==== Basic backreferences ====
// Simple backreference - match
testMatchOnly(/(.)\1/, "aa", true, "simple backref match");
testMatchOnly(/(.)\1/, "bb", true, "simple backref match 2");
testMatchOnly(/(.)\1/, "11", true, "simple backref match digit");
// Simple backreference - no match
testMatchOnly(/(.)\1/, "ab", false, "simple backref no match");
testMatchOnly(/(.)\1/, "a", false, "simple backref too short");
testMatchOnly(/(.)\1/, "", false, "simple backref empty");
// ==== Multiple backreferences ====
testMatchOnly(/(.)(.)\1\2/, "abab", true, "multiple backref match");
testMatchOnly(/(.)(.)\1\2/, "abba", false, "multiple backref no match");
testMatchOnly(/(.)(.)\1\2/, "xyzxy", false, "multiple backref partial");
// Three capture groups
testMatchOnly(/(.)(.)(.)(\1\2\3)/, "abcabc", true, "three groups match");
testMatchOnly(/(.)(.)(.)(\1\2\3)/, "abcdef", false, "three groups no match");
// ==== Quantified backreferences ====
// Greedy quantifiers
testMatchOnly(/(.)\1+/, "aaa", true, "backref + match");
testMatchOnly(/(.)\1+/, "a", false, "backref + no match (needs 2+)");
testMatchOnly(/(.)\1*/, "a", true, "backref * match (0 repeats ok)");
testMatchOnly(/(.)\1*/, "aaa", true, "backref * match multiple");
testMatchOnly(/(.)\1?/, "a", true, "backref ? match (0 ok)");
testMatchOnly(/(.)\1?/, "aa", true, "backref ? match (1 ok)");
testMatchOnly(/(.)\1{2}/, "aaa", true, "backref {2} match");
testMatchOnly(/(.)\1{2}/, "aa", false, "backref {2} no match");
testMatchOnly(/(.)\1{2,4}/, "aaaa", true, "backref {2,4} match");
testMatchOnly(/(.)\1{2,4}/, "aa", false, "backref {2,4} no match (too few)");
// Non-greedy quantifiers
testMatchOnly(/(.)\1+?/, "aaa", true, "backref +? match");
testMatchOnly(/(.)\1*?/, "a", true, "backref *? match");
testMatchOnly(/(.)\1??/, "a", true, "backref ?? match");
// ==== Named capture groups ====
testMatchOnly(/(?<char>.)\k<char>/, "aa", true, "named backref match");
testMatchOnly(/(?<char>.)\k<char>/, "ab", false, "named backref no match");
testMatchOnly(/(?<a>.)(?<b>.)\k<a>\k<b>/, "abab", true, "multiple named backref match");
testMatchOnly(/(?<a>.)(?<b>.)\k<a>\k<b>/, "abba", false, "multiple named backref no match");
// ==== Duplicate named capture groups ====
testMatchOnly(/(?:(?<x>a)|(?<x>b))\k<x>/, "aa", true, "duplicate named group match a");
testMatchOnly(/(?:(?<x>a)|(?<x>b))\k<x>/, "bb", true, "duplicate named group match b");
testMatchOnly(/(?:(?<x>a)|(?<x>b))\k<x>/, "ab", false, "duplicate named group no match");
testMatchOnly(/(?:(?<x>a)|(?<x>b))\k<x>/, "ba", false, "duplicate named group no match 2");
// More complex duplicate named groups
testMatchOnly(/(?:(?:(?<x>a)|(?<x>b))\k<x>){2}/, "aabb", true, "repeated duplicate named group");
testMatchOnly(/(?:(?:(?<x>a)|(?<x>b))\k<x>){2}/, "aaab", false, "repeated duplicate named group no match");
// ==== Nested parentheses ====
// /((.))\1\2/ captures one char in group 1 and 2 (same char), then expects that char twice more
testMatchOnly(/((.))\1\2/, "aaaa", true, "nested groups match");
testMatchOnly(/((.))\1\2/, "abab", false, "nested groups no match (different chars)");
testMatchOnly(/((.))\1\2/, "abcd", false, "nested groups no match");
// /((.)(.))\1/ captures two chars in group 1, then expects the same two chars
testMatchOnly(/((.)(.))\1/, "abab", true, "nested with multiple chars");
testMatchOnly(/((.)(.))\1/, "abcabc", false, "nested with multiple chars no match");
// ==== Case insensitive ====
testMatchOnly(/(.)\1/i, "aA", true, "ignoreCase match");
testMatchOnly(/(.)\1/i, "Aa", true, "ignoreCase match reverse");
testMatchOnly(/(.)\1/i, "ab", false, "ignoreCase no match");
testMatchOnly(/(?<x>.)\k<x>/i, "aA", true, "named ignoreCase match");
// ==== Unicode mode ====
testMatchOnly(/(.)\1/u, "aa", true, "unicode mode match");
testMatchOnly(/(.)\1/u, "ab", false, "unicode mode no match");
// Non-BMP characters (surrogate pairs)
testMatchOnly(/(.)\1/u, "\u{1F600}\u{1F600}", true, "emoji match");
testMatchOnly(/(.)\1/u, "\u{1F600}\u{1F601}", false, "emoji no match");
testMatchOnly(/(.)\1/u, "\u{10000}\u{10000}", true, "non-BMP match");
// ==== Mixed with non-capturing groups ====
testMatchOnly(/(?:a)(b)\1/, "abb", true, "non-capture prefix match");
testMatchOnly(/(?:a)(b)\1/, "abc", false, "non-capture prefix no match");
testMatchOnly(/(a)(?:b)\1/, "aba", true, "non-capture middle match");
testMatchOnly(/(a)(?:b)(c)\1\2/, "abcac", true, "mixed groups match");
// ==== Complex patterns (marked.js fences style) ====
testMatchOnly(/(`{3,}).*\1/, "```code```", true, "fences pattern match");
testMatchOnly(/(`{3,}).*\1/, "````code````", true, "fences pattern 4 backticks");
testMatchOnly(/(`{3,}).*\1/, "```code``", false, "fences pattern no match (fewer)");
testMatchOnly(/(`{3,}).*\1/, "```code", false, "fences pattern no match (none)");
// Tilde variant
testMatchOnly(/(~{3,}).*\1/, "~~~code~~~", true, "tilde fences match");
testMatchOnly(/(~{3,}).*\1/, "~~~code~~", false, "tilde fences no match");
// ==== Lookahead with backreference ====
testMatchOnly(/(?=(.)\1)aa/, "aa", true, "lookahead with backref match");
testMatchOnly(/(?=(.)\1)ab/, "ab", false, "lookahead with backref no match");
// ==== Empty capture ====
testMatchOnly(/()\1/, "", true, "empty capture match");
testMatchOnly(/()\1a/, "a", true, "empty capture with char");
testMatchOnly(/()?\1/, "", true, "optional empty capture");
// ==== Backreference to unmatched group ====
testMatchOnly(/(a)?\1/, "aa", true, "optional group matched");
testMatchOnly(/(a)?\1/, "", true, "optional group unmatched (backref matches empty)");
testMatchOnly(/(a)?\1/, "a", true, "optional group captures but backref empty ok");
// ==== Long strings ====
let longStr = "a".repeat(1000);
testMatchOnly(/(.)\1{999}/, longStr, true, "long string match");
testMatchOnly(/(.)\1{1000}/, longStr, false, "long string no match (off by one)");
// Repeated pattern
let repeatedPairs = "ab".repeat(500);
testMatchOnly(/(..)\1{499}/, repeatedPairs, true, "repeated pairs match");
// ==== Backreference at different positions ====
testMatchOnly(/^(.)\1$/, "aa", true, "anchored backref match");
testMatchOnly(/^(.)\1$/, "aaa", false, "anchored backref no match (too long)");
testMatchOnly(/(.)\1$/, "baa", true, "end anchored backref match");
testMatchOnly(/^(.)\1/, "aab", true, "start anchored backref match");
// ==== Word boundaries ====
testMatchOnly(/\b(.)\1\b/, "aa", true, "word boundary backref match");
testMatchOnly(/(\w)\1/, "aa", true, "word char backref match");
testMatchOnly(/(\w)\1/, "a1", false, "word char backref no match");
// ==== Character classes ====
testMatchOnly(/([aeiou])\1/, "ee", true, "vowel backref match");
testMatchOnly(/([aeiou])\1/, "ea", false, "vowel backref no match");
testMatchOnly(/([0-9])\1/, "55", true, "digit class backref match");
testMatchOnly(/([^a])\1/, "bb", true, "negated class backref match");
// ==== Multiline mode ====
testMatchOnly(/^(.)\1/m, "aa\nbb", true, "multiline backref match");
testMatchOnly(/(.)\1$/m, "ab\ncc", true, "multiline end backref match");
// ==== dotAll mode ====
testMatchOnly(/(.)\1/s, "aa", true, "dotAll backref match");
testMatchOnly(/(.).\1/s, "a\na", true, "dotAll with newline match");
testMatchOnly(/(.).\1/s, "a\nb", false, "dotAll with newline no match");
// ==== Stress test: Many capture groups ====
// 10 capture groups
testMatchOnly(/(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\1\2\3\4\5\6\7\8\9\10/,
"abcdefghijabcdefghij", true, "10 groups match");
testMatchOnly(/(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)\1\2\3\4\5\6\7\8\9\10/,
"abcdefghijabcdefghix", false, "10 groups no match");
// ==== Alternation with backreference ====
testMatchOnly(/(a|b)\1/, "aa", true, "alternation backref match a");
testMatchOnly(/(a|b)\1/, "bb", true, "alternation backref match b");
testMatchOnly(/(a|b)\1/, "ab", false, "alternation backref no match");
testMatchOnly(/(a|b)\1/, "ba", false, "alternation backref no match 2");
// ==== Complex alternation ====
testMatchOnly(/(?:(a)|(b))\1\2/, "a", false, "complex alt - partial capture");
testMatchOnly(/((?:a|b)+)\1/, "abab", true, "repeated alt group match");
testMatchOnly(/((?:a|b)+)\1/, "abba", true, "repeated alt group match (bb part)");
testMatchOnly(/((?:a|b)+)\1/, "abcd", false, "repeated alt group no match");