eslint-plugin-unicorn
Version:
Various awesome ESLint rules
136 lines (118 loc) • 3.3 kB
JavaScript
;
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({
name: 'exec',
length: 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({
name: 'match',
length: 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;
};
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;
}
])
);
module.exports = {
create: checkVueTemplate(create),
meta: {
type: 'suggestion',
docs: {
description: 'Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`.'
},
fixable: 'code',
messages
}
};