]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/new-cap.js
import 8.23.1 source
[pve-eslint.git] / eslint / lib / rules / new-cap.js
1 /**
2 * @fileoverview Rule to flag use of constructors without capital letters
3 * @author Nicholas C. Zakas
4 */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 const CAPS_ALLOWED = [
19 "Array",
20 "Boolean",
21 "Date",
22 "Error",
23 "Function",
24 "Number",
25 "Object",
26 "RegExp",
27 "String",
28 "Symbol",
29 "BigInt"
30 ];
31
32 /**
33 * Ensure that if the key is provided, it must be an array.
34 * @param {Object} obj Object to check with `key`.
35 * @param {string} key Object key to check on `obj`.
36 * @param {any} fallback If obj[key] is not present, this will be returned.
37 * @throws {TypeError} If key is not an own array type property of `obj`.
38 * @returns {string[]} Returns obj[key] if it's an Array, otherwise `fallback`
39 */
40 function checkArray(obj, key, fallback) {
41
42 /* c8 ignore start */
43 if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) {
44 throw new TypeError(`${key}, if provided, must be an Array`);
45 }/* c8 ignore stop */
46 return obj[key] || fallback;
47 }
48
49 /**
50 * A reducer function to invert an array to an Object mapping the string form of the key, to `true`.
51 * @param {Object} map Accumulator object for the reduce.
52 * @param {string} key Object key to set to `true`.
53 * @returns {Object} Returns the updated Object for further reduction.
54 */
55 function invert(map, key) {
56 map[key] = true;
57 return map;
58 }
59
60 /**
61 * Creates an object with the cap is new exceptions as its keys and true as their values.
62 * @param {Object} config Rule configuration
63 * @returns {Object} Object with cap is new exceptions.
64 */
65 function calculateCapIsNewExceptions(config) {
66 let capIsNewExceptions = checkArray(config, "capIsNewExceptions", CAPS_ALLOWED);
67
68 if (capIsNewExceptions !== CAPS_ALLOWED) {
69 capIsNewExceptions = capIsNewExceptions.concat(CAPS_ALLOWED);
70 }
71
72 return capIsNewExceptions.reduce(invert, {});
73 }
74
75 //------------------------------------------------------------------------------
76 // Rule Definition
77 //------------------------------------------------------------------------------
78
79 /** @type {import('../shared/types').Rule} */
80 module.exports = {
81 meta: {
82 type: "suggestion",
83
84 docs: {
85 description: "Require constructor names to begin with a capital letter",
86 recommended: false,
87 url: "https://eslint.org/docs/rules/new-cap"
88 },
89
90 schema: [
91 {
92 type: "object",
93 properties: {
94 newIsCap: {
95 type: "boolean",
96 default: true
97 },
98 capIsNew: {
99 type: "boolean",
100 default: true
101 },
102 newIsCapExceptions: {
103 type: "array",
104 items: {
105 type: "string"
106 }
107 },
108 newIsCapExceptionPattern: {
109 type: "string"
110 },
111 capIsNewExceptions: {
112 type: "array",
113 items: {
114 type: "string"
115 }
116 },
117 capIsNewExceptionPattern: {
118 type: "string"
119 },
120 properties: {
121 type: "boolean",
122 default: true
123 }
124 },
125 additionalProperties: false
126 }
127 ],
128 messages: {
129 upper: "A function with a name starting with an uppercase letter should only be used as a constructor.",
130 lower: "A constructor name should not start with a lowercase letter."
131 }
132 },
133
134 create(context) {
135
136 const config = Object.assign({}, context.options[0]);
137
138 config.newIsCap = config.newIsCap !== false;
139 config.capIsNew = config.capIsNew !== false;
140 const skipProperties = config.properties === false;
141
142 const newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {});
143 const newIsCapExceptionPattern = config.newIsCapExceptionPattern ? new RegExp(config.newIsCapExceptionPattern, "u") : null;
144
145 const capIsNewExceptions = calculateCapIsNewExceptions(config);
146 const capIsNewExceptionPattern = config.capIsNewExceptionPattern ? new RegExp(config.capIsNewExceptionPattern, "u") : null;
147
148 const listeners = {};
149
150 const sourceCode = context.getSourceCode();
151
152 //--------------------------------------------------------------------------
153 // Helpers
154 //--------------------------------------------------------------------------
155
156 /**
157 * Get exact callee name from expression
158 * @param {ASTNode} node CallExpression or NewExpression node
159 * @returns {string} name
160 */
161 function extractNameFromExpression(node) {
162 return node.callee.type === "Identifier"
163 ? node.callee.name
164 : astUtils.getStaticPropertyName(node.callee) || "";
165 }
166
167 /**
168 * Returns the capitalization state of the string -
169 * Whether the first character is uppercase, lowercase, or non-alphabetic
170 * @param {string} str String
171 * @returns {string} capitalization state: "non-alpha", "lower", or "upper"
172 */
173 function getCap(str) {
174 const firstChar = str.charAt(0);
175
176 const firstCharLower = firstChar.toLowerCase();
177 const firstCharUpper = firstChar.toUpperCase();
178
179 if (firstCharLower === firstCharUpper) {
180
181 // char has no uppercase variant, so it's non-alphabetic
182 return "non-alpha";
183 }
184 if (firstChar === firstCharLower) {
185 return "lower";
186 }
187 return "upper";
188
189 }
190
191 /**
192 * Check if capitalization is allowed for a CallExpression
193 * @param {Object} allowedMap Object mapping calleeName to a Boolean
194 * @param {ASTNode} node CallExpression node
195 * @param {string} calleeName Capitalized callee name from a CallExpression
196 * @param {Object} pattern RegExp object from options pattern
197 * @returns {boolean} Returns true if the callee may be capitalized
198 */
199 function isCapAllowed(allowedMap, node, calleeName, pattern) {
200 const sourceText = sourceCode.getText(node.callee);
201
202 if (allowedMap[calleeName] || allowedMap[sourceText]) {
203 return true;
204 }
205
206 if (pattern && pattern.test(sourceText)) {
207 return true;
208 }
209
210 const callee = astUtils.skipChainExpression(node.callee);
211
212 if (calleeName === "UTC" && callee.type === "MemberExpression") {
213
214 // allow if callee is Date.UTC
215 return callee.object.type === "Identifier" &&
216 callee.object.name === "Date";
217 }
218
219 return skipProperties && callee.type === "MemberExpression";
220 }
221
222 /**
223 * Reports the given messageId for the given node. The location will be the start of the property or the callee.
224 * @param {ASTNode} node CallExpression or NewExpression node.
225 * @param {string} messageId The messageId to report.
226 * @returns {void}
227 */
228 function report(node, messageId) {
229 let callee = astUtils.skipChainExpression(node.callee);
230
231 if (callee.type === "MemberExpression") {
232 callee = callee.property;
233 }
234
235 context.report({ node, loc: callee.loc, messageId });
236 }
237
238 //--------------------------------------------------------------------------
239 // Public
240 //--------------------------------------------------------------------------
241
242 if (config.newIsCap) {
243 listeners.NewExpression = function(node) {
244
245 const constructorName = extractNameFromExpression(node);
246
247 if (constructorName) {
248 const capitalization = getCap(constructorName);
249 const isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName, newIsCapExceptionPattern);
250
251 if (!isAllowed) {
252 report(node, "lower");
253 }
254 }
255 };
256 }
257
258 if (config.capIsNew) {
259 listeners.CallExpression = function(node) {
260
261 const calleeName = extractNameFromExpression(node);
262
263 if (calleeName) {
264 const capitalization = getCap(calleeName);
265 const isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName, capIsNewExceptionPattern);
266
267 if (!isAllowed) {
268 report(node, "upper");
269 }
270 }
271 };
272 }
273
274 return listeners;
275 }
276 };