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