| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 | 'use strict';const getScopes = require('./utils/get-scopes.js');const MESSAGE_ID = 'no-unused-properties';const messages = {	[MESSAGE_ID]: 'Property `{{name}}` is defined but never used.',};const getDeclaratorOrPropertyValue = declaratorOrProperty =>	declaratorOrProperty.init	|| declaratorOrProperty.value;const isMemberExpressionCall = memberExpression =>	memberExpression.parent.type === 'CallExpression'	&& memberExpression.parent.callee === memberExpression;const isMemberExpressionAssignment = memberExpression =>	memberExpression.parent.type === 'AssignmentExpression';const isMemberExpressionComputedBeyondPrediction = memberExpression =>	memberExpression.computed	&& memberExpression.property.type !== 'Literal';const specialProtoPropertyKey = {	type: 'Identifier',	name: '__proto__',};const propertyKeysEqual = (keyA, keyB) => {	if (keyA.type === 'Identifier') {		if (keyB.type === 'Identifier') {			return keyA.name === keyB.name;		}		if (keyB.type === 'Literal') {			return keyA.name === keyB.value;		}	}	if (keyA.type === 'Literal') {		if (keyB.type === 'Identifier') {			return keyA.value === keyB.name;		}		if (keyB.type === 'Literal') {			return keyA.value === keyB.value;		}	}	return false;};const objectPatternMatchesObjectExprPropertyKey = (pattern, key) =>	pattern.properties.some(property => {		if (property.type === 'RestElement') {			return true;		}		return propertyKeysEqual(property.key, key);	});const isLeafDeclaratorOrProperty = declaratorOrProperty => {	const value = getDeclaratorOrPropertyValue(declaratorOrProperty);	if (!value) {		return true;	}	if (value.type !== 'ObjectExpression') {		return true;	}	return false;};const isUnusedVariable = variable => {	const hasReadReference = variable.references.some(reference => reference.isRead());	return !hasReadReference;};/** @param {import('eslint').Rule.RuleContext} context */const create = context => {	const getPropertyDisplayName = property => {		if (property.key.type === 'Identifier') {			return property.key.name;		}		if (property.key.type === 'Literal') {			return property.key.value;		}		return context.getSourceCode().getText(property.key);	};	const checkProperty = (property, references, path) => {		if (references.length === 0) {			context.report({				node: property,				messageId: MESSAGE_ID,				data: {					name: getPropertyDisplayName(property),				},			});			return;		}		checkObject(property, references, path);	};	const checkProperties = (objectExpression, references, path = []) => {		for (const property of objectExpression.properties) {			const {key} = property;			if (!key) {				continue;			}			if (propertyKeysEqual(key, specialProtoPropertyKey)) {				continue;			}			const nextPath = [...path, key];			const nextReferences = references				.map(reference => {					const {parent} = reference.identifier;					if (reference.init) {						if (							parent.type === 'VariableDeclarator'							&& parent.parent.type === 'VariableDeclaration'							&& parent.parent.parent.type === 'ExportNamedDeclaration'						) {							return {identifier: parent};						}						return;					}					if (parent.type === 'MemberExpression') {						if (							isMemberExpressionAssignment(parent)							|| isMemberExpressionCall(parent)							|| isMemberExpressionComputedBeyondPrediction(parent)							|| propertyKeysEqual(parent.property, key)						) {							return {identifier: parent};						}						return;					}					if (						parent.type === 'VariableDeclarator'						&& parent.id.type === 'ObjectPattern'					) {						if (objectPatternMatchesObjectExprPropertyKey(parent.id, key)) {							return {identifier: parent};						}						return;					}					if (						parent.type === 'AssignmentExpression'						&& parent.left.type === 'ObjectPattern'					) {						if (objectPatternMatchesObjectExprPropertyKey(parent.left, key)) {							return {identifier: parent};						}						return;					}					return reference;				})				.filter(Boolean);			checkProperty(property, nextReferences, nextPath);		}	};	const checkObject = (declaratorOrProperty, references, path) => {		if (isLeafDeclaratorOrProperty(declaratorOrProperty)) {			return;		}		const value = getDeclaratorOrPropertyValue(declaratorOrProperty);		checkProperties(value, references, path);	};	const checkVariable = variable => {		if (variable.defs.length !== 1) {			return;		}		if (isUnusedVariable(variable)) {			return;		}		const [definition] = variable.defs;		checkObject(definition.node, variable.references);	};	const checkVariables = scope => {		for (const variable of scope.variables) {			checkVariable(variable);		}	};	return {		'Program:exit'() {			const scopes = getScopes(context.getScope());			for (const scope of scopes) {				if (scope.type === 'global') {					continue;				}				checkVariables(scope);			}		},	};};/** @type {import('eslint').Rule.RuleModule} */module.exports = {	create,	meta: {		type: 'suggestion',		docs: {			description: 'Disallow unused object properties.',		},		messages,	},};
 |