123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- 'use strict';
- const {isParenthesized, getStaticValue} = require('eslint-utils');
- const {checkVueTemplate} = require('./utils/rule.js');
- const {methodCallSelector} = require('./selectors/index.js');
- const {isBooleanNode} = require('./utils/boolean.js');
- const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
- const REGEXP_EXEC = 'regexp-exec';
- const STRING_MATCH = 'string-match';
- const messages = {
- [REGEXP_EXEC]: 'Prefer `.test(…)` over `.exec(…)`.',
- [STRING_MATCH]: 'Prefer `RegExp#test(…)` over `String#match(…)`.',
- };
- const cases = [
- {
- type: REGEXP_EXEC,
- selector: methodCallSelector({
- method: 'exec',
- argumentsLength: 1,
- }),
- getNodes: node => ({
- stringNode: node.arguments[0],
- methodNode: node.callee.property,
- regexpNode: node.callee.object,
- }),
- fix: (fixer, {methodNode}) => fixer.replaceText(methodNode, 'test'),
- },
- {
- type: STRING_MATCH,
- selector: methodCallSelector({
- method: 'match',
- argumentsLength: 1,
- }),
- getNodes: node => ({
- stringNode: node.callee.object,
- methodNode: node.callee.property,
- regexpNode: node.arguments[0],
- }),
- * fix(fixer, {stringNode, methodNode, regexpNode}, sourceCode) {
- yield fixer.replaceText(methodNode, 'test');
- let stringText = sourceCode.getText(stringNode);
- if (
- !isParenthesized(regexpNode, sourceCode)
- // Only `SequenceExpression` need add parentheses
- && stringNode.type === 'SequenceExpression'
- ) {
- stringText = `(${stringText})`;
- }
- yield fixer.replaceText(regexpNode, stringText);
- let regexpText = sourceCode.getText(regexpNode);
- if (
- !isParenthesized(stringNode, sourceCode)
- && shouldAddParenthesesToMemberExpressionObject(regexpNode, sourceCode)
- ) {
- regexpText = `(${regexpText})`;
- }
- // The nodes that pass `isBooleanNode` cannot have an ASI problem.
- yield fixer.replaceText(stringNode, regexpText);
- },
- },
- ];
- const isRegExpNode = node => {
- if (node.type === 'Literal' && node.regex) {
- return true;
- }
- if (
- node.type === 'NewExpression'
- && node.callee.type === 'Identifier'
- && node.callee.name === 'RegExp'
- ) {
- return true;
- }
- return false;
- };
- /** @param {import('eslint').Rule.RuleContext} context */
- const create = context => Object.fromEntries(
- cases.map(checkCase => [
- checkCase.selector,
- node => {
- if (!isBooleanNode(node)) {
- return;
- }
- const {type, getNodes, fix} = checkCase;
- const nodes = getNodes(node);
- const {methodNode, regexpNode} = nodes;
- if (regexpNode.type === 'Literal' && !regexpNode.regex) {
- return;
- }
- const problem = {
- node: type === REGEXP_EXEC ? methodNode : node,
- messageId: type,
- };
- if (!isRegExpNode(regexpNode)) {
- const staticResult = getStaticValue(regexpNode, context.getScope());
- if (staticResult) {
- const {value} = staticResult;
- if (
- Object.prototype.toString.call(value) !== '[object RegExp]'
- || value.flags.includes('g')
- ) {
- return problem;
- }
- }
- }
- problem.fix = fixer => fix(fixer, nodes, context.getSourceCode());
- return problem;
- },
- ]),
- );
- /** @type {import('eslint').Rule.RuleModule} */
- module.exports = {
- create: checkVueTemplate(create),
- meta: {
- type: 'suggestion',
- docs: {
- description: 'Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`.',
- },
- fixable: 'code',
- messages,
- },
- };
|