// Copyright (C) 2017 Mathias Bynens. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- description: | Collection of functions used to assert the correctness of RegExp objects. defines: [buildString, testPropertyEscapes, testPropertyOfStrings, testExtendedCharacterClass, matchValidator] ---*/ function buildString(args) { // Use member expressions rather than destructuring `args` for improved // compatibility with engines that only implement assignment patterns // partially or not at all. const loneCodePoints = args.loneCodePoints; const ranges = args.ranges; const CHUNK_SIZE = 10000; let result = String.fromCodePoint.apply(null, loneCodePoints); for (let i = 0; i < ranges.length; i++) { let range = ranges[i]; let start = range[0]; let end = range[1]; let codePoints = []; for (let length = 0, codePoint = start; codePoint <= end; codePoint++) { codePoints[length++] = codePoint; if (length === CHUNK_SIZE) { result += String.fromCodePoint.apply(null, codePoints); codePoints.length = length = 0; } } result += String.fromCodePoint.apply(null, codePoints); } return result; } function printCodePoint(codePoint) { const hex = codePoint .toString(16) .toUpperCase() .padStart(6, "0"); return `U+${hex}`; } function printStringCodePoints(string) { const buf = []; for (let symbol of string) { let formatted = printCodePoint(symbol.codePointAt(0)); buf.push(formatted); } return buf.join(' '); } function testPropertyEscapes(regExp, string, expression) { if (!regExp.test(string)) { for (let symbol of string) { let formatted = printCodePoint(symbol.codePointAt(0)); assert( regExp.test(symbol), `\`${ expression }\` should match ${ formatted } (\`${ symbol }\`)` ); } } } function testPropertyOfStrings(args) { // Use member expressions rather than destructuring `args` for improved // compatibility with engines that only implement assignment patterns // partially or not at all. const regExp = args.regExp; const expression = args.expression; const matchStrings = args.matchStrings; const nonMatchStrings = args.nonMatchStrings; const allStrings = matchStrings.join(''); if (!regExp.test(allStrings)) { for (let string of matchStrings) { assert( regExp.test(string), `\`${ expression }\` should match ${ string } (${ printStringCodePoints(string) })` ); } } if (!nonMatchStrings) return; const allNonMatchStrings = nonMatchStrings.join(''); if (regExp.test(allNonMatchStrings)) { for (let string of nonMatchStrings) { assert( !regExp.test(string), `\`${ expression }\` should not match ${ string } (${ printStringCodePoints(string) })` ); } } } // The exact same logic can be used to test extended character classes // as enabled through the RegExp `v` flag. This is useful to test not // just standalone properties of strings, but also string literals, and // set operations. const testExtendedCharacterClass = testPropertyOfStrings; // Returns a function that validates a RegExp match result. // // Example: // // var validate = matchValidator(['b'], 1, 'abc'); // validate(/b/.exec('abc')); // function matchValidator(expectedEntries, expectedIndex, expectedInput) { return function(match) { assert.compareArray(match, expectedEntries, 'Match entries'); assert.sameValue(match.index, expectedIndex, 'Match index'); assert.sameValue(match.input, expectedInput, 'Match input'); } }