securityos/node_modules/eslint-plugin-unicorn/rules/no-unused-properties.js

238 lines
5.1 KiB
JavaScript

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