better-regex.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. 'use strict';
  2. const cleanRegexp = require('clean-regexp');
  3. const {optimize} = require('regexp-tree');
  4. const quoteString = require('./utils/quote-string.js');
  5. const {newExpressionSelector} = require('./selectors/index.js');
  6. const {isStringLiteral} = require('./ast/index.js');
  7. const MESSAGE_ID = 'better-regex';
  8. const messages = {
  9. [MESSAGE_ID]: '{{original}} can be optimized to {{optimized}}.',
  10. };
  11. const newRegExp = newExpressionSelector({name: 'RegExp', minimumArguments: 1});
  12. /** @param {import('eslint').Rule.RuleContext} context */
  13. const create = context => {
  14. const {sortCharacterClasses} = context.options[0] || {};
  15. const ignoreList = [];
  16. if (sortCharacterClasses === false) {
  17. ignoreList.push('charClassClassrangesMerge');
  18. }
  19. return {
  20. 'Literal[regex]'(node) {
  21. const {raw: original, regex} = node;
  22. // Regular Expressions with `u` flag are not well handled by `regexp-tree`
  23. // https://github.com/DmitrySoshnikov/regexp-tree/issues/162
  24. if (regex.flags.includes('u')) {
  25. return;
  26. }
  27. let optimized = original;
  28. try {
  29. optimized = optimize(original, undefined, {blacklist: ignoreList}).toString();
  30. } catch (error) {
  31. return {
  32. node,
  33. data: {
  34. original,
  35. error: error.message,
  36. },
  37. message: 'Problem parsing {{original}}: {{error}}',
  38. };
  39. }
  40. if (original === optimized) {
  41. return;
  42. }
  43. return {
  44. node,
  45. messageId: MESSAGE_ID,
  46. data: {
  47. original,
  48. optimized,
  49. },
  50. fix: fixer => fixer.replaceText(node, optimized),
  51. };
  52. },
  53. [newRegExp](node) {
  54. const [patternNode, flagsNode] = node.arguments;
  55. if (!isStringLiteral(patternNode)) {
  56. return;
  57. }
  58. const oldPattern = patternNode.value;
  59. const flags = isStringLiteral(flagsNode)
  60. ? flagsNode.value
  61. : '';
  62. const newPattern = cleanRegexp(oldPattern, flags);
  63. if (oldPattern !== newPattern) {
  64. return {
  65. node,
  66. messageId: MESSAGE_ID,
  67. data: {
  68. original: oldPattern,
  69. optimized: newPattern,
  70. },
  71. fix: fixer => fixer.replaceText(
  72. patternNode,
  73. quoteString(newPattern, patternNode.raw.charAt(0)),
  74. ),
  75. };
  76. }
  77. },
  78. };
  79. };
  80. const schema = [
  81. {
  82. type: 'object',
  83. additionalProperties: false,
  84. properties: {
  85. sortCharacterClasses: {
  86. type: 'boolean',
  87. default: true,
  88. },
  89. },
  90. },
  91. ];
  92. /** @type {import('eslint').Rule.RuleModule} */
  93. module.exports = {
  94. create,
  95. meta: {
  96. type: 'suggestion',
  97. docs: {
  98. description: 'Improve regexes by making them shorter, consistent, and safer.',
  99. },
  100. fixable: 'code',
  101. schema,
  102. messages,
  103. },
  104. };