Skip to content

Commit

Permalink
Tests: Added test for zero-width lookbehinds (PrismJS#2220)
Browse files Browse the repository at this point in the history
  • Loading branch information
RunDevelopment authored and quentinvernot committed Sep 11, 2020
1 parent e056770 commit c9e0715
Showing 1 changed file with 53 additions and 34 deletions.
87 changes: 53 additions & 34 deletions tests/pattern-tests.js
Expand Up @@ -147,6 +147,31 @@ function testPatterns(Prism) {
});
}

/**
* Returns whether the given element will always have zero width meaning that it doesn't consume characters.
*
* @param {Element} element
* @returns {boolean}
*/
function isAlwaysZeroWidth(element) {
switch (element.type) {
case 'Assertion':
// assertions == ^, $, \b, lookarounds
return true;
case 'Quantifier':
return element.max === 0 || isAlwaysZeroWidth(element.element);
case 'CapturingGroup':
case 'Group':
// every element in every alternative has to be of zero length
return element.alternatives.every(alt => alt.elements.every(isAlwaysZeroWidth));
case 'Backreference':
// on if the group referred to is of zero length
return isAlwaysZeroWidth(element.resolved);
default:
return false; // what's left are characters
}
}


it('- should not match the empty string', function () {
forEachPattern(({ pattern, tokenPath }) => {
Expand All @@ -168,32 +193,7 @@ function testPatterns(Prism) {
});
});

it('- should not have lookbehind groups which can be preceded by other some characters', function () {
/**
* Returns whether the given element will have zero length meaning that it doesn't extend the matched string.
*
* @param {Element} element
* @returns {boolean}
*/
function isZeroLength(element) {
switch (element.type) {
case 'Assertion':
// assertions == ^, $, \b, lookarounds
return true;
case 'Quantifier':
return element.max === 0 || isZeroLength(element.element);
case 'CapturingGroup':
case 'Group':
// every element in every alternative has to be of zero length
return element.alternatives.every(alt => alt.elements.every(isZeroLength));
case 'Backreference':
// on if the group referred to is of zero length
return isZeroLength(element.resolved);
default:
return false; // what's left are characters
}
}

it('- should not have lookbehind groups that can be preceded by other some characters', function () {
/**
* Returns whether the given element will always match the start of the string.
*
Expand All @@ -205,7 +205,7 @@ function testPatterns(Prism) {
switch (parent.type) {
case 'Alternative':
// all elements before this element have to of zero length
if (!parent.elements.slice(0, parent.elements.indexOf(element)).every(isZeroLength)) {
if (!parent.elements.slice(0, parent.elements.indexOf(element)).every(isAlwaysZeroWidth)) {
return false;
}
const grandParent = parent.parent;
Expand All @@ -216,7 +216,7 @@ function testPatterns(Prism) {
}

case 'Quantifier':
if (parent.max === null /* null == open ended */ || parent.max >= 2) {
if (parent.max >= 2) {
return false;
} else {
return isFirstMatch(parent);
Expand All @@ -228,13 +228,32 @@ function testPatterns(Prism) {
}

forEachPattern(({ ast, tokenPath, lookbehind }) => {
if (lookbehind) {
forEachCapturingGroup(ast.pattern, ({ group, number }) => {
if (number === 1 && !isFirstMatch(group)) {
assert.fail(`Token ${tokenPath}: The lookbehind group (if matched at all) always has to be at index 0 relative to the whole match.`);
}
});
if (!lookbehind) {
return;
}
forEachCapturingGroup(ast.pattern, ({ group, number }) => {
if (number === 1 && !isFirstMatch(group)) {
assert.fail(`Token ${tokenPath}: `
+ `The lookbehind group (if matched) always has to be at index 0 relative to the whole match.`);
}
});
});
});

it('- should not have lookbehind groups that only have zero-width alternatives', function () {
forEachPattern(({ ast, tokenPath, lookbehind, reportError }) => {
if (!lookbehind) {
return;
}
forEachCapturingGroup(ast.pattern, ({ group, number }) => {
if (number === 1 && isAlwaysZeroWidth(group)) {
const groupContent = group.raw.substr(1, group.raw.length - 2);
const replacement = group.alternatives.length === 1 ? groupContent : `(?:${groupContent})`;
reportError(`Token ${tokenPath}: The lookbehind group ${group.raw} does not consume characters. `
+ `Therefor it is not necessary to use a lookbehind group. `
+ `Replacing the lookbehind group with: ${replacement}`);
}
});
});
});

Expand Down

0 comments on commit c9e0715

Please sign in to comment.