From fe8fffb1462a939c91e656beb2e51879cb91d324 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 16 May 2023 12:57:55 +0800 Subject: [PATCH] Replace `callOrNewExpressionSelector` with `isCallOrNewExpression` (#2105) --- rules/ast/call-or-new-expression.js | 15 ++- rules/ast/index.js | 14 ++- rules/error-message.js | 15 ++- rules/prefer-set-has.js | 119 +++++++++--------- .../call-or-new-expression-selector.js | 7 -- rules/selectors/index.js | 1 - rules/utils/index.js | 2 + 7 files changed, 93 insertions(+), 80 deletions(-) diff --git a/rules/ast/call-or-new-expression.js b/rules/ast/call-or-new-expression.js index 90d0f5aea4..c8b7b45329 100644 --- a/rules/ast/call-or-new-expression.js +++ b/rules/ast/call-or-new-expression.js @@ -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; } @@ -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] @@ -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, }; diff --git a/rules/ast/index.js b/rules/ast/index.js index 3ad5f9c186..e4f4218247 100644 --- a/rules/ast/index.js +++ b/rules/ast/index.js @@ -8,6 +8,11 @@ const { isNullLiteral, isRegexLiteral, } = require('./literal.js'); +const { + isNewExpression, + isCallExpression, + isCallOrNewExpression, +} = require('./call-or-new-expression.js'); module.exports = { isLiteral, @@ -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'), }; diff --git a/rules/error-message.js b/rules/error-message.js index 91f5ad4694..37e2a0682e 100644 --- a/rules/error-message.js +++ b/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'; @@ -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', @@ -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; diff --git a/rules/prefer-set-has.js b/rules/prefer-set-has.js index b726a690b5..b1113cb8b1 100644 --- a/rules/prefer-set-has.js +++ b/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'; @@ -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 ?? {}; @@ -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 diff --git a/rules/selectors/call-or-new-expression-selector.js b/rules/selectors/call-or-new-expression-selector.js index 22fce86887..1a43b98bb6 100644 --- a/rules/selectors/call-or-new-expression-selector.js +++ b/rules/selectors/call-or-new-expression-selector.js @@ -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, }; diff --git a/rules/selectors/index.js b/rules/selectors/index.js index 90a1590425..1792dcc5fc 100644 --- a/rules/selectors/index.js +++ b/rules/selectors/index.js @@ -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, }; diff --git a/rules/utils/index.js b/rules/utils/index.js index 787271fcb6..4a781633f7 100644 --- a/rules/utils/index.js +++ b/rules/utils/index.js @@ -18,6 +18,7 @@ module.exports = { getParenthesizedRange, getParenthesizedText, getParenthesizedTimes, + getVariableIdentifiers: require('./get-variable-identifiers.js'), isArrayPrototypeProperty, isNodeMatches, isNodeMatchesNameOrPath, @@ -29,3 +30,4 @@ module.exports = { needsSemicolon: require('./needs-semicolon.js'), shouldAddParenthesesToMemberExpressionObject: require('./should-add-parentheses-to-member-expression-object.js'), }; +