Skip to content

Commit

Permalink
Replace callOrNewExpressionSelector with isCallOrNewExpression (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed May 16, 2023
1 parent 3af88c1 commit fe8fffb
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 80 deletions.
15 changes: 11 additions & 4 deletions rules/ast/call-or-new-expression.js
Expand Up @@ -13,8 +13,8 @@
} | string | string[]
} CallOrNewExpressionCheckOptions
*/
function create(node, options, type) {
if (node?.type !== type) {
function create(node, options, types) {
if (!types.includes(node?.type)) {
return false;
}

Expand Down Expand Up @@ -100,7 +100,7 @@ function create(node, options, type) {
@param {CallOrNewExpressionCheckOptions} [options]
@returns {boolean}
*/
const isCallExpression = (node, options) => create(node, options, 'CallExpression');
const isCallExpression = (node, options) => create(node, options, ['CallExpression']);

/**
@param {CallOrNewExpressionCheckOptions} [options]
Expand All @@ -111,10 +111,17 @@ const isNewExpression = (node, options) => {
throw new TypeError('Cannot check node.optional in `isNewExpression`.');
}

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

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

module.exports = {
isCallExpression,
isNewExpression,
isCallOrNewExpression,
};
14 changes: 10 additions & 4 deletions rules/ast/index.js
Expand Up @@ -8,6 +8,11 @@ const {
isNullLiteral,
isRegexLiteral,
} = require('./literal.js');
const {
isNewExpression,
isCallExpression,
isCallOrNewExpression,
} = require('./call-or-new-expression.js');

module.exports = {
isLiteral,
Expand All @@ -18,11 +23,12 @@ module.exports = {
isRegexLiteral,

isArrowFunctionBody: require('./is-arrow-function-body.js'),
isCallExpression,
isCallOrNewExpression,
isEmptyNode: require('./is-empty-node.js'),
isStaticRequire: require('./is-static-require.js'),
isUndefined: require('./is-undefined.js'),
isNewExpression: require('./call-or-new-expression.js').isNewExpression,
isCallExpression: require('./call-or-new-expression.js').isCallExpression,
isMemberExpression: require('./is-member-expression.js'),
isMethodCall: require('./is-method-call.js'),
isNewExpression,
isStaticRequire: require('./is-static-require.js'),
isUndefined: require('./is-undefined.js'),
};
15 changes: 11 additions & 4 deletions rules/error-message.js
@@ -1,7 +1,7 @@
'use strict';
const {getStaticValue} = require('@eslint-community/eslint-utils');
const isShadowed = require('./utils/is-shadowed.js');
const {callOrNewExpressionSelector} = require('./selectors/index.js');
const {isCallOrNewExpression} = require('./ast/index.js');

const MESSAGE_ID_MISSING_MESSAGE = 'missing-message';
const MESSAGE_ID_EMPTY_MESSAGE = 'message-is-empty-string';
Expand All @@ -12,7 +12,7 @@ const messages = {
[MESSAGE_ID_NOT_STRING]: 'Error message should be a string.',
};

const selector = callOrNewExpressionSelector([
const builtinErrors = [
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
'Error',
'EvalError',
Expand All @@ -23,11 +23,18 @@ const selector = callOrNewExpressionSelector([
'URIError',
'InternalError',
'AggregateError',
]);
];

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](expression) {
'CallExpression,NewExpression'(expression) {
if (!isCallOrNewExpression(expression, {
names: builtinErrors,
optional: false,
})) {
return;
}

const scope = context.sourceCode.getScope(expression);
if (isShadowed(scope, expression.callee)) {
return;
Expand Down
119 changes: 59 additions & 60 deletions rules/prefer-set-has.js
@@ -1,12 +1,7 @@
'use strict';
const {findVariable} = require('@eslint-community/eslint-utils');
const getVariableIdentifiers = require('./utils/get-variable-identifiers.js');
const {
matches,
not,
methodCallSelector,
callOrNewExpressionSelector,
} = require('./selectors/index.js');
const {getVariableIdentifiers} = require('./utils/index.js');
const {isCallOrNewExpression, isMethodCall} = require('./ast/index.js');

const MESSAGE_ID_ERROR = 'error';
const MESSAGE_ID_SUGGESTION = 'suggestion';
Expand All @@ -15,58 +10,23 @@ const messages = {
[MESSAGE_ID_SUGGESTION]: 'Switch `{{name}}` to `Set`.',
};

// `[]`
const arrayExpressionSelector = [
'[init.type="ArrayExpression"]',
].join('');

// `Array()` and `new Array()`
const newArraySelector = callOrNewExpressionSelector({name: 'Array', path: 'init'});

// `Array.from()` and `Array.of()`
const arrayStaticMethodSelector = methodCallSelector({
object: 'Array',
methods: ['from', 'of'],
path: 'init',
});

// Array methods that return an array
const arrayMethodSelector = methodCallSelector({
methods: [
'concat',
'copyWithin',
'fill',
'filter',
'flat',
'flatMap',
'map',
'reverse',
'slice',
'sort',
'splice',
'toReversed',
'toSorted',
'toSpliced',
'with',
],
path: 'init',
});

const selector = [
'VariableDeclaration',
// Exclude `export const foo = [];`
not('ExportNamedDeclaration > .declaration'),
' > ',
'VariableDeclarator.declarations',
matches([
arrayExpressionSelector,
newArraySelector,
arrayStaticMethodSelector,
arrayMethodSelector,
]),
' > ',
'Identifier.id',
].join('');
const arrayMethodsReturnsArray = [
'concat',
'copyWithin',
'fill',
'filter',
'flat',
'flatMap',
'map',
'reverse',
'slice',
'sort',
'splice',
'toReversed',
'toSorted',
'toSpliced',
'with',
];

const isIncludesCall = node => {
const {type, optional, callee, arguments: includesArguments} = node.parent.parent ?? {};
Expand Down Expand Up @@ -114,7 +74,46 @@ const isMultipleCall = (identifier, node) => {

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](node) {
Identifier(node) {
const {parent} = node;

if (!(
parent.type === 'VariableDeclarator'
&& parent.id === node
&& Boolean(parent.init)
&& parent.parent.type === 'VariableDeclaration'
&& parent.parent.declarations.includes(parent)
// Exclude `export const foo = [];`
&& !(
parent.parent.parent.type === 'ExportNamedDeclaration'
&& parent.parent.parent.declaration === parent.parent
)
&& (
// `[]`
parent.init.type === 'ArrayExpression'
// `Array()` and `new Array()`
|| isCallOrNewExpression(parent.init, {
name: 'Array',
optional: false,
})
// `Array.from()` and `Array.of()`
|| isMethodCall(parent.init, {
object: 'Array',
methods: ['from', 'of'],
optionalCall: false,
optionalMember: false,
})
// Array methods that return an array
|| isMethodCall(parent.init, {
methods: arrayMethodsReturnsArray,
optionalCall: false,
optionalMember: false,
})
)
)) {
return;
}

const variable = findVariable(context.sourceCode.getScope(node), node);

// This was reported https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1075#issuecomment-768073342
Expand Down
7 changes: 0 additions & 7 deletions rules/selectors/call-or-new-expression-selector.js
Expand Up @@ -100,14 +100,7 @@ const callExpressionSelector = options => create(options, ['CallExpression']);
*/
const newExpressionSelector = options => create(options, ['NewExpression']);

/**
@param {CallOrNewExpressionOptions} [options]
@returns {string}
*/
const callOrNewExpressionSelector = options => create(options, ['CallExpression', 'NewExpression']);

module.exports = {
newExpressionSelector,
callExpressionSelector,
callOrNewExpressionSelector,
};
1 change: 0 additions & 1 deletion rules/selectors/index.js
Expand Up @@ -13,5 +13,4 @@ module.exports = {
referenceIdentifierSelector: require('./reference-identifier-selector.js'),
callExpressionSelector: require('./call-or-new-expression-selector.js').callExpressionSelector,
newExpressionSelector: require('./call-or-new-expression-selector.js').newExpressionSelector,
callOrNewExpressionSelector: require('./call-or-new-expression-selector.js').callOrNewExpressionSelector,
};
2 changes: 2 additions & 0 deletions rules/utils/index.js
Expand Up @@ -18,6 +18,7 @@ module.exports = {
getParenthesizedRange,
getParenthesizedText,
getParenthesizedTimes,
getVariableIdentifiers: require('./get-variable-identifiers.js'),
isArrayPrototypeProperty,
isNodeMatches,
isNodeMatchesNameOrPath,
Expand All @@ -29,3 +30,4 @@ module.exports = {
needsSemicolon: require('./needs-semicolon.js'),
shouldAddParenthesesToMemberExpressionObject: require('./should-add-parentheses-to-member-expression-object.js'),
};

0 comments on commit fe8fffb

Please sign in to comment.