.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / one-var.js
1 /**
2  * @fileoverview A rule to control the use of single variable declarations.
3  * @author Ian Christian Myers
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 /**
19  * Determines whether the given node is in a statement list.
20  * @param {ASTNode} node node to check
21  * @returns {boolean} `true` if the given node is in a statement list
22  */
23 function isInStatementList(node) {
24     return astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type);
25 }
26
27 //------------------------------------------------------------------------------
28 // Rule Definition
29 //------------------------------------------------------------------------------
30
31 module.exports = {
32     meta: {
33         type: "suggestion",
34
35         docs: {
36             description: "enforce variables to be declared either together or separately in functions",
37             category: "Stylistic Issues",
38             recommended: false,
39             url: "https://eslint.org/docs/rules/one-var"
40         },
41
42         fixable: "code",
43
44         schema: [
45             {
46                 oneOf: [
47                     {
48                         enum: ["always", "never", "consecutive"]
49                     },
50                     {
51                         type: "object",
52                         properties: {
53                             separateRequires: {
54                                 type: "boolean"
55                             },
56                             var: {
57                                 enum: ["always", "never", "consecutive"]
58                             },
59                             let: {
60                                 enum: ["always", "never", "consecutive"]
61                             },
62                             const: {
63                                 enum: ["always", "never", "consecutive"]
64                             }
65                         },
66                         additionalProperties: false
67                     },
68                     {
69                         type: "object",
70                         properties: {
71                             initialized: {
72                                 enum: ["always", "never", "consecutive"]
73                             },
74                             uninitialized: {
75                                 enum: ["always", "never", "consecutive"]
76                             }
77                         },
78                         additionalProperties: false
79                     }
80                 ]
81             }
82         ],
83
84         messages: {
85             combineUninitialized: "Combine this with the previous '{{type}}' statement with uninitialized variables.",
86             combineInitialized: "Combine this with the previous '{{type}}' statement with initialized variables.",
87             splitUninitialized: "Split uninitialized '{{type}}' declarations into multiple statements.",
88             splitInitialized: "Split initialized '{{type}}' declarations into multiple statements.",
89             splitRequires: "Split requires to be separated into a single block.",
90             combine: "Combine this with the previous '{{type}}' statement.",
91             split: "Split '{{type}}' declarations into multiple statements."
92         }
93     },
94
95     create(context) {
96         const MODE_ALWAYS = "always";
97         const MODE_NEVER = "never";
98         const MODE_CONSECUTIVE = "consecutive";
99         const mode = context.options[0] || MODE_ALWAYS;
100
101         const options = {};
102
103         if (typeof mode === "string") { // simple options configuration with just a string
104             options.var = { uninitialized: mode, initialized: mode };
105             options.let = { uninitialized: mode, initialized: mode };
106             options.const = { uninitialized: mode, initialized: mode };
107         } else if (typeof mode === "object") { // options configuration is an object
108             options.separateRequires = !!mode.separateRequires;
109             options.var = { uninitialized: mode.var, initialized: mode.var };
110             options.let = { uninitialized: mode.let, initialized: mode.let };
111             options.const = { uninitialized: mode.const, initialized: mode.const };
112             if (Object.prototype.hasOwnProperty.call(mode, "uninitialized")) {
113                 options.var.uninitialized = mode.uninitialized;
114                 options.let.uninitialized = mode.uninitialized;
115                 options.const.uninitialized = mode.uninitialized;
116             }
117             if (Object.prototype.hasOwnProperty.call(mode, "initialized")) {
118                 options.var.initialized = mode.initialized;
119                 options.let.initialized = mode.initialized;
120                 options.const.initialized = mode.initialized;
121             }
122         }
123
124         const sourceCode = context.getSourceCode();
125
126         //--------------------------------------------------------------------------
127         // Helpers
128         //--------------------------------------------------------------------------
129
130         const functionStack = [];
131         const blockStack = [];
132
133         /**
134          * Increments the blockStack counter.
135          * @returns {void}
136          * @private
137          */
138         function startBlock() {
139             blockStack.push({
140                 let: { initialized: false, uninitialized: false },
141                 const: { initialized: false, uninitialized: false }
142             });
143         }
144
145         /**
146          * Increments the functionStack counter.
147          * @returns {void}
148          * @private
149          */
150         function startFunction() {
151             functionStack.push({ initialized: false, uninitialized: false });
152             startBlock();
153         }
154
155         /**
156          * Decrements the blockStack counter.
157          * @returns {void}
158          * @private
159          */
160         function endBlock() {
161             blockStack.pop();
162         }
163
164         /**
165          * Decrements the functionStack counter.
166          * @returns {void}
167          * @private
168          */
169         function endFunction() {
170             functionStack.pop();
171             endBlock();
172         }
173
174         /**
175          * Check if a variable declaration is a require.
176          * @param {ASTNode} decl variable declaration Node
177          * @returns {bool} if decl is a require, return true; else return false.
178          * @private
179          */
180         function isRequire(decl) {
181             return decl.init && decl.init.type === "CallExpression" && decl.init.callee.name === "require";
182         }
183
184         /**
185          * Records whether initialized/uninitialized/required variables are defined in current scope.
186          * @param {string} statementType node.kind, one of: "var", "let", or "const"
187          * @param {ASTNode[]} declarations List of declarations
188          * @param {Object} currentScope The scope being investigated
189          * @returns {void}
190          * @private
191          */
192         function recordTypes(statementType, declarations, currentScope) {
193             for (let i = 0; i < declarations.length; i++) {
194                 if (declarations[i].init === null) {
195                     if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) {
196                         currentScope.uninitialized = true;
197                     }
198                 } else {
199                     if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) {
200                         if (options.separateRequires && isRequire(declarations[i])) {
201                             currentScope.required = true;
202                         } else {
203                             currentScope.initialized = true;
204                         }
205                     }
206                 }
207             }
208         }
209
210         /**
211          * Determines the current scope (function or block)
212          * @param  {string} statementType node.kind, one of: "var", "let", or "const"
213          * @returns {Object} The scope associated with statementType
214          */
215         function getCurrentScope(statementType) {
216             let currentScope;
217
218             if (statementType === "var") {
219                 currentScope = functionStack[functionStack.length - 1];
220             } else if (statementType === "let") {
221                 currentScope = blockStack[blockStack.length - 1].let;
222             } else if (statementType === "const") {
223                 currentScope = blockStack[blockStack.length - 1].const;
224             }
225             return currentScope;
226         }
227
228         /**
229          * Counts the number of initialized and uninitialized declarations in a list of declarations
230          * @param {ASTNode[]} declarations List of declarations
231          * @returns {Object} Counts of 'uninitialized' and 'initialized' declarations
232          * @private
233          */
234         function countDeclarations(declarations) {
235             const counts = { uninitialized: 0, initialized: 0 };
236
237             for (let i = 0; i < declarations.length; i++) {
238                 if (declarations[i].init === null) {
239                     counts.uninitialized++;
240                 } else {
241                     counts.initialized++;
242                 }
243             }
244             return counts;
245         }
246
247         /**
248          * Determines if there is more than one var statement in the current scope.
249          * @param {string} statementType node.kind, one of: "var", "let", or "const"
250          * @param {ASTNode[]} declarations List of declarations
251          * @returns {boolean} Returns true if it is the first var declaration, false if not.
252          * @private
253          */
254         function hasOnlyOneStatement(statementType, declarations) {
255
256             const declarationCounts = countDeclarations(declarations);
257             const currentOptions = options[statementType] || {};
258             const currentScope = getCurrentScope(statementType);
259             const hasRequires = declarations.some(isRequire);
260
261             if (currentOptions.uninitialized === MODE_ALWAYS && currentOptions.initialized === MODE_ALWAYS) {
262                 if (currentScope.uninitialized || currentScope.initialized) {
263                     if (!hasRequires) {
264                         return false;
265                     }
266                 }
267             }
268
269             if (declarationCounts.uninitialized > 0) {
270                 if (currentOptions.uninitialized === MODE_ALWAYS && currentScope.uninitialized) {
271                     return false;
272                 }
273             }
274             if (declarationCounts.initialized > 0) {
275                 if (currentOptions.initialized === MODE_ALWAYS && currentScope.initialized) {
276                     if (!hasRequires) {
277                         return false;
278                     }
279                 }
280             }
281             if (currentScope.required && hasRequires) {
282                 return false;
283             }
284             recordTypes(statementType, declarations, currentScope);
285             return true;
286         }
287
288         /**
289          * Fixer to join VariableDeclaration's into a single declaration
290          * @param {VariableDeclarator[]} declarations The `VariableDeclaration` to join
291          * @returns {Function} The fixer function
292          */
293         function joinDeclarations(declarations) {
294             const declaration = declarations[0];
295             const body = Array.isArray(declaration.parent.parent.body) ? declaration.parent.parent.body : [];
296             const currentIndex = body.findIndex(node => node.range[0] === declaration.parent.range[0]);
297             const previousNode = body[currentIndex - 1];
298
299             return fixer => {
300                 const type = sourceCode.getTokenBefore(declaration);
301                 const prevSemi = sourceCode.getTokenBefore(type);
302                 const res = [];
303
304                 if (previousNode && previousNode.kind === sourceCode.getText(type)) {
305                     if (prevSemi.value === ";") {
306                         res.push(fixer.replaceText(prevSemi, ","));
307                     } else {
308                         res.push(fixer.insertTextAfter(prevSemi, ","));
309                     }
310                     res.push(fixer.replaceText(type, ""));
311                 }
312
313                 return res;
314             };
315         }
316
317         /**
318          * Fixer to split a VariableDeclaration into individual declarations
319          * @param {VariableDeclaration} declaration The `VariableDeclaration` to split
320          * @returns {Function|null} The fixer function
321          */
322         function splitDeclarations(declaration) {
323             const { parent } = declaration;
324
325             // don't autofix code such as: if (foo) var x, y;
326             if (!isInStatementList(parent.type === "ExportNamedDeclaration" ? parent : declaration)) {
327                 return null;
328             }
329
330             return fixer => declaration.declarations.map(declarator => {
331                 const tokenAfterDeclarator = sourceCode.getTokenAfter(declarator);
332
333                 if (tokenAfterDeclarator === null) {
334                     return null;
335                 }
336
337                 const afterComma = sourceCode.getTokenAfter(tokenAfterDeclarator, { includeComments: true });
338
339                 if (tokenAfterDeclarator.value !== ",") {
340                     return null;
341                 }
342
343                 const exportPlacement = declaration.parent.type === "ExportNamedDeclaration" ? "export " : "";
344
345                 /*
346                  * `var x,y`
347                  * tokenAfterDeclarator ^^ afterComma
348                  */
349                 if (afterComma.range[0] === tokenAfterDeclarator.range[1]) {
350                     return fixer.replaceText(tokenAfterDeclarator, `; ${exportPlacement}${declaration.kind} `);
351                 }
352
353                 /*
354                  * `var x,
355                  * tokenAfterDeclarator ^
356                  *      y`
357                  *      ^ afterComma
358                  */
359                 if (
360                     afterComma.loc.start.line > tokenAfterDeclarator.loc.end.line ||
361                     afterComma.type === "Line" ||
362                     afterComma.type === "Block"
363                 ) {
364                     let lastComment = afterComma;
365
366                     while (lastComment.type === "Line" || lastComment.type === "Block") {
367                         lastComment = sourceCode.getTokenAfter(lastComment, { includeComments: true });
368                     }
369
370                     return fixer.replaceTextRange(
371                         [tokenAfterDeclarator.range[0], lastComment.range[0]],
372                         `;${sourceCode.text.slice(tokenAfterDeclarator.range[1], lastComment.range[0])}${exportPlacement}${declaration.kind} `
373                     );
374                 }
375
376                 return fixer.replaceText(tokenAfterDeclarator, `; ${exportPlacement}${declaration.kind}`);
377             }).filter(x => x);
378         }
379
380         /**
381          * Checks a given VariableDeclaration node for errors.
382          * @param {ASTNode} node The VariableDeclaration node to check
383          * @returns {void}
384          * @private
385          */
386         function checkVariableDeclaration(node) {
387             const parent = node.parent;
388             const type = node.kind;
389
390             if (!options[type]) {
391                 return;
392             }
393
394             const declarations = node.declarations;
395             const declarationCounts = countDeclarations(declarations);
396             const mixedRequires = declarations.some(isRequire) && !declarations.every(isRequire);
397
398             if (options[type].initialized === MODE_ALWAYS) {
399                 if (options.separateRequires && mixedRequires) {
400                     context.report({
401                         node,
402                         messageId: "splitRequires"
403                     });
404                 }
405             }
406
407             // consecutive
408             const nodeIndex = (parent.body && parent.body.length > 0 && parent.body.indexOf(node)) || 0;
409
410             if (nodeIndex > 0) {
411                 const previousNode = parent.body[nodeIndex - 1];
412                 const isPreviousNodeDeclaration = previousNode.type === "VariableDeclaration";
413                 const declarationsWithPrevious = declarations.concat(previousNode.declarations || []);
414
415                 if (
416                     isPreviousNodeDeclaration &&
417                     previousNode.kind === type &&
418                     !(declarationsWithPrevious.some(isRequire) && !declarationsWithPrevious.every(isRequire))
419                 ) {
420                     const previousDeclCounts = countDeclarations(previousNode.declarations);
421
422                     if (options[type].initialized === MODE_CONSECUTIVE && options[type].uninitialized === MODE_CONSECUTIVE) {
423                         context.report({
424                             node,
425                             messageId: "combine",
426                             data: {
427                                 type
428                             },
429                             fix: joinDeclarations(declarations)
430                         });
431                     } else if (options[type].initialized === MODE_CONSECUTIVE && declarationCounts.initialized > 0 && previousDeclCounts.initialized > 0) {
432                         context.report({
433                             node,
434                             messageId: "combineInitialized",
435                             data: {
436                                 type
437                             },
438                             fix: joinDeclarations(declarations)
439                         });
440                     } else if (options[type].uninitialized === MODE_CONSECUTIVE &&
441                             declarationCounts.uninitialized > 0 &&
442                             previousDeclCounts.uninitialized > 0) {
443                         context.report({
444                             node,
445                             messageId: "combineUninitialized",
446                             data: {
447                                 type
448                             },
449                             fix: joinDeclarations(declarations)
450                         });
451                     }
452                 }
453             }
454
455             // always
456             if (!hasOnlyOneStatement(type, declarations)) {
457                 if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) {
458                     context.report({
459                         node,
460                         messageId: "combine",
461                         data: {
462                             type
463                         },
464                         fix: joinDeclarations(declarations)
465                     });
466                 } else {
467                     if (options[type].initialized === MODE_ALWAYS && declarationCounts.initialized > 0) {
468                         context.report({
469                             node,
470                             messageId: "combineInitialized",
471                             data: {
472                                 type
473                             },
474                             fix: joinDeclarations(declarations)
475                         });
476                     }
477                     if (options[type].uninitialized === MODE_ALWAYS && declarationCounts.uninitialized > 0) {
478                         if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) {
479                             return;
480                         }
481                         context.report({
482                             node,
483                             messageId: "combineUninitialized",
484                             data: {
485                                 type
486                             },
487                             fix: joinDeclarations(declarations)
488                         });
489                     }
490                 }
491             }
492
493             // never
494             if (parent.type !== "ForStatement" || parent.init !== node) {
495                 const totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized;
496
497                 if (totalDeclarations > 1) {
498                     if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) {
499
500                         // both initialized and uninitialized
501                         context.report({
502                             node,
503                             messageId: "split",
504                             data: {
505                                 type
506                             },
507                             fix: splitDeclarations(node)
508                         });
509                     } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) {
510
511                         // initialized
512                         context.report({
513                             node,
514                             messageId: "splitInitialized",
515                             data: {
516                                 type
517                             },
518                             fix: splitDeclarations(node)
519                         });
520                     } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) {
521
522                         // uninitialized
523                         context.report({
524                             node,
525                             messageId: "splitUninitialized",
526                             data: {
527                                 type
528                             },
529                             fix: splitDeclarations(node)
530                         });
531                     }
532                 }
533             }
534         }
535
536         //--------------------------------------------------------------------------
537         // Public API
538         //--------------------------------------------------------------------------
539
540         return {
541             Program: startFunction,
542             FunctionDeclaration: startFunction,
543             FunctionExpression: startFunction,
544             ArrowFunctionExpression: startFunction,
545             BlockStatement: startBlock,
546             ForStatement: startBlock,
547             ForInStatement: startBlock,
548             ForOfStatement: startBlock,
549             SwitchStatement: startBlock,
550             VariableDeclaration: checkVariableDeclaration,
551             "ForStatement:exit": endBlock,
552             "ForOfStatement:exit": endBlock,
553             "ForInStatement:exit": endBlock,
554             "SwitchStatement:exit": endBlock,
555             "BlockStatement:exit": endBlock,
556             "Program:exit": endFunction,
557             "FunctionDeclaration:exit": endFunction,
558             "FunctionExpression:exit": endFunction,
559             "ArrowFunctionExpression:exit": endFunction
560         };
561
562     }
563 };