1/** 2 * @fileoverview Rule to warn when a function expression does not have a name. 3 * @author Kyle T. Nunery 4 */ 5 6"use strict"; 7 8//------------------------------------------------------------------------------ 9// Requirements 10//------------------------------------------------------------------------------ 11 12const astUtils = require("./utils/ast-utils"); 13 14/** 15 * Checks whether or not a given variable is a function name. 16 * @param {eslint-scope.Variable} variable A variable to check. 17 * @returns {boolean} `true` if the variable is a function name. 18 */ 19function isFunctionName(variable) { 20 return variable && variable.defs[0].type === "FunctionName"; 21} 22 23//------------------------------------------------------------------------------ 24// Rule Definition 25//------------------------------------------------------------------------------ 26 27module.exports = { 28 meta: { 29 type: "suggestion", 30 31 docs: { 32 description: "require or disallow named `function` expressions", 33 category: "Stylistic Issues", 34 recommended: false, 35 url: "https://eslint.org/docs/rules/func-names" 36 }, 37 38 schema: { 39 definitions: { 40 value: { 41 enum: [ 42 "always", 43 "as-needed", 44 "never" 45 ] 46 } 47 }, 48 items: [ 49 { 50 $ref: "#/definitions/value" 51 }, 52 { 53 type: "object", 54 properties: { 55 generators: { 56 $ref: "#/definitions/value" 57 } 58 }, 59 additionalProperties: false 60 } 61 ] 62 }, 63 64 messages: { 65 unnamed: "Unexpected unnamed {{name}}.", 66 named: "Unexpected named {{name}}." 67 } 68 }, 69 70 create(context) { 71 72 const sourceCode = context.getSourceCode(); 73 74 /** 75 * Returns the config option for the given node. 76 * @param {ASTNode} node A node to get the config for. 77 * @returns {string} The config option. 78 */ 79 function getConfigForNode(node) { 80 if ( 81 node.generator && 82 context.options.length > 1 && 83 context.options[1].generators 84 ) { 85 return context.options[1].generators; 86 } 87 88 return context.options[0] || "always"; 89 } 90 91 /** 92 * Determines whether the current FunctionExpression node is a get, set, or 93 * shorthand method in an object literal or a class. 94 * @param {ASTNode} node A node to check. 95 * @returns {boolean} True if the node is a get, set, or shorthand method. 96 */ 97 function isObjectOrClassMethod(node) { 98 const parent = node.parent; 99 100 return (parent.type === "MethodDefinition" || ( 101 parent.type === "Property" && ( 102 parent.method || 103 parent.kind === "get" || 104 parent.kind === "set" 105 ) 106 )); 107 } 108 109 /** 110 * Determines whether the current FunctionExpression node has a name that would be 111 * inferred from context in a conforming ES6 environment. 112 * @param {ASTNode} node A node to check. 113 * @returns {boolean} True if the node would have a name assigned automatically. 114 */ 115 function hasInferredName(node) { 116 const parent = node.parent; 117 118 return isObjectOrClassMethod(node) || 119 (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) || 120 (parent.type === "Property" && parent.value === node) || 121 (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) || 122 (parent.type === "AssignmentPattern" && parent.left.type === "Identifier" && parent.right === node); 123 } 124 125 /** 126 * Reports that an unnamed function should be named 127 * @param {ASTNode} node The node to report in the event of an error. 128 * @returns {void} 129 */ 130 function reportUnexpectedUnnamedFunction(node) { 131 context.report({ 132 node, 133 messageId: "unnamed", 134 loc: astUtils.getFunctionHeadLoc(node, sourceCode), 135 data: { name: astUtils.getFunctionNameWithKind(node) } 136 }); 137 } 138 139 /** 140 * Reports that a named function should be unnamed 141 * @param {ASTNode} node The node to report in the event of an error. 142 * @returns {void} 143 */ 144 function reportUnexpectedNamedFunction(node) { 145 context.report({ 146 node, 147 messageId: "named", 148 loc: astUtils.getFunctionHeadLoc(node, sourceCode), 149 data: { name: astUtils.getFunctionNameWithKind(node) } 150 }); 151 } 152 153 /** 154 * The listener for function nodes. 155 * @param {ASTNode} node function node 156 * @returns {void} 157 */ 158 function handleFunction(node) { 159 160 // Skip recursive functions. 161 const nameVar = context.getDeclaredVariables(node)[0]; 162 163 if (isFunctionName(nameVar) && nameVar.references.length > 0) { 164 return; 165 } 166 167 const hasName = Boolean(node.id && node.id.name); 168 const config = getConfigForNode(node); 169 170 if (config === "never") { 171 if (hasName && node.type !== "FunctionDeclaration") { 172 reportUnexpectedNamedFunction(node); 173 } 174 } else if (config === "as-needed") { 175 if (!hasName && !hasInferredName(node)) { 176 reportUnexpectedUnnamedFunction(node); 177 } 178 } else { 179 if (!hasName && !isObjectOrClassMethod(node)) { 180 reportUnexpectedUnnamedFunction(node); 181 } 182 } 183 } 184 185 return { 186 "FunctionExpression:exit": handleFunction, 187 "ExportDefaultDeclaration > FunctionDeclaration": handleFunction 188 }; 189 } 190}; 191