Spaces:
Sleeping
Sleeping
| /** | |
| * @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 | |
| */ | |
| ; | |
| //-------------------------------------------------------------------------- | |
| // 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 {number} 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); | |
| } | |
| }, | |
| }; | |
| }, | |
| }; | |