routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+232
@@ -0,0 +1,232 @@
|
||||
import getScopes from './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 getPropertyKeyName = key => {
|
||||
if (key.type === 'Identifier' || key.type === 'JSXIdentifier') {
|
||||
return key.name;
|
||||
}
|
||||
|
||||
if (key.type === 'Literal') {
|
||||
return key.value;
|
||||
}
|
||||
};
|
||||
|
||||
const propertyKeysEqual = (keyA, keyB) => {
|
||||
const keyNameA = getPropertyKeyName(keyA);
|
||||
return keyNameA !== undefined && keyNameA === getPropertyKeyName(keyB);
|
||||
};
|
||||
|
||||
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 {sourceCode} = context;
|
||||
const getPropertyDisplayName = property => {
|
||||
if (property.key.type === 'Identifier') {
|
||||
return property.key.name;
|
||||
}
|
||||
|
||||
if (property.key.type === 'Literal') {
|
||||
return property.key.value;
|
||||
}
|
||||
|
||||
return sourceCode.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'
|
||||
|| parent.type === 'JSXMemberExpression'
|
||||
) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
context.on('Program:exit', program => {
|
||||
const scopes = getScopes(sourceCode.getScope(program));
|
||||
for (const scope of scopes) {
|
||||
if (scope.type === 'global') {
|
||||
continue;
|
||||
}
|
||||
|
||||
checkVariables(scope);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
const config = {
|
||||
create,
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Disallow unused object properties.',
|
||||
recommended: false,
|
||||
},
|
||||
messages,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user