2025-04-19 23:12:19 -04:00

146 lines
4.2 KiB
JavaScript

/**
* @fileoverview Rule to suggest using "Reflect" api over Function/Object methods
* @author Keith Cirkel <http://keithcirkel.co.uk>
* @deprecated in ESLint v3.9.0
*/
'use strict';
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'Require `Reflect` methods where applicable',
recommended: false,
url: 'https://eslint.org/docs/latest/rules/prefer-reflect',
},
deprecated: {
message: 'The original intention of this rule was misguided.',
url: 'https://eslint.org/docs/latest/rules/prefer-reflect',
deprecatedSince: '3.9.0',
availableUntil: null,
replacedBy: [],
},
schema: [
{
type: 'object',
properties: {
exceptions: {
type: 'array',
items: {
enum: [
'apply',
'call',
'delete',
'defineProperty',
'getOwnPropertyDescriptor',
'getPrototypeOf',
'setPrototypeOf',
'isExtensible',
'getOwnPropertyNames',
'preventExtensions',
],
},
uniqueItems: true,
},
},
additionalProperties: false,
},
],
messages: {
preferReflect: 'Avoid using {{existing}}, instead use {{substitute}}.',
},
},
create(context) {
const existingNames = {
apply: 'Function.prototype.apply',
call: 'Function.prototype.call',
defineProperty: 'Object.defineProperty',
getOwnPropertyDescriptor: 'Object.getOwnPropertyDescriptor',
getPrototypeOf: 'Object.getPrototypeOf',
setPrototypeOf: 'Object.setPrototypeOf',
isExtensible: 'Object.isExtensible',
getOwnPropertyNames: 'Object.getOwnPropertyNames',
preventExtensions: 'Object.preventExtensions',
};
const reflectSubstitutes = {
apply: 'Reflect.apply',
call: 'Reflect.apply',
defineProperty: 'Reflect.defineProperty',
getOwnPropertyDescriptor: 'Reflect.getOwnPropertyDescriptor',
getPrototypeOf: 'Reflect.getPrototypeOf',
setPrototypeOf: 'Reflect.setPrototypeOf',
isExtensible: 'Reflect.isExtensible',
getOwnPropertyNames: 'Reflect.getOwnPropertyNames',
preventExtensions: 'Reflect.preventExtensions',
};
const exceptions = (context.options[0] || {}).exceptions || [];
/**
* Reports the Reflect violation based on the `existing` and `substitute`
* @param {Object} node The node that violates the rule.
* @param {string} existing The existing method name that has been used.
* @param {string} substitute The Reflect substitute that should be used.
* @returns {void}
*/
function report(node, existing, substitute) {
context.report({
node,
messageId: 'preferReflect',
data: {
existing,
substitute,
},
});
}
return {
CallExpression(node) {
const methodName = (node.callee.property || {}).name;
const isReflectCall = (node.callee.object || {}).name === 'Reflect';
const hasReflectSubstitute = Object.hasOwn(
reflectSubstitutes,
methodName
);
const userConfiguredException = exceptions.includes(methodName);
if (
hasReflectSubstitute &&
!isReflectCall &&
!userConfiguredException
) {
report(
node,
existingNames[methodName],
reflectSubstitutes[methodName]
);
}
},
UnaryExpression(node) {
const isDeleteOperator = node.operator === 'delete';
const targetsIdentifier = node.argument.type === 'Identifier';
const userConfiguredException = exceptions.includes('delete');
if (
isDeleteOperator &&
!targetsIdentifier &&
!userConfiguredException
) {
report(node, 'the delete keyword', 'Reflect.deleteProperty');
}
},
};
},
};