UNPKG

6.02 kBJavaScriptView Raw
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 /**
73 * Returns the config option for the given node.
74 * @param {ASTNode} node - A node to get the config for.
75 * @returns {string} The config option.
76 */
77 function getConfigForNode(node) {
78 if (
79 node.generator &&
80 context.options.length > 1 &&
81 context.options[1].generators
82 ) {
83 return context.options[1].generators;
84 }
85
86 return context.options[0] || "always";
87 }
88
89 /**
90 * Determines whether the current FunctionExpression node is a get, set, or
91 * shorthand method in an object literal or a class.
92 * @param {ASTNode} node - A node to check.
93 * @returns {boolean} True if the node is a get, set, or shorthand method.
94 */
95 function isObjectOrClassMethod(node) {
96 const parent = node.parent;
97
98 return (parent.type === "MethodDefinition" || (
99 parent.type === "Property" && (
100 parent.method ||
101 parent.kind === "get" ||
102 parent.kind === "set"
103 )
104 ));
105 }
106
107 /**
108 * Determines whether the current FunctionExpression node has a name that would be
109 * inferred from context in a conforming ES6 environment.
110 * @param {ASTNode} node - A node to check.
111 * @returns {boolean} True if the node would have a name assigned automatically.
112 */
113 function hasInferredName(node) {
114 const parent = node.parent;
115
116 return isObjectOrClassMethod(node) ||
117 (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) ||
118 (parent.type === "Property" && parent.value === node) ||
119 (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) ||
120 (parent.type === "ExportDefaultDeclaration" && parent.declaration === node) ||
121 (parent.type === "AssignmentPattern" && parent.right === node);
122 }
123
124 /**
125 * Reports that an unnamed function should be named
126 * @param {ASTNode} node - The node to report in the event of an error.
127 * @returns {void}
128 */
129 function reportUnexpectedUnnamedFunction(node) {
130 context.report({
131 node,
132 messageId: "unnamed",
133 data: { name: astUtils.getFunctionNameWithKind(node) }
134 });
135 }
136
137 /**
138 * Reports that a named function should be unnamed
139 * @param {ASTNode} node - The node to report in the event of an error.
140 * @returns {void}
141 */
142 function reportUnexpectedNamedFunction(node) {
143 context.report({
144 node,
145 messageId: "named",
146 data: { name: astUtils.getFunctionNameWithKind(node) }
147 });
148 }
149
150 return {
151 "FunctionExpression:exit"(node) {
152
153 // Skip recursive functions.
154 const nameVar = context.getDeclaredVariables(node)[0];
155
156 if (isFunctionName(nameVar) && nameVar.references.length > 0) {
157 return;
158 }
159
160 const hasName = Boolean(node.id && node.id.name);
161 const config = getConfigForNode(node);
162
163 if (config === "never") {
164 if (hasName) {
165 reportUnexpectedNamedFunction(node);
166 }
167 } else if (config === "as-needed") {
168 if (!hasName && !hasInferredName(node)) {
169 reportUnexpectedUnnamedFunction(node);
170 }
171 } else {
172 if (!hasName && !isObjectOrClassMethod(node)) {
173 reportUnexpectedUnnamedFunction(node);
174 }
175 }
176 }
177 };
178 }
179};