| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 | 'use strict';const avoidCapture = require('./utils/avoid-capture.js');const {not, notLeftHandSideSelector} = require('./selectors/index.js');const MESSAGE_ID = 'consistentDestructuring';const MESSAGE_ID_SUGGEST = 'consistentDestructuringSuggest';const declaratorSelector = [	'VariableDeclarator',	'[id.type="ObjectPattern"]',	'[init]',	'[init.type!="Literal"]',].join('');const memberSelector = [	'MemberExpression',	'[computed!=true]',	notLeftHandSideSelector(),	not([		'CallExpression > .callee',		'NewExpression> .callee',	]),].join('');const isSimpleExpression = expression => {	while (expression) {		if (expression.computed) {			return false;		}		if (expression.type !== 'MemberExpression') {			break;		}		expression = expression.object;	}	return expression.type === 'Identifier'		|| expression.type === 'ThisExpression';};const isChildInParentScope = (child, parent) => {	while (child) {		if (child === parent) {			return true;		}		child = child.upper;	}	return false;};/** @param {import('eslint').Rule.RuleContext} context */const create = context => {	const source = context.getSourceCode();	const declarations = new Map();	return {		[declaratorSelector](node) {			// Ignore any complex expressions (e.g. arrays, functions)			if (!isSimpleExpression(node.init)) {				return;			}			declarations.set(source.getText(node.init), {				scope: context.getScope(),				variables: context.getDeclaredVariables(node),				objectPattern: node.id,			});		},		[memberSelector](node) {			const declaration = declarations.get(source.getText(node.object));			if (!declaration) {				return;			}			const {scope, objectPattern} = declaration;			const memberScope = context.getScope();			// Property is destructured outside the current scope			if (!isChildInParentScope(memberScope, scope)) {				return;			}			const destructurings = objectPattern.properties.filter(property =>				property.type === 'Property'				&& property.key.type === 'Identifier'				&& property.value.type === 'Identifier',			);			const lastProperty = objectPattern.properties[objectPattern.properties.length - 1];			const hasRest = lastProperty && lastProperty.type === 'RestElement';			const expression = source.getText(node);			const member = source.getText(node.property);			// Member might already be destructured			const destructuredMember = destructurings.find(property =>				property.key.name === member,			);			if (!destructuredMember) {				// Don't destructure additional members when rest is used				if (hasRest) {					return;				}				// Destructured member collides with an existing identifier				if (avoidCapture(member, [memberScope]) !== member) {					return;				}			}			// Don't try to fix nested member expressions			if (node.parent.type === 'MemberExpression') {				return {					node,					messageId: MESSAGE_ID,				};			}			const newMember = destructuredMember ? destructuredMember.value.name : member;			return {				node,				messageId: MESSAGE_ID,				suggest: [{					messageId: MESSAGE_ID_SUGGEST,					data: {						expression,						property: newMember,					},					* fix(fixer) {						const {properties} = objectPattern;						const lastProperty = properties[properties.length - 1];						yield fixer.replaceText(node, newMember);						if (!destructuredMember) {							yield lastProperty								? fixer.insertTextAfter(lastProperty, `, ${newMember}`)								: fixer.replaceText(objectPattern, `{${newMember}}`);						}					},				}],			};		},	};};/** @type {import('eslint').Rule.RuleModule} */module.exports = {	create,	meta: {		type: 'suggestion',		docs: {			description: 'Use destructured variables over properties.',		},		fixable: 'code',		hasSuggestions: true,		messages: {			[MESSAGE_ID]: 'Use destructured variables over properties.',			[MESSAGE_ID_SUGGEST]: 'Replace `{{expression}}` with destructured property `{{property}}`.',		},	},};
 |