2 * @fileoverview Rule to require function names to match the name of the variable or property to which they are assigned.
3 * @author Annie Zhang, Pavel Strashkin
8 //--------------------------------------------------------------------------
10 //--------------------------------------------------------------------------
12 const astUtils
= require("./utils/ast-utils");
13 const esutils
= require("esutils");
15 //--------------------------------------------------------------------------
17 //--------------------------------------------------------------------------
20 * Determines if a pattern is `module.exports` or `module["exports"]`
21 * @param {ASTNode} pattern The left side of the AssignmentExpression
22 * @returns {boolean} True if the pattern is `module.exports` or `module["exports"]`
24 function isModuleExports(pattern
) {
25 if (pattern
.type
=== "MemberExpression" && pattern
.object
.type
=== "Identifier" && pattern
.object
.name
=== "module") {
28 if (pattern
.property
.type
=== "Identifier" && pattern
.property
.name
=== "exports") {
33 if (pattern
.property
.type
=== "Literal" && pattern
.property
.value
=== "exports") {
41 * Determines if a string name is a valid identifier
42 * @param {string} name The string to be checked
43 * @param {int} ecmaVersion The ECMAScript version if specified in the parserOptions config
44 * @returns {boolean} True if the string is a valid identifier
46 function isIdentifier(name
, ecmaVersion
) {
47 if (ecmaVersion
>= 6) {
48 return esutils
.keyword
.isIdentifierES6(name
);
50 return esutils
.keyword
.isIdentifierES5(name
);
53 //------------------------------------------------------------------------------
55 //------------------------------------------------------------------------------
57 const alwaysOrNever
= { enum: ["always", "never"] };
58 const optionsObject
= {
61 considerPropertyDescriptor
: {
64 includeCommonJSModuleExports
: {
68 additionalProperties
: false
71 /** @type {import('../shared/types').Rule} */
77 description
: "Require function names to match the name of the variable or property to which they are assigned",
79 url
: "https://eslint.org/docs/rules/func-name-matching"
85 additionalItems
: false,
86 items
: [alwaysOrNever
, optionsObject
]
89 additionalItems
: false,
90 items
: [optionsObject
]
95 matchProperty
: "Function name `{{funcName}}` should match property name `{{name}}`.",
96 matchVariable
: "Function name `{{funcName}}` should match variable name `{{name}}`.",
97 notMatchProperty
: "Function name `{{funcName}}` should not match property name `{{name}}`.",
98 notMatchVariable
: "Function name `{{funcName}}` should not match variable name `{{name}}`."
103 const options
= (typeof context
.options
[0] === "object" ? context
.options
[0] : context
.options
[1]) || {};
104 const nameMatches
= typeof context
.options
[0] === "string" ? context
.options
[0] : "always";
105 const considerPropertyDescriptor
= options
.considerPropertyDescriptor
;
106 const includeModuleExports
= options
.includeCommonJSModuleExports
;
107 const ecmaVersion
= context
.parserOptions
&& context
.parserOptions
.ecmaVersion
? context
.parserOptions
.ecmaVersion
: 5;
110 * Check whether node is a certain CallExpression.
111 * @param {string} objName object name
112 * @param {string} funcName function name
113 * @param {ASTNode} node The node to check
114 * @returns {boolean} `true` if node matches CallExpression
116 function isPropertyCall(objName
, funcName
, node
) {
120 return node
.type
=== "CallExpression" && astUtils
.isSpecificMemberAccess(node
.callee
, objName
, funcName
);
124 * Compares identifiers based on the nameMatches option
125 * @param {string} x the first identifier
126 * @param {string} y the second identifier
127 * @returns {boolean} whether the two identifiers should warn.
129 function shouldWarn(x
, y
) {
130 return (nameMatches
=== "always" && x
!== y
) || (nameMatches
=== "never" && x
=== y
);
135 * @param {ASTNode} node The node to report
136 * @param {string} name The variable or property name
137 * @param {string} funcName The function name
138 * @param {boolean} isProp True if the reported node is a property assignment
141 function report(node
, name
, funcName
, isProp
) {
144 if (nameMatches
=== "always" && isProp
) {
145 messageId
= "matchProperty";
146 } else if (nameMatches
=== "always") {
147 messageId
= "matchVariable";
149 messageId
= "notMatchProperty";
151 messageId
= "notMatchVariable";
164 * Determines whether a given node is a string literal
165 * @param {ASTNode} node The node to check
166 * @returns {boolean} `true` if the node is a string literal
168 function isStringLiteral(node
) {
169 return node
.type
=== "Literal" && typeof node
.value
=== "string";
172 //--------------------------------------------------------------------------
174 //--------------------------------------------------------------------------
177 VariableDeclarator(node
) {
178 if (!node
.init
|| node
.init
.type
!== "FunctionExpression" || node
.id
.type
!== "Identifier") {
181 if (node
.init
.id
&& shouldWarn(node
.id
.name
, node
.init
.id
.name
)) {
182 report(node
, node
.id
.name
, node
.init
.id
.name
, false);
186 AssignmentExpression(node
) {
188 node
.right
.type
!== "FunctionExpression" ||
189 (node
.left
.computed
&& node
.left
.property
.type
!== "Literal") ||
190 (!includeModuleExports
&& isModuleExports(node
.left
)) ||
191 (node
.left
.type
!== "Identifier" && node
.left
.type
!== "MemberExpression")
196 const isProp
= node
.left
.type
=== "MemberExpression";
197 const name
= isProp
? astUtils
.getStaticPropertyName(node
.left
) : node
.left
.name
;
199 if (node
.right
.id
&& name
&& isIdentifier(name
) && shouldWarn(name
, node
.right
.id
.name
)) {
200 report(node
, name
, node
.right
.id
.name
, isProp
);
204 "Property, PropertyDefinition[value]"(node
) {
205 if (!(node
.value
.type
=== "FunctionExpression" && node
.value
.id
)) {
209 if (node
.key
.type
=== "Identifier" && !node
.computed
) {
210 const functionName
= node
.value
.id
.name
;
211 let propertyName
= node
.key
.name
;
214 considerPropertyDescriptor
&&
215 propertyName
=== "value" &&
216 node
.parent
.type
=== "ObjectExpression"
218 if (isPropertyCall("Object", "defineProperty", node
.parent
.parent
) || isPropertyCall("Reflect", "defineProperty", node
.parent
.parent
)) {
219 const property
= node
.parent
.parent
.arguments
[1];
221 if (isStringLiteral(property
) && shouldWarn(property
.value
, functionName
)) {
222 report(node
, property
.value
, functionName
, true);
224 } else if (isPropertyCall("Object", "defineProperties", node
.parent
.parent
.parent
.parent
)) {
225 propertyName
= node
.parent
.parent
.key
.name
;
226 if (!node
.parent
.parent
.computed
&& shouldWarn(propertyName
, functionName
)) {
227 report(node
, propertyName
, functionName
, true);
229 } else if (isPropertyCall("Object", "create", node
.parent
.parent
.parent
.parent
)) {
230 propertyName
= node
.parent
.parent
.key
.name
;
231 if (!node
.parent
.parent
.computed
&& shouldWarn(propertyName
, functionName
)) {
232 report(node
, propertyName
, functionName
, true);
234 } else if (shouldWarn(propertyName
, functionName
)) {
235 report(node
, propertyName
, functionName
, true);
237 } else if (shouldWarn(propertyName
, functionName
)) {
238 report(node
, propertyName
, functionName
, true);
244 isStringLiteral(node
.key
) &&
245 isIdentifier(node
.key
.value
, ecmaVersion
) &&
246 shouldWarn(node
.key
.value
, node
.value
.id
.name
)
248 report(node
, node
.key
.value
, node
.value
.id
.name
, true);