123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- '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
- && memberExpression.parent.type === 'CallExpression'
- && memberExpression.parent.callee === memberExpression;
- const isMemberExpressionAssignment = memberExpression =>
- memberExpression.parent
- && 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,
- },
- };
|