.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / 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 {*} 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  */
39 function 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  */
54 function 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  */
64 function 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
78 module.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             return node.callee.type === "Identifier"
162                 ? node.callee.name
163                 : astUtils.getStaticPropertyName(node.callee) || "";
164         }
165
166         /**
167          * Returns the capitalization state of the string -
168          * Whether the first character is uppercase, lowercase, or non-alphabetic
169          * @param {string} str String
170          * @returns {string} capitalization state: "non-alpha", "lower", or "upper"
171          */
172         function getCap(str) {
173             const firstChar = str.charAt(0);
174
175             const firstCharLower = firstChar.toLowerCase();
176             const firstCharUpper = firstChar.toUpperCase();
177
178             if (firstCharLower === firstCharUpper) {
179
180                 // char has no uppercase variant, so it's non-alphabetic
181                 return "non-alpha";
182             }
183             if (firstChar === firstCharLower) {
184                 return "lower";
185             }
186             return "upper";
187
188         }
189
190         /**
191          * Check if capitalization is allowed for a CallExpression
192          * @param {Object} allowedMap Object mapping calleeName to a Boolean
193          * @param {ASTNode} node CallExpression node
194          * @param {string} calleeName Capitalized callee name from a CallExpression
195          * @param {Object} pattern RegExp object from options pattern
196          * @returns {boolean} Returns true if the callee may be capitalized
197          */
198         function isCapAllowed(allowedMap, node, calleeName, pattern) {
199             const sourceText = sourceCode.getText(node.callee);
200
201             if (allowedMap[calleeName] || allowedMap[sourceText]) {
202                 return true;
203             }
204
205             if (pattern && pattern.test(sourceText)) {
206                 return true;
207             }
208
209             const callee = astUtils.skipChainExpression(node.callee);
210
211             if (calleeName === "UTC" && callee.type === "MemberExpression") {
212
213                 // allow if callee is Date.UTC
214                 return callee.object.type === "Identifier" &&
215                     callee.object.name === "Date";
216             }
217
218             return skipProperties && callee.type === "MemberExpression";
219         }
220
221         /**
222          * Reports the given messageId for the given node. The location will be the start of the property or the callee.
223          * @param {ASTNode} node CallExpression or NewExpression node.
224          * @param {string} messageId The messageId to report.
225          * @returns {void}
226          */
227         function report(node, messageId) {
228             let callee = astUtils.skipChainExpression(node.callee);
229
230             if (callee.type === "MemberExpression") {
231                 callee = callee.property;
232             }
233
234             context.report({ node, loc: callee.loc, messageId });
235         }
236
237         //--------------------------------------------------------------------------
238         // Public
239         //--------------------------------------------------------------------------
240
241         if (config.newIsCap) {
242             listeners.NewExpression = function(node) {
243
244                 const constructorName = extractNameFromExpression(node);
245
246                 if (constructorName) {
247                     const capitalization = getCap(constructorName);
248                     const isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName, newIsCapExceptionPattern);
249
250                     if (!isAllowed) {
251                         report(node, "lower");
252                     }
253                 }
254             };
255         }
256
257         if (config.capIsNew) {
258             listeners.CallExpression = function(node) {
259
260                 const calleeName = extractNameFromExpression(node);
261
262                 if (calleeName) {
263                     const capitalization = getCap(calleeName);
264                     const isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName, capIsNewExceptionPattern);
265
266                     if (!isAllowed) {
267                         report(node, "upper");
268                     }
269                 }
270             };
271         }
272
273         return listeners;
274     }
275 };