]>
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 | ||
27 | module.exports = { | |
28 | meta: { | |
29 | type: "suggestion", | |
30 | ||
31 | docs: { | |
32 | description: "require or disallow named `function` expressions", | |
eb39fafa DC |
33 | recommended: false, |
34 | url: "https://eslint.org/docs/rules/func-names" | |
35 | }, | |
36 | ||
37 | schema: { | |
38 | definitions: { | |
39 | value: { | |
40 | enum: [ | |
41 | "always", | |
42 | "as-needed", | |
43 | "never" | |
44 | ] | |
45 | } | |
46 | }, | |
47 | items: [ | |
48 | { | |
49 | $ref: "#/definitions/value" | |
50 | }, | |
51 | { | |
52 | type: "object", | |
53 | properties: { | |
54 | generators: { | |
55 | $ref: "#/definitions/value" | |
56 | } | |
57 | }, | |
58 | additionalProperties: false | |
59 | } | |
60 | ] | |
61 | }, | |
62 | ||
63 | messages: { | |
64 | unnamed: "Unexpected unnamed {{name}}.", | |
65 | named: "Unexpected named {{name}}." | |
66 | } | |
67 | }, | |
68 | ||
69 | create(context) { | |
70 | ||
71 | const sourceCode = context.getSourceCode(); | |
72 | ||
73 | /** | |
74 | * Returns the config option for the given node. | |
75 | * @param {ASTNode} node A node to get the config for. | |
76 | * @returns {string} The config option. | |
77 | */ | |
78 | function getConfigForNode(node) { | |
79 | if ( | |
80 | node.generator && | |
81 | context.options.length > 1 && | |
82 | context.options[1].generators | |
83 | ) { | |
84 | return context.options[1].generators; | |
85 | } | |
86 | ||
87 | return context.options[0] || "always"; | |
88 | } | |
89 | ||
90 | /** | |
91 | * Determines whether the current FunctionExpression node is a get, set, or | |
92 | * shorthand method in an object literal or a class. | |
93 | * @param {ASTNode} node A node to check. | |
94 | * @returns {boolean} True if the node is a get, set, or shorthand method. | |
95 | */ | |
96 | function isObjectOrClassMethod(node) { | |
97 | const parent = node.parent; | |
98 | ||
99 | return (parent.type === "MethodDefinition" || ( | |
100 | parent.type === "Property" && ( | |
101 | parent.method || | |
102 | parent.kind === "get" || | |
103 | parent.kind === "set" | |
104 | ) | |
105 | )); | |
106 | } | |
107 | ||
108 | /** | |
109 | * Determines whether the current FunctionExpression node has a name that would be | |
110 | * inferred from context in a conforming ES6 environment. | |
111 | * @param {ASTNode} node A node to check. | |
112 | * @returns {boolean} True if the node would have a name assigned automatically. | |
113 | */ | |
114 | function hasInferredName(node) { | |
115 | const parent = node.parent; | |
116 | ||
117 | return isObjectOrClassMethod(node) || | |
118 | (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) || | |
119 | (parent.type === "Property" && parent.value === node) || | |
609c276f | 120 | (parent.type === "PropertyDefinition" && parent.value === node) || |
eb39fafa DC |
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 | }; |