UNPKG

eslint-plugin-unicorn

Version:
206 lines (173 loc) 4.33 kB
import {getStaticValue} from '@eslint-community/eslint-utils'; import { getStaticValueIfNoSideEffects, hasSideEffectfulConstInitializer, hasPotentiallyMutableMemberAccess, isGlobalIdentifier, isTypeScriptExpressionWrapper, } from './utils/index.js'; const MESSAGE_ID_COMPUTED_KEY = 'computed-key'; const MESSAGE_ID_NESTED_ARRAY = 'nested-array'; const MESSAGE_ID_DEEP_OBJECT = 'deep-object'; const MESSAGE_ID_PROPERTY_ASSIGNMENT = 'property-assignment'; const messages = { [MESSAGE_ID_COMPUTED_KEY]: 'Do not use computed keys in object destructuring.', [MESSAGE_ID_NESTED_ARRAY]: 'Do not use array destructuring inside object destructuring.', [MESSAGE_ID_DEEP_OBJECT]: 'Do not use object destructuring deeper than two levels.', [MESSAGE_ID_PROPERTY_ASSIGNMENT]: 'Do not assign destructured values to object properties.', }; const staticGlobalProperties = new Map([ ['Math', new Set(['E', 'LN2', 'LN10', 'LOG2E', 'LOG10E', 'PI', 'SQRT1_2', 'SQRT2'])], ['String', new Set(['raw'])], ]); const isKnownStaticGlobalExpression = (node, context) => { const property = node.type === 'TaggedTemplateExpression' ? node.tag : node; if ( property.type !== 'MemberExpression' || property.computed || property.object.type !== 'Identifier' || !isGlobalIdentifier(property.object, context) || property.property.type !== 'Identifier' ) { return false; } return staticGlobalProperties.get(property.object.name)?.has(property.property.name) ?? false; }; function getParentPattern(node) { const {parent} = node; if (!parent) { return; } if ( isTypeScriptExpressionWrapper(parent) && parent.expression === node ) { return getParentPattern(parent); } if ( parent.type === 'AssignmentPattern' && parent.left === node ) { return getParentPattern(parent); } if ( parent.type === 'RestElement' && parent.argument === node ) { return getParentPattern(parent); } if ( parent.type === 'Property' && parent.value === node && parent.parent.type === 'ObjectPattern' ) { return parent.parent; } if ( parent.type === 'ObjectPattern' || parent.type === 'ArrayPattern' ) { return parent; } } function getObjectPatternDepth(node) { let depth = 0; while (node) { if (node.type === 'ObjectPattern') { depth++; } node = getParentPattern(node); } return depth; } function isStaticComputedKey(node, context) { if (hasSideEffectfulConstInitializer(node, context)) { return false; } if ( node.type === 'MemberExpression' && !isGlobalIdentifier(node.object, context) && hasPotentiallyMutableMemberAccess(node, context) ) { return false; } if (getStaticValueIfNoSideEffects(node, context) !== undefined) { return true; } if (!isKnownStaticGlobalExpression(node, context)) { return false; } return getStaticValue(node, context.sourceCode.getScope(node)) !== null; } /** @param {import('eslint').Rule.RuleContext} context */ const create = context => { context.on('Property', node => { if ( node.parent.type !== 'ObjectPattern' || !node.computed ) { return; } // A computed key is the only way to exclude a dynamic property from a rest element, // so allow it when the same object pattern collects a rest. A static key is not // dynamic, so it stays disallowed. if ( !isStaticComputedKey(node.key, context) && node.parent.properties.some(property => property.type === 'RestElement') ) { return; } return { node, messageId: MESSAGE_ID_COMPUTED_KEY, }; }); context.on('ArrayPattern', node => { if (getObjectPatternDepth(node) === 0) { return; } return { node, messageId: MESSAGE_ID_NESTED_ARRAY, }; }); context.on('ObjectPattern', node => { if (getObjectPatternDepth(node) !== 3) { return; } return { node, messageId: MESSAGE_ID_DEEP_OBJECT, }; }); context.on('MemberExpression', node => { if (getParentPattern(node)?.type !== 'ObjectPattern') { return; } return { node, messageId: MESSAGE_ID_PROPERTY_ASSIGNMENT, }; }); }; /** @type {import('eslint').Rule.RuleModule} */ const config = { create, meta: { type: 'suggestion', docs: { description: 'Disallow unreadable object destructuring.', recommended: 'unopinionated', }, schema: [], messages, languages: [ 'js/js', ], }, }; export default config;