|
- '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,
- },
- };
|