Spaces:
Sleeping
Sleeping
| /** | |
| * @fileoverview Rule to warn when a function expression does not have a name. | |
| * @author Kyle T. Nunery | |
| */ | |
| ; | |
| //------------------------------------------------------------------------------ | |
| // Requirements | |
| //------------------------------------------------------------------------------ | |
| const astUtils = require("./utils/ast-utils"); | |
| //------------------------------------------------------------------------------ | |
| // Types | |
| //------------------------------------------------------------------------------ | |
| /** @typedef {import("eslint-scope").Variable} Variable */ | |
| //------------------------------------------------------------------------------ | |
| // Helpers | |
| //------------------------------------------------------------------------------ | |
| /** | |
| * Checks whether or not a given variable is a function name. | |
| * @param {Variable} variable A variable to check. | |
| * @returns {boolean} `true` if the variable is a function name. | |
| */ | |
| function isFunctionName(variable) { | |
| return variable && variable.defs[0].type === "FunctionName"; | |
| } | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
| /** @type {import('../types').Rule.RuleModule} */ | |
| module.exports = { | |
| meta: { | |
| type: "suggestion", | |
| defaultOptions: ["always", {}], | |
| docs: { | |
| description: "Require or disallow named `function` expressions", | |
| recommended: false, | |
| url: "https://eslint.org/docs/latest/rules/func-names", | |
| }, | |
| schema: { | |
| definitions: { | |
| value: { | |
| enum: ["always", "as-needed", "never"], | |
| }, | |
| }, | |
| type: "array", | |
| items: [ | |
| { | |
| $ref: "#/definitions/value", | |
| }, | |
| { | |
| type: "object", | |
| properties: { | |
| generators: { | |
| $ref: "#/definitions/value", | |
| }, | |
| }, | |
| additionalProperties: false, | |
| }, | |
| ], | |
| additionalItems: false, | |
| }, | |
| messages: { | |
| unnamed: "Unexpected unnamed {{name}}.", | |
| named: "Unexpected named {{name}}.", | |
| }, | |
| }, | |
| create(context) { | |
| const sourceCode = context.sourceCode; | |
| /** | |
| * Returns the config option for the given node. | |
| * @param {ASTNode} node A node to get the config for. | |
| * @returns {string} The config option. | |
| */ | |
| function getConfigForNode(node) { | |
| if (node.generator && context.options[1].generators) { | |
| return context.options[1].generators; | |
| } | |
| return context.options[0]; | |
| } | |
| /** | |
| * Determines whether the current FunctionExpression node is a get, set, or | |
| * shorthand method in an object literal or a class. | |
| * @param {ASTNode} node A node to check. | |
| * @returns {boolean} True if the node is a get, set, or shorthand method. | |
| */ | |
| function isObjectOrClassMethod(node) { | |
| const parent = node.parent; | |
| return ( | |
| parent.type === "MethodDefinition" || | |
| (parent.type === "Property" && | |
| (parent.method || | |
| parent.kind === "get" || | |
| parent.kind === "set")) | |
| ); | |
| } | |
| /** | |
| * Determines whether the current FunctionExpression node has a name that would be | |
| * inferred from context in a conforming ES6 environment. | |
| * @param {ASTNode} node A node to check. | |
| * @returns {boolean} True if the node would have a name assigned automatically. | |
| */ | |
| function hasInferredName(node) { | |
| const parent = node.parent; | |
| return ( | |
| isObjectOrClassMethod(node) || | |
| (parent.type === "VariableDeclarator" && | |
| parent.id.type === "Identifier" && | |
| parent.init === node) || | |
| (parent.type === "Property" && parent.value === node) || | |
| (parent.type === "PropertyDefinition" && | |
| parent.value === node) || | |
| (parent.type === "AssignmentExpression" && | |
| parent.left.type === "Identifier" && | |
| parent.right === node) || | |
| (parent.type === "AssignmentPattern" && | |
| parent.left.type === "Identifier" && | |
| parent.right === node) | |
| ); | |
| } | |
| /** | |
| * Reports that an unnamed function should be named | |
| * @param {ASTNode} node The node to report in the event of an error. | |
| * @returns {void} | |
| */ | |
| function reportUnexpectedUnnamedFunction(node) { | |
| context.report({ | |
| node, | |
| messageId: "unnamed", | |
| loc: astUtils.getFunctionHeadLoc(node, sourceCode), | |
| data: { name: astUtils.getFunctionNameWithKind(node) }, | |
| }); | |
| } | |
| /** | |
| * Reports that a named function should be unnamed | |
| * @param {ASTNode} node The node to report in the event of an error. | |
| * @returns {void} | |
| */ | |
| function reportUnexpectedNamedFunction(node) { | |
| context.report({ | |
| node, | |
| messageId: "named", | |
| loc: astUtils.getFunctionHeadLoc(node, sourceCode), | |
| data: { name: astUtils.getFunctionNameWithKind(node) }, | |
| }); | |
| } | |
| /** | |
| * The listener for function nodes. | |
| * @param {ASTNode} node function node | |
| * @returns {void} | |
| */ | |
| function handleFunction(node) { | |
| // Skip recursive functions. | |
| const nameVar = sourceCode.getDeclaredVariables(node)[0]; | |
| if (isFunctionName(nameVar) && nameVar.references.length > 0) { | |
| return; | |
| } | |
| const hasName = Boolean(node.id && node.id.name); | |
| const config = getConfigForNode(node); | |
| if (config === "never") { | |
| if (hasName && node.type !== "FunctionDeclaration") { | |
| reportUnexpectedNamedFunction(node); | |
| } | |
| } else if (config === "as-needed") { | |
| if (!hasName && !hasInferredName(node)) { | |
| reportUnexpectedUnnamedFunction(node); | |
| } | |
| } else { | |
| if (!hasName && !isObjectOrClassMethod(node)) { | |
| reportUnexpectedUnnamedFunction(node); | |
| } | |
| } | |
| } | |
| return { | |
| "FunctionExpression:exit": handleFunction, | |
| "ExportDefaultDeclaration > FunctionDeclaration": handleFunction, | |
| }; | |
| }, | |
| }; | |