codtracker-js/node_modules/eslint/lib/rules/func-name-matching.js
2025-04-19 23:12:19 -04:00

317 lines
9.4 KiB
JavaScript

/**
* @fileoverview Rule to require function names to match the name of the variable or property to which they are assigned.
* @author Annie Zhang, Pavel Strashkin
*/
'use strict';
//--------------------------------------------------------------------------
// Requirements
//--------------------------------------------------------------------------
const astUtils = require('./utils/ast-utils');
const esutils = require('esutils');
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
/**
* Determines if a pattern is `module.exports` or `module["exports"]`
* @param {ASTNode} pattern The left side of the AssignmentExpression
* @returns {boolean} True if the pattern is `module.exports` or `module["exports"]`
*/
function isModuleExports(pattern) {
if (
pattern.type === 'MemberExpression' &&
pattern.object.type === 'Identifier' &&
pattern.object.name === 'module'
) {
// module.exports
if (
pattern.property.type === 'Identifier' &&
pattern.property.name === 'exports'
) {
return true;
}
// module["exports"]
if (
pattern.property.type === 'Literal' &&
pattern.property.value === 'exports'
) {
return true;
}
}
return false;
}
/**
* Determines if a string name is a valid identifier
* @param {string} name The string to be checked
* @param {int} ecmaVersion The ECMAScript version if specified in the parserOptions config
* @returns {boolean} True if the string is a valid identifier
*/
function isIdentifier(name, ecmaVersion) {
if (ecmaVersion >= 2015) {
return esutils.keyword.isIdentifierES6(name);
}
return esutils.keyword.isIdentifierES5(name);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
const alwaysOrNever = { enum: ['always', 'never'] };
const optionsObject = {
type: 'object',
properties: {
considerPropertyDescriptor: {
type: 'boolean',
},
includeCommonJSModuleExports: {
type: 'boolean',
},
},
additionalProperties: false,
};
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description:
'Require function names to match the name of the variable or property to which they are assigned',
recommended: false,
frozen: true,
url: 'https://eslint.org/docs/latest/rules/func-name-matching',
},
schema: {
anyOf: [
{
type: 'array',
additionalItems: false,
items: [alwaysOrNever, optionsObject],
},
{
type: 'array',
additionalItems: false,
items: [optionsObject],
},
],
},
messages: {
matchProperty:
'Function name `{{funcName}}` should match property name `{{name}}`.',
matchVariable:
'Function name `{{funcName}}` should match variable name `{{name}}`.',
notMatchProperty:
'Function name `{{funcName}}` should not match property name `{{name}}`.',
notMatchVariable:
'Function name `{{funcName}}` should not match variable name `{{name}}`.',
},
},
create(context) {
const options =
(typeof context.options[0] === 'object' ?
context.options[0]
: context.options[1]) || {};
const nameMatches =
typeof context.options[0] === 'string' ? context.options[0] : 'always';
const considerPropertyDescriptor = options.considerPropertyDescriptor;
const includeModuleExports = options.includeCommonJSModuleExports;
const ecmaVersion = context.languageOptions.ecmaVersion;
/**
* Check whether node is a certain CallExpression.
* @param {string} objName object name
* @param {string} funcName function name
* @param {ASTNode} node The node to check
* @returns {boolean} `true` if node matches CallExpression
*/
function isPropertyCall(objName, funcName, node) {
if (!node) {
return false;
}
return (
node.type === 'CallExpression' &&
astUtils.isSpecificMemberAccess(node.callee, objName, funcName)
);
}
/**
* Compares identifiers based on the nameMatches option
* @param {string} x the first identifier
* @param {string} y the second identifier
* @returns {boolean} whether the two identifiers should warn.
*/
function shouldWarn(x, y) {
return (
(nameMatches === 'always' && x !== y) ||
(nameMatches === 'never' && x === y)
);
}
/**
* Reports
* @param {ASTNode} node The node to report
* @param {string} name The variable or property name
* @param {string} funcName The function name
* @param {boolean} isProp True if the reported node is a property assignment
* @returns {void}
*/
function report(node, name, funcName, isProp) {
let messageId;
if (nameMatches === 'always' && isProp) {
messageId = 'matchProperty';
} else if (nameMatches === 'always') {
messageId = 'matchVariable';
} else if (isProp) {
messageId = 'notMatchProperty';
} else {
messageId = 'notMatchVariable';
}
context.report({
node,
messageId,
data: {
name,
funcName,
},
});
}
/**
* Determines whether a given node is a string literal
* @param {ASTNode} node The node to check
* @returns {boolean} `true` if the node is a string literal
*/
function isStringLiteral(node) {
return node.type === 'Literal' && typeof node.value === 'string';
}
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
return {
VariableDeclarator(node) {
if (
!node.init ||
node.init.type !== 'FunctionExpression' ||
node.id.type !== 'Identifier'
) {
return;
}
if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) {
report(node, node.id.name, node.init.id.name, false);
}
},
AssignmentExpression(node) {
if (
node.right.type !== 'FunctionExpression' ||
(node.left.computed && node.left.property.type !== 'Literal') ||
(!includeModuleExports && isModuleExports(node.left)) ||
(node.left.type !== 'Identifier' &&
node.left.type !== 'MemberExpression')
) {
return;
}
const isProp = node.left.type === 'MemberExpression';
const name =
isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name;
if (
node.right.id &&
name &&
isIdentifier(name) &&
shouldWarn(name, node.right.id.name)
) {
report(node, name, node.right.id.name, isProp);
}
},
'Property, PropertyDefinition[value]'(node) {
if (!(node.value.type === 'FunctionExpression' && node.value.id)) {
return;
}
if (node.key.type === 'Identifier' && !node.computed) {
const functionName = node.value.id.name;
let propertyName = node.key.name;
if (
considerPropertyDescriptor &&
propertyName === 'value' &&
node.parent.type === 'ObjectExpression'
) {
if (
isPropertyCall('Object', 'defineProperty', node.parent.parent) ||
isPropertyCall('Reflect', 'defineProperty', node.parent.parent)
) {
const property = node.parent.parent.arguments[1];
if (
isStringLiteral(property) &&
shouldWarn(property.value, functionName)
) {
report(node, property.value, functionName, true);
}
} else if (
isPropertyCall(
'Object',
'defineProperties',
node.parent.parent.parent.parent
)
) {
propertyName = node.parent.parent.key.name;
if (
!node.parent.parent.computed &&
shouldWarn(propertyName, functionName)
) {
report(node, propertyName, functionName, true);
}
} else if (
isPropertyCall(
'Object',
'create',
node.parent.parent.parent.parent
)
) {
propertyName = node.parent.parent.key.name;
if (
!node.parent.parent.computed &&
shouldWarn(propertyName, functionName)
) {
report(node, propertyName, functionName, true);
}
} else if (shouldWarn(propertyName, functionName)) {
report(node, propertyName, functionName, true);
}
} else if (shouldWarn(propertyName, functionName)) {
report(node, propertyName, functionName, true);
}
return;
}
if (
isStringLiteral(node.key) &&
isIdentifier(node.key.value, ecmaVersion) &&
shouldWarn(node.key.value, node.value.id.name)
) {
report(node, node.key.value, node.value.id.name, true);
}
},
};
},
};