| /** |
| * 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; |
| } |