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