UNPKG

7.35 kBJavaScriptView Raw
1/**
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
4 */
5
6"use strict";
7
8//--------------------------------------------------------------------------
9// Requirements
10//--------------------------------------------------------------------------
11
12const astUtils = require("../ast-utils");
13const esutils = require("esutils");
14
15//--------------------------------------------------------------------------
16// Helpers
17//--------------------------------------------------------------------------
18
19/**
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"]`
23 */
24function isModuleExports(pattern) {
25 if (pattern.type === "MemberExpression" && pattern.object.type === "Identifier" && pattern.object.name === "module") {
26
27 // module.exports
28 if (pattern.property.type === "Identifier" && pattern.property.name === "exports") {
29 return true;
30 }
31
32 // module["exports"]
33 if (pattern.property.type === "Literal" && pattern.property.value === "exports") {
34 return true;
35 }
36 }
37 return false;
38}
39
40/**
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
45 */
46function isIdentifier(name, ecmaVersion) {
47 if (ecmaVersion >= 6) {
48 return esutils.keyword.isIdentifierES6(name);
49 }
50 return esutils.keyword.isIdentifierES5(name);
51}
52
53//------------------------------------------------------------------------------
54// Rule Definition
55//------------------------------------------------------------------------------
56
57const alwaysOrNever = { enum: ["always", "never"] };
58const optionsObject = {
59 type: "object",
60 properties: {
61 includeCommonJSModuleExports: {
62 type: "boolean"
63 }
64 },
65 additionalProperties: false
66};
67
68module.exports = {
69 meta: {
70 docs: {
71 description: "require function names to match the name of the variable or property to which they are assigned",
72 category: "Stylistic Issues",
73 recommended: false,
74 url: "https://eslint.org/docs/rules/func-name-matching"
75 },
76
77 schema: {
78 anyOf: [{
79 type: "array",
80 additionalItems: false,
81 items: [alwaysOrNever, optionsObject]
82 }, {
83 type: "array",
84 additionalItems: false,
85 items: [optionsObject]
86 }]
87 }
88 },
89
90 create(context) {
91 const options = (typeof context.options[0] === "object" ? context.options[0] : context.options[1]) || {};
92 const nameMatches = typeof context.options[0] === "string" ? context.options[0] : "always";
93 const includeModuleExports = options.includeCommonJSModuleExports;
94 const ecmaVersion = context.parserOptions && context.parserOptions.ecmaVersion ? context.parserOptions.ecmaVersion : 5;
95
96 /**
97 * Compares identifiers based on the nameMatches option
98 * @param {string} x the first identifier
99 * @param {string} y the second identifier
100 * @returns {boolean} whether the two identifiers should warn.
101 */
102 function shouldWarn(x, y) {
103 return (nameMatches === "always" && x !== y) || (nameMatches === "never" && x === y);
104 }
105
106 /**
107 * Reports
108 * @param {ASTNode} node The node to report
109 * @param {string} name The variable or property name
110 * @param {string} funcName The function name
111 * @param {boolean} isProp True if the reported node is a property assignment
112 * @returns {void}
113 */
114 function report(node, name, funcName, isProp) {
115 let message;
116
117 if (nameMatches === "always" && isProp) {
118 message = "Function name `{{funcName}}` should match property name `{{name}}`";
119 } else if (nameMatches === "always") {
120 message = "Function name `{{funcName}}` should match variable name `{{name}}`";
121 } else if (isProp) {
122 message = "Function name `{{funcName}}` should not match property name `{{name}}`";
123 } else {
124 message = "Function name `{{funcName}}` should not match variable name `{{name}}`";
125 }
126 context.report({
127 node,
128 message,
129 data: {
130 name,
131 funcName
132 }
133 });
134 }
135
136 /**
137 * Determines whether a given node is a string literal
138 * @param {ASTNode} node The node to check
139 * @returns {boolean} `true` if the node is a string literal
140 */
141 function isStringLiteral(node) {
142 return node.type === "Literal" && typeof node.value === "string";
143 }
144
145 //--------------------------------------------------------------------------
146 // Public
147 //--------------------------------------------------------------------------
148
149 return {
150
151 VariableDeclarator(node) {
152 if (!node.init || node.init.type !== "FunctionExpression" || node.id.type !== "Identifier") {
153 return;
154 }
155 if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) {
156 report(node, node.id.name, node.init.id.name, false);
157 }
158 },
159
160 AssignmentExpression(node) {
161 if (
162 node.right.type !== "FunctionExpression" ||
163 (node.left.computed && node.left.property.type !== "Literal") ||
164 (!includeModuleExports && isModuleExports(node.left)) ||
165 (node.left.type !== "Identifier" && node.left.type !== "MemberExpression")
166 ) {
167 return;
168 }
169
170 const isProp = node.left.type === "MemberExpression";
171 const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name;
172
173 if (node.right.id && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
174 report(node, name, node.right.id.name, isProp);
175 }
176 },
177
178 Property(node) {
179 if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && !isStringLiteral(node.key)) {
180 return;
181 }
182 if (node.key.type === "Identifier" && shouldWarn(node.key.name, node.value.id.name)) {
183 report(node, node.key.name, node.value.id.name, true);
184 } else if (
185 isStringLiteral(node.key) &&
186 isIdentifier(node.key.value, ecmaVersion) &&
187 shouldWarn(node.key.value, node.value.id.name)
188 ) {
189 report(node, node.key.value, node.value.id.name, true);
190 }
191 }
192 };
193 }
194};