Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add is{Call,New}Expression to replace selectors #2083

Merged
merged 15 commits into from
May 12, 2023
120 changes: 120 additions & 0 deletions rules/ast/call-or-new-expression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use strict';

/**
@typedef {
{
name?: string,
names?: string[],
argumentsLength?: number,
minimumArguments?: number,
maximumArguments?: number,
allowSpreadElement?: boolean,
optional?: boolean,
} | string | string[]
} CallOrNewExpressionOptions
*/
function create(node, options, type) {
if (node?.type !== type) {
return false;
}

if (typeof options === 'string') {
options = {names: [options]};
}

if (Array.isArray(options)) {
options = {names: options};
}

let {
name,
names,
argumentsLength,
minimumArguments,
maximumArguments,
allowSpreadElement,
optional,
} = {
minimumArguments: 0,
maximumArguments: Number.POSITIVE_INFINITY,
allowSpreadElement: false,
...options,
};

if (name) {
names = [name];
}

if (
(optional === true && (node.optional !== optional))
|| (
optional === false
// `node.optional` can be `undefined` in some parsers
&& node.optional
)
) {
return false;
}

if (typeof argumentsLength === 'number' && node.arguments.length !== argumentsLength) {
return false;
}

if (minimumArguments !== 0 && node.arguments.length < minimumArguments) {
return false;
}

if (Number.isFinite(maximumArguments) && node.arguments.length > maximumArguments) {
return false;
}

if (!allowSpreadElement) {
const maximumArgumentsLength = Number.isFinite(maximumArguments) ? maximumArguments : argumentsLength;
if (
typeof maximumArgumentsLength === 'number'
&& node.arguments.some(
(node, index) =>
node.type === 'SpreadElement'
&& index < maximumArgumentsLength,
)
) {
return false;
}
}

if (
Array.isArray(names)
&& names.length > 0
&& (
node.callee.type !== 'Identifier'
|| !names.includes(node.callee.name)
)
) {
return false;
}

return true;
}

/**
@param {CallOrNewExpressionOptions} [options]
@returns {boolean}
*/
const isCallExpression = (node, options) => create(node, options, 'CallExpression');

/**
@param {CallOrNewExpressionOptions} [options]
@returns {boolean}
*/
const isNewExpression = (node, options) => {
if (typeof options?.optional === 'boolean') {
throw new TypeError('Cannot check node.optional in `isNewExpression`.');
}

return create(node, options, 'NewExpression');
};

module.exports = {
isCallExpression,
isNewExpression,
};
3 changes: 2 additions & 1 deletion rules/ast/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ module.exports = {
isEmptyNode: require('./is-empty-node.js'),
isStaticRequire: require('./is-static-require.js'),
isUndefined: require('./is-undefined.js'),
isNewExpression: require('./is-new-expression.js'),
isNewExpression: require('./call-or-new-expression.js').isNewExpression,
isCallExpression: require('./call-or-new-expression.js').isCallExpression,
};
22 changes: 0 additions & 22 deletions rules/ast/is-new-expression.js

This file was deleted.

11 changes: 6 additions & 5 deletions rules/better-regex.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
const cleanRegexp = require('clean-regexp');
const {optimize} = require('regexp-tree');
const escapeString = require('./utils/escape-string.js');
const {newExpressionSelector} = require('./selectors/index.js');
const {isStringLiteral} = require('./ast/index.js');
const {isStringLiteral, isNewExpression} = require('./ast/index.js');

const MESSAGE_ID = 'better-regex';
const MESSAGE_ID_PARSE_ERROR = 'better-regex/parse-error';
Expand All @@ -12,8 +11,6 @@ const messages = {
[MESSAGE_ID_PARSE_ERROR]: 'Problem parsing {{original}}: {{error}}',
};

const newRegExp = newExpressionSelector({name: 'RegExp', minimumArguments: 1});

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const {sortCharacterClasses} = context.options[0] || {};
Expand Down Expand Up @@ -80,7 +77,11 @@ const create = context => {
fix: fixer => fixer.replaceText(node, optimized),
});
},
[newRegExp](node) {
NewExpression(node) {
if (!isNewExpression(node, {name: 'RegExp', minimumArguments: 1})) {
return;
}

const [patternNode, flagsNode] = node.arguments;

if (!isStringLiteral(patternNode)) {
Expand Down
19 changes: 12 additions & 7 deletions rules/no-new-array.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';
const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-utils');
const needsSemicolon = require('./utils/needs-semicolon.js');
const {newExpressionSelector} = require('./selectors/index.js');
const isNumber = require('./utils/is-number.js');
const {isNewExpression} = require('./ast/index.js');

const MESSAGE_ID_ERROR = 'error';
const MESSAGE_ID_LENGTH = 'array-length';
Expand All @@ -14,13 +14,18 @@ const messages = {
[MESSAGE_ID_ONLY_ELEMENT]: 'The argument is the only element of array.',
[MESSAGE_ID_SPREAD]: 'Spread the argument.',
};
const newArraySelector = newExpressionSelector({
name: 'Array',
argumentsLength: 1,
allowSpreadElement: true,
});

function getProblem(context, node) {
if (
!isNewExpression(node, {
name: 'Array',
argumentsLength: 1,
allowSpreadElement: true,
})
) {
return;
}

const problem = {
node,
messageId: MESSAGE_ID_ERROR,
Expand Down Expand Up @@ -79,7 +84,7 @@ function getProblem(context, node) {

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[newArraySelector](node) {
NewExpression(node) {
return getProblem(context, node);
},
});
Expand Down
8 changes: 6 additions & 2 deletions rules/no-new-buffer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';
const {getStaticValue} = require('@eslint-community/eslint-utils');
const {newExpressionSelector} = require('./selectors/index.js');
const {switchNewExpressionToCallExpression} = require('./fix/index.js');
const isNumber = require('./utils/is-number.js');
const {isNewExpression} = require('./ast/index.js');

const ERROR = 'error';
const ERROR_UNKNOWN = 'error-unknown';
Expand Down Expand Up @@ -54,7 +54,11 @@ function fix(node, sourceCode, method) {
const create = context => {
const {sourceCode} = context;
return {
[newExpressionSelector('Buffer')](node) {
NewExpression(node) {
if (!isNewExpression(node, {name: 'Buffer'})) {
return;
}

const method = inferMethod(node.arguments, sourceCode.getScope(node));

if (method) {
Expand Down
10 changes: 7 additions & 3 deletions rules/prefer-at.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ const {
getNegativeIndexLengthNode,
removeLengthNode,
} = require('./shared/negative-index.js');
const {methodCallSelector, callExpressionSelector} = require('./selectors/index.js');
const {methodCallSelector} = require('./selectors/index.js');
const {removeMemberExpressionProperty, removeMethodCall} = require('./fix/index.js');
const {isLiteral} = require('./ast/index.js');
const {isLiteral, isCallExpression} = require('./ast/index.js');

const MESSAGE_ID_NEGATIVE_INDEX = 'negative-index';
const MESSAGE_ID_INDEX = 'index';
Expand Down Expand Up @@ -275,7 +275,11 @@ function create(context) {

return problem;
},
[callExpressionSelector({argumentsLength: 1})](node) {
CallExpression(node) {
if (!isCallExpression(node, {argumentsLength: 1, optional: false})) {
return;
}

const matchedFunction = getLastFunctions.find(nameOrPath => isNodeMatchesNameOrPath(node.callee, nameOrPath));
if (!matchedFunction) {
return;
Expand Down
12 changes: 4 additions & 8 deletions rules/prefer-type-error.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use strict';
const {newExpressionSelector} = require('./selectors/index.js');
const {isNewExpression} = require('./ast/index.js');

const MESSAGE_ID = 'prefer-type-error';
const messages = {
Expand Down Expand Up @@ -51,11 +51,6 @@ const typeCheckGlobalIdentifiers = new Set([
'isFinite',
]);

const selector = [
'ThrowStatement',
newExpressionSelector({name: 'Error', path: 'argument'}),
].join('');

const isTypecheckingIdentifier = (node, callExpression, isMemberExpression) =>
callExpression !== undefined
&& callExpression.arguments.length > 0
Expand Down Expand Up @@ -125,9 +120,10 @@ const isTypechecking = node => node.type === 'IfStatement' && isTypecheckingExpr

/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
[selector](node) {
ThrowStatement(node) {
if (
isLone(node)
isNewExpression(node.argument, {name: 'Error'})
&& isLone(node)
&& node.parent.parent
&& isTypechecking(node.parent.parent)
) {
Expand Down