blob: 9218a38151bd7305fbdbbb85e97e2ff2fafd6b96 [file] [log] [blame]
/**
* The MIT License (MIT)
* Copyright (c) 2017-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
*/
'use strict';
/**
* A regexp-tree plugin to remove unnecessary escape.
*
* \e -> e
*
* [\(] -> [(]
*/
module.exports = {
_hasXFlag: false,
init: function init(ast) {
this._hasXFlag = ast.flags.includes('x');
},
Char: function Char(path) {
var node = path.node;
if (!node.escaped) {
return;
}
if (shouldUnescape(path, this._hasXFlag)) {
delete node.escaped;
}
}
};
function shouldUnescape(path, hasXFlag) {
var value = path.node.value,
index = path.index,
parent = path.parent;
// In char class (, etc are allowed.
if (parent.type !== 'CharacterClass' && parent.type !== 'ClassRange') {
return !preservesEscape(value, index, parent, hasXFlag);
}
return !preservesInCharClass(value, index, parent);
}
/**
* \], \\, \^, \-
*/
function preservesInCharClass(value, index, parent) {
if (value === '^') {
// Avoid [\^a] turning into [^a]
return index === 0 && !parent.negative;
}
if (value === '-') {
// Avoid [a\-z] turning into [a-z]
return index !== 0 && index !== parent.expressions.length - 1;
}
return (/[\]\\]/.test(value)
);
}
function preservesEscape(value, index, parent, hasXFlag) {
if (value === '{') {
return preservesOpeningCurlyBraceEscape(index, parent);
}
if (value === '}') {
return preservesClosingCurlyBraceEscape(index, parent);
}
if (hasXFlag && /[ #]/.test(value)) {
return true;
}
return (/[*[()+?^$./\\|]/.test(value)
);
}
function consumeNumbers(startIndex, parent, rtl) {
var i = startIndex;
var siblingNode = (rtl ? i >= 0 : i < parent.expressions.length) && parent.expressions[i];
while (siblingNode && siblingNode.type === 'Char' && siblingNode.kind === 'simple' && !siblingNode.escaped && /\d/.test(siblingNode.value)) {
rtl ? i-- : i++;
siblingNode = (rtl ? i >= 0 : i < parent.expressions.length) && parent.expressions[i];
}
return Math.abs(startIndex - i);
}
function isSimpleChar(node, value) {
return node && node.type === 'Char' && node.kind === 'simple' && !node.escaped && node.value === value;
}
function preservesOpeningCurlyBraceEscape(index, parent) {
var nbFollowingNumbers = consumeNumbers(index + 1, parent);
var i = index + nbFollowingNumbers + 1;
var nextSiblingNode = i < parent.expressions.length && parent.expressions[i];
if (nbFollowingNumbers) {
// Avoid \{3} turning into {3}
if (isSimpleChar(nextSiblingNode, '}')) {
return true;
}
if (isSimpleChar(nextSiblingNode, ',')) {
nbFollowingNumbers = consumeNumbers(i + 1, parent);
i = i + nbFollowingNumbers + 1;
nextSiblingNode = i < parent.expressions.length && parent.expressions[i];
// Avoid \{3,} turning into {3,}
return isSimpleChar(nextSiblingNode, '}');
}
}
return false;
}
function preservesClosingCurlyBraceEscape(index, parent) {
var nbPrecedingNumbers = consumeNumbers(index - 1, parent, true);
var i = index - nbPrecedingNumbers - 1;
var previousSiblingNode = i >= 0 && parent.expressions[i];
// Avoid {3\} turning into {3}
if (nbPrecedingNumbers && isSimpleChar(previousSiblingNode, '{')) {
return true;
}
if (isSimpleChar(previousSiblingNode, ',')) {
nbPrecedingNumbers = consumeNumbers(i - 1, parent, true);
i = i - nbPrecedingNumbers - 1;
previousSiblingNode = i < parent.expressions.length && parent.expressions[i];
// Avoid {3,\} turning into {3,}
return nbPrecedingNumbers && isSimpleChar(previousSiblingNode, '{');
}
return false;
}