UNPKG

eslint-plugin-unicorn

Version:
127 lines (105 loc) 2.83 kB
import {findVariable} from '@eslint-community/eslint-utils'; import {fixSpaceAroundKeyword} from './fix/index.js'; import {isNewExpression, isMemberExpression, isMethodCall} from './ast/index.js'; const MESSAGE_ID = 'prefer-set-size'; const messages = { [MESSAGE_ID]: 'Prefer using `Set#size` instead of `Array#length`.', }; const isNewSet = node => isNewExpression(node, {name: 'Set'}); function isSet(node, scope) { if (isNewSet(node)) { return true; } if (node.type !== 'Identifier') { return false; } const variable = findVariable(scope, node); if (!variable || variable.defs.length !== 1) { return false; } const [definition] = variable.defs; if (definition.type !== 'Variable' || definition.kind !== 'const') { return false; } const declarator = definition.node; return declarator.type === 'VariableDeclarator' && declarator.id.type === 'Identifier' && isNewSet(definition.node.init); } function getSetNode(memberExpressionObject) { // `[...set].length` if ( memberExpressionObject.type === 'ArrayExpression' && memberExpressionObject.elements.length === 1 && memberExpressionObject.elements[0]?.type === 'SpreadElement' ) { return memberExpressionObject.elements[0].argument; } // `Array.from(set).length` if ( isMethodCall(memberExpressionObject, { object: 'Array', method: 'from', argumentsLength: 1, optionalCall: false, optionalMember: false, }) ) { return memberExpressionObject.arguments[0]; } } function createFix(context, lengthAccessNode, set) { const {sourceCode} = context; const { object: array, property, } = lengthAccessNode; if (sourceCode.getCommentsInside(array).length > sourceCode.getCommentsInside(set).length) { return; } /** @param {import('eslint').Rule.RuleFixer} fixer */ return function * (fixer) { yield fixer.replaceText(property, 'size'); yield fixer.replaceText(array, sourceCode.getText(set)); if (array.type === 'ArrayExpression') { yield fixSpaceAroundKeyword(fixer, lengthAccessNode, context); } }; } /** @param {import('eslint').Rule.RuleContext} context */ const create = context => { const {sourceCode} = context; context.on('MemberExpression', node => { if ( !isMemberExpression(node, { property: 'length', optional: false, }) ) { return; } const set = getSetNode(node.object); if (!set || !isSet(set, sourceCode.getScope(set))) { return; } return { node: node.property, messageId: MESSAGE_ID, fix: createFix(context, node, set), }; }); }; /** @type {import('eslint').Rule.RuleModule} */ const config = { create, meta: { type: 'suggestion', docs: { description: 'Prefer using `Set#size` instead of `Array#length`.', recommended: 'unopinionated', }, fixable: 'code', messages, }, }; export default config;