]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
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 | ||
12 | const 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 | */ | |
19 | function isFunctionName(variable) { | |
20 | return variable && variable.defs[0].type === "FunctionName"; | |
21 | } | |
22 | ||
23 | //------------------------------------------------------------------------------ | |
24 | // Rule Definition | |
25 | //------------------------------------------------------------------------------ | |
26 | ||
34eeec05 | 27 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
28 | module.exports = { |
29 | meta: { | |
30 | type: "suggestion", | |
31 | ||
32 | docs: { | |
33 | description: "require or disallow named `function` expressions", | |
eb39fafa DC |
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) || | |
609c276f | 121 | (parent.type === "PropertyDefinition" && parent.value === node) || |
eb39fafa DC |
122 | (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) || |
123 | (parent.type === "AssignmentPattern" && parent.left.type === "Identifier" && parent.right === node); | |
124 | } | |
125 | ||
126 | /** | |
127 | * Reports that an unnamed function should be named | |
128 | * @param {ASTNode} node The node to report in the event of an error. | |
129 | * @returns {void} | |
130 | */ | |
131 | function reportUnexpectedUnnamedFunction(node) { | |
132 | context.report({ | |
133 | node, | |
134 | messageId: "unnamed", | |
135 | loc: astUtils.getFunctionHeadLoc(node, sourceCode), | |
136 | data: { name: astUtils.getFunctionNameWithKind(node) } | |
137 | }); | |
138 | } | |
139 | ||
140 | /** | |
141 | * Reports that a named function should be unnamed | |
142 | * @param {ASTNode} node The node to report in the event of an error. | |
143 | * @returns {void} | |
144 | */ | |
145 | function reportUnexpectedNamedFunction(node) { | |
146 | context.report({ | |
147 | node, | |
148 | messageId: "named", | |
149 | loc: astUtils.getFunctionHeadLoc(node, sourceCode), | |
150 | data: { name: astUtils.getFunctionNameWithKind(node) } | |
151 | }); | |
152 | } | |
153 | ||
154 | /** | |
155 | * The listener for function nodes. | |
156 | * @param {ASTNode} node function node | |
157 | * @returns {void} | |
158 | */ | |
159 | function handleFunction(node) { | |
160 | ||
161 | // Skip recursive functions. | |
162 | const nameVar = context.getDeclaredVariables(node)[0]; | |
163 | ||
164 | if (isFunctionName(nameVar) && nameVar.references.length > 0) { | |
165 | return; | |
166 | } | |
167 | ||
168 | const hasName = Boolean(node.id && node.id.name); | |
169 | const config = getConfigForNode(node); | |
170 | ||
171 | if (config === "never") { | |
172 | if (hasName && node.type !== "FunctionDeclaration") { | |
173 | reportUnexpectedNamedFunction(node); | |
174 | } | |
175 | } else if (config === "as-needed") { | |
176 | if (!hasName && !hasInferredName(node)) { | |
177 | reportUnexpectedUnnamedFunction(node); | |
178 | } | |
179 | } else { | |
180 | if (!hasName && !isObjectOrClassMethod(node)) { | |
181 | reportUnexpectedUnnamedFunction(node); | |
182 | } | |
183 | } | |
184 | } | |
185 | ||
186 | return { | |
187 | "FunctionExpression:exit": handleFunction, | |
188 | "ExportDefaultDeclaration > FunctionDeclaration": handleFunction | |
189 | }; | |
190 | } | |
191 | }; |