massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / arrow-body-style.js
1 /**
2  * @fileoverview Rule to require braces in arrow function body.
3  * @author Alberto Rodríguez
4  */
5 "use strict";
6
7 //------------------------------------------------------------------------------
8 // Requirements
9 //------------------------------------------------------------------------------
10
11 const astUtils = require("./utils/ast-utils");
12
13 //------------------------------------------------------------------------------
14 // Rule Definition
15 //------------------------------------------------------------------------------
16
17 module.exports = {
18     meta: {
19         type: "suggestion",
20
21         docs: {
22             description: "require braces around arrow function bodies",
23             category: "ECMAScript 6",
24             recommended: false,
25             url: "https://eslint.org/docs/rules/arrow-body-style"
26         },
27
28         schema: {
29             anyOf: [
30                 {
31                     type: "array",
32                     items: [
33                         {
34                             enum: ["always", "never"]
35                         }
36                     ],
37                     minItems: 0,
38                     maxItems: 1
39                 },
40                 {
41                     type: "array",
42                     items: [
43                         {
44                             enum: ["as-needed"]
45                         },
46                         {
47                             type: "object",
48                             properties: {
49                                 requireReturnForObjectLiteral: { type: "boolean" }
50                             },
51                             additionalProperties: false
52                         }
53                     ],
54                     minItems: 0,
55                     maxItems: 2
56                 }
57             ]
58         },
59
60         fixable: "code",
61
62         messages: {
63             unexpectedOtherBlock: "Unexpected block statement surrounding arrow body.",
64             unexpectedEmptyBlock: "Unexpected block statement surrounding arrow body; put a value of `undefined` immediately after the `=>`.",
65             unexpectedObjectBlock: "Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the `=>`.",
66             unexpectedSingleBlock: "Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`.",
67             expectedBlock: "Expected block statement surrounding arrow body."
68         }
69     },
70
71     create(context) {
72         const options = context.options;
73         const always = options[0] === "always";
74         const asNeeded = !options[0] || options[0] === "as-needed";
75         const never = options[0] === "never";
76         const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral;
77         const sourceCode = context.getSourceCode();
78         let funcInfo = null;
79
80         /**
81          * Checks whether the given node has ASI problem or not.
82          * @param {Token} token The token to check.
83          * @returns {boolean} `true` if it changes semantics if `;` or `}` followed by the token are removed.
84          */
85         function hasASIProblem(token) {
86             return token && token.type === "Punctuator" && /^[([/`+-]/u.test(token.value);
87         }
88
89         /**
90          * Gets the closing parenthesis by the given node.
91          * @param {ASTNode} node first node after an opening parenthesis.
92          * @returns {Token} The found closing parenthesis token.
93          */
94         function findClosingParen(node) {
95             let nodeToCheck = node;
96
97             while (!astUtils.isParenthesised(sourceCode, nodeToCheck)) {
98                 nodeToCheck = nodeToCheck.parent;
99             }
100             return sourceCode.getTokenAfter(nodeToCheck);
101         }
102
103         /**
104          * Check whether the node is inside of a for loop's init
105          * @param {ASTNode} node node is inside for loop
106          * @returns {boolean} `true` if the node is inside of a for loop, else `false`
107          */
108         function isInsideForLoopInitializer(node) {
109             if (node && node.parent) {
110                 if (node.parent.type === "ForStatement" && node.parent.init === node) {
111                     return true;
112                 }
113                 return isInsideForLoopInitializer(node.parent);
114             }
115             return false;
116         }
117
118         /**
119          * Determines whether a arrow function body needs braces
120          * @param {ASTNode} node The arrow function node.
121          * @returns {void}
122          */
123         function validate(node) {
124             const arrowBody = node.body;
125
126             if (arrowBody.type === "BlockStatement") {
127                 const blockBody = arrowBody.body;
128
129                 if (blockBody.length !== 1 && !never) {
130                     return;
131                 }
132
133                 if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" &&
134                     blockBody[0].argument && blockBody[0].argument.type === "ObjectExpression") {
135                     return;
136                 }
137
138                 if (never || asNeeded && blockBody[0].type === "ReturnStatement") {
139                     let messageId;
140
141                     if (blockBody.length === 0) {
142                         messageId = "unexpectedEmptyBlock";
143                     } else if (blockBody.length > 1) {
144                         messageId = "unexpectedOtherBlock";
145                     } else if (blockBody[0].argument === null) {
146                         messageId = "unexpectedSingleBlock";
147                     } else if (astUtils.isOpeningBraceToken(sourceCode.getFirstToken(blockBody[0], { skip: 1 }))) {
148                         messageId = "unexpectedObjectBlock";
149                     } else {
150                         messageId = "unexpectedSingleBlock";
151                     }
152
153                     context.report({
154                         node,
155                         loc: arrowBody.loc,
156                         messageId,
157                         fix(fixer) {
158                             const fixes = [];
159
160                             if (blockBody.length !== 1 ||
161                                 blockBody[0].type !== "ReturnStatement" ||
162                                 !blockBody[0].argument ||
163                                 hasASIProblem(sourceCode.getTokenAfter(arrowBody))
164                             ) {
165                                 return fixes;
166                             }
167
168                             const openingBrace = sourceCode.getFirstToken(arrowBody);
169                             const closingBrace = sourceCode.getLastToken(arrowBody);
170                             const firstValueToken = sourceCode.getFirstToken(blockBody[0], 1);
171                             const lastValueToken = sourceCode.getLastToken(blockBody[0]);
172                             const commentsExist =
173                                 sourceCode.commentsExistBetween(openingBrace, firstValueToken) ||
174                                 sourceCode.commentsExistBetween(lastValueToken, closingBrace);
175
176                             /*
177                              * Remove tokens around the return value.
178                              * If comments don't exist, remove extra spaces as well.
179                              */
180                             if (commentsExist) {
181                                 fixes.push(
182                                     fixer.remove(openingBrace),
183                                     fixer.remove(closingBrace),
184                                     fixer.remove(sourceCode.getTokenAfter(openingBrace)) // return keyword
185                                 );
186                             } else {
187                                 fixes.push(
188                                     fixer.removeRange([openingBrace.range[0], firstValueToken.range[0]]),
189                                     fixer.removeRange([lastValueToken.range[1], closingBrace.range[1]])
190                                 );
191                             }
192
193                             /*
194                              * If the first token of the return value is `{` or the return value is a sequence expression,
195                              * enclose the return value by parentheses to avoid syntax error.
196                              */
197                             if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression" || (funcInfo.hasInOperator && isInsideForLoopInitializer(node))) {
198                                 if (!astUtils.isParenthesised(sourceCode, blockBody[0].argument)) {
199                                     fixes.push(
200                                         fixer.insertTextBefore(firstValueToken, "("),
201                                         fixer.insertTextAfter(lastValueToken, ")")
202                                     );
203                                 }
204                             }
205
206                             /*
207                              * If the last token of the return statement is semicolon, remove it.
208                              * Non-block arrow body is an expression, not a statement.
209                              */
210                             if (astUtils.isSemicolonToken(lastValueToken)) {
211                                 fixes.push(fixer.remove(lastValueToken));
212                             }
213
214                             return fixes;
215                         }
216                     });
217                 }
218             } else {
219                 if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) {
220                     context.report({
221                         node,
222                         loc: arrowBody.loc,
223                         messageId: "expectedBlock",
224                         fix(fixer) {
225                             const fixes = [];
226                             const arrowToken = sourceCode.getTokenBefore(arrowBody, astUtils.isArrowToken);
227                             const [firstTokenAfterArrow, secondTokenAfterArrow] = sourceCode.getTokensAfter(arrowToken, { count: 2 });
228                             const lastToken = sourceCode.getLastToken(node);
229
230                             let parenthesisedObjectLiteral = null;
231
232                             if (
233                                 astUtils.isOpeningParenToken(firstTokenAfterArrow) &&
234                                 astUtils.isOpeningBraceToken(secondTokenAfterArrow)
235                             ) {
236                                 const braceNode = sourceCode.getNodeByRangeIndex(secondTokenAfterArrow.range[0]);
237
238                                 if (braceNode.type === "ObjectExpression") {
239                                     parenthesisedObjectLiteral = braceNode;
240                                 }
241                             }
242
243                             // If the value is object literal, remove parentheses which were forced by syntax.
244                             if (parenthesisedObjectLiteral) {
245                                 const openingParenToken = firstTokenAfterArrow;
246                                 const openingBraceToken = secondTokenAfterArrow;
247
248                                 if (astUtils.isTokenOnSameLine(openingParenToken, openingBraceToken)) {
249                                     fixes.push(fixer.replaceText(openingParenToken, "{return "));
250                                 } else {
251
252                                     // Avoid ASI
253                                     fixes.push(
254                                         fixer.replaceText(openingParenToken, "{"),
255                                         fixer.insertTextBefore(openingBraceToken, "return ")
256                                     );
257                                 }
258
259                                 // Closing paren for the object doesn't have to be lastToken, e.g.: () => ({}).foo()
260                                 fixes.push(fixer.remove(findClosingParen(parenthesisedObjectLiteral)));
261                                 fixes.push(fixer.insertTextAfter(lastToken, "}"));
262
263                             } else {
264                                 fixes.push(fixer.insertTextBefore(firstTokenAfterArrow, "{return "));
265                                 fixes.push(fixer.insertTextAfter(lastToken, "}"));
266                             }
267
268                             return fixes;
269                         }
270                     });
271                 }
272             }
273         }
274
275         return {
276             "BinaryExpression[operator='in']"() {
277                 let info = funcInfo;
278
279                 while (info) {
280                     info.hasInOperator = true;
281                     info = info.upper;
282                 }
283             },
284             ArrowFunctionExpression() {
285                 funcInfo = {
286                     upper: funcInfo,
287                     hasInOperator: false
288                 };
289             },
290             "ArrowFunctionExpression:exit"(node) {
291                 validate(node);
292                 funcInfo = funcInfo.upper;
293             }
294         };
295     }
296 };