prefer-regexp-test.js 3.3 KB

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