prefer-regexp-test.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. 'use strict';
  2. const {isParenthesized, getStaticValue} = require('eslint-utils');
  3. const {checkVueTemplate} = require('./utils/rule.js');
  4. const {methodCallSelector} = require('./selectors/index.js');
  5. const {isBooleanNode} = require('./utils/boolean.js');
  6. const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
  7. const REGEXP_EXEC = 'regexp-exec';
  8. const STRING_MATCH = 'string-match';
  9. const messages = {
  10. [REGEXP_EXEC]: 'Prefer `.test(…)` over `.exec(…)`.',
  11. [STRING_MATCH]: 'Prefer `RegExp#test(…)` over `String#match(…)`.',
  12. };
  13. const cases = [
  14. {
  15. type: REGEXP_EXEC,
  16. selector: methodCallSelector({
  17. method: 'exec',
  18. argumentsLength: 1,
  19. }),
  20. getNodes: node => ({
  21. stringNode: node.arguments[0],
  22. methodNode: node.callee.property,
  23. regexpNode: node.callee.object,
  24. }),
  25. fix: (fixer, {methodNode}) => fixer.replaceText(methodNode, 'test'),
  26. },
  27. {
  28. type: STRING_MATCH,
  29. selector: methodCallSelector({
  30. method: 'match',
  31. argumentsLength: 1,
  32. }),
  33. getNodes: node => ({
  34. stringNode: node.callee.object,
  35. methodNode: node.callee.property,
  36. regexpNode: node.arguments[0],
  37. }),
  38. * fix(fixer, {stringNode, methodNode, regexpNode}, sourceCode) {
  39. yield fixer.replaceText(methodNode, 'test');
  40. let stringText = sourceCode.getText(stringNode);
  41. if (
  42. !isParenthesized(regexpNode, sourceCode)
  43. // Only `SequenceExpression` need add parentheses
  44. && stringNode.type === 'SequenceExpression'
  45. ) {
  46. stringText = `(${stringText})`;
  47. }
  48. yield fixer.replaceText(regexpNode, stringText);
  49. let regexpText = sourceCode.getText(regexpNode);
  50. if (
  51. !isParenthesized(stringNode, sourceCode)
  52. && shouldAddParenthesesToMemberExpressionObject(regexpNode, sourceCode)
  53. ) {
  54. regexpText = `(${regexpText})`;
  55. }
  56. // The nodes that pass `isBooleanNode` cannot have an ASI problem.
  57. yield fixer.replaceText(stringNode, regexpText);
  58. },
  59. },
  60. ];
  61. const isRegExpNode = node => {
  62. if (node.type === 'Literal' && node.regex) {
  63. return true;
  64. }
  65. if (
  66. node.type === 'NewExpression'
  67. && node.callee.type === 'Identifier'
  68. && node.callee.name === 'RegExp'
  69. ) {
  70. return true;
  71. }
  72. return false;
  73. };
  74. /** @param {import('eslint').Rule.RuleContext} context */
  75. const create = context => Object.fromEntries(
  76. cases.map(checkCase => [
  77. checkCase.selector,
  78. node => {
  79. if (!isBooleanNode(node)) {
  80. return;
  81. }
  82. const {type, getNodes, fix} = checkCase;
  83. const nodes = getNodes(node);
  84. const {methodNode, regexpNode} = nodes;
  85. if (regexpNode.type === 'Literal' && !regexpNode.regex) {
  86. return;
  87. }
  88. const problem = {
  89. node: type === REGEXP_EXEC ? methodNode : node,
  90. messageId: type,
  91. };
  92. if (!isRegExpNode(regexpNode)) {
  93. const staticResult = getStaticValue(regexpNode, context.getScope());
  94. if (staticResult) {
  95. const {value} = staticResult;
  96. if (
  97. Object.prototype.toString.call(value) !== '[object RegExp]'
  98. || value.flags.includes('g')
  99. ) {
  100. return problem;
  101. }
  102. }
  103. }
  104. problem.fix = fixer => fix(fixer, nodes, context.getSourceCode());
  105. return problem;
  106. },
  107. ]),
  108. );
  109. /** @type {import('eslint').Rule.RuleModule} */
  110. module.exports = {
  111. create: checkVueTemplate(create),
  112. meta: {
  113. type: 'suggestion',
  114. docs: {
  115. description: 'Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`.',
  116. },
  117. fixable: 'code',
  118. messages,
  119. },
  120. };