char-class-to-single-char-transform.js 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. /**
  2. * The MIT License (MIT)
  3. * Copyright (c) 2017-present Dmitry Soshnikov <dmitry.soshnikov@gmail.com>
  4. */
  5. 'use strict';
  6. /**
  7. * A regexp-tree plugin to replace single char character classes with
  8. * just that character.
  9. *
  10. * [\d] -> \d, [^\w] -> \W
  11. */
  12. module.exports = {
  13. CharacterClass: function CharacterClass(path) {
  14. var node = path.node;
  15. if (node.expressions.length !== 1 || !hasAppropriateSiblings(path) || !isAppropriateChar(node.expressions[0])) {
  16. return;
  17. }
  18. var _node$expressions$ = node.expressions[0],
  19. value = _node$expressions$.value,
  20. kind = _node$expressions$.kind,
  21. escaped = _node$expressions$.escaped;
  22. if (node.negative) {
  23. // For negative can extract only meta chars like [^\w] -> \W
  24. // cannot do for [^a] -> a (wrong).
  25. if (!isMeta(value)) {
  26. return;
  27. }
  28. value = getInverseMeta(value);
  29. }
  30. path.replace({
  31. type: 'Char',
  32. value: value,
  33. kind: kind,
  34. escaped: escaped || shouldEscape(value)
  35. });
  36. }
  37. };
  38. function isAppropriateChar(node) {
  39. return node.type === 'Char' &&
  40. // We don't extract [\b] (backspace) since \b has different
  41. // semantics (word boundary).
  42. node.value !== '\\b';
  43. }
  44. function isMeta(value) {
  45. return (/^\\[dwsDWS]$/.test(value)
  46. );
  47. }
  48. function getInverseMeta(value) {
  49. return (/[dws]/.test(value) ? value.toUpperCase() : value.toLowerCase()
  50. );
  51. }
  52. function hasAppropriateSiblings(path) {
  53. var parent = path.parent,
  54. index = path.index;
  55. if (parent.type !== 'Alternative') {
  56. return true;
  57. }
  58. var previousNode = parent.expressions[index - 1];
  59. if (previousNode == null) {
  60. return true;
  61. }
  62. // Don't optimized \1[0] to \10
  63. if (previousNode.type === 'Backreference' && previousNode.kind === 'number') {
  64. return false;
  65. }
  66. // Don't optimized \2[0] to \20
  67. if (previousNode.type === 'Char' && previousNode.kind === 'decimal') {
  68. return false;
  69. }
  70. return true;
  71. }
  72. // Note: \{ and \} are always preserved to avoid `a[{]2[}]` turning
  73. // into `a{2}`.
  74. function shouldEscape(value) {
  75. return (/[*[()+?$./{}|]/.test(value)
  76. );
  77. }