2 * @fileoverview Rule to require braces in arrow function body.
3 * @author Alberto RodrÃguez
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const astUtils = require("./utils/ast-utils");
13 //------------------------------------------------------------------------------
15 //------------------------------------------------------------------------------
22 description: "require braces around arrow function bodies",
23 category: "ECMAScript 6",
25 url: "https://eslint.org/docs/rules/arrow-body-style"
34 enum: ["always", "never"]
49 requireReturnForObjectLiteral: { type: "boolean" }
51 additionalProperties: false
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."
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();
80 * Checks whether the given node has ASI problem or not.
81 * @param {Token} token The token to check.
82 * @returns {boolean} `true` if it changes semantics if `;` or `}` followed by the token are removed.
84 function hasASIProblem(token) {
85 return token && token.type === "Punctuator" && /^[([/`+-]/u.test(token.value);
89 * Gets the closing parenthesis which is the pair of the given opening parenthesis.
90 * @param {Token} token The opening parenthesis token to get.
91 * @returns {Token} The found closing parenthesis token.
93 function findClosingParen(token) {
94 let node = sourceCode.getNodeByRangeIndex(token.range[1]);
96 while (!astUtils.isParenthesised(sourceCode, node)) {
99 return sourceCode.getTokenAfter(node);
103 * Determines whether a arrow function body needs braces
104 * @param {ASTNode} node The arrow function node.
107 function validate(node) {
108 const arrowBody = node.body;
110 if (arrowBody.type === "BlockStatement") {
111 const blockBody = arrowBody.body;
113 if (blockBody.length !== 1 && !never) {
117 if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" &&
118 blockBody[0].argument && blockBody[0].argument.type === "ObjectExpression") {
122 if (never || asNeeded && blockBody[0].type === "ReturnStatement") {
125 if (blockBody.length === 0) {
126 messageId = "unexpectedEmptyBlock";
127 } else if (blockBody.length > 1) {
128 messageId = "unexpectedOtherBlock";
129 } else if (blockBody[0].argument === null) {
130 messageId = "unexpectedSingleBlock";
131 } else if (astUtils.isOpeningBraceToken(sourceCode.getFirstToken(blockBody[0], { skip: 1 }))) {
132 messageId = "unexpectedObjectBlock";
134 messageId = "unexpectedSingleBlock";
139 loc: arrowBody.loc.start,
144 if (blockBody.length !== 1 ||
145 blockBody[0].type !== "ReturnStatement" ||
146 !blockBody[0].argument ||
147 hasASIProblem(sourceCode.getTokenAfter(arrowBody))
152 const openingBrace = sourceCode.getFirstToken(arrowBody);
153 const closingBrace = sourceCode.getLastToken(arrowBody);
154 const firstValueToken = sourceCode.getFirstToken(blockBody[0], 1);
155 const lastValueToken = sourceCode.getLastToken(blockBody[0]);
156 const commentsExist =
157 sourceCode.commentsExistBetween(openingBrace, firstValueToken) ||
158 sourceCode.commentsExistBetween(lastValueToken, closingBrace);
161 * Remove tokens around the return value.
162 * If comments don't exist, remove extra spaces as well.
166 fixer.remove(openingBrace),
167 fixer.remove(closingBrace),
168 fixer.remove(sourceCode.getTokenAfter(openingBrace)) // return keyword
172 fixer.removeRange([openingBrace.range[0], firstValueToken.range[0]]),
173 fixer.removeRange([lastValueToken.range[1], closingBrace.range[1]])
178 * If the first token of the reutrn value is `{` or the return value is a sequence expression,
179 * enclose the return value by parentheses to avoid syntax error.
181 if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression") {
183 fixer.insertTextBefore(firstValueToken, "("),
184 fixer.insertTextAfter(lastValueToken, ")")
189 * If the last token of the return statement is semicolon, remove it.
190 * Non-block arrow body is an expression, not a statement.
192 if (astUtils.isSemicolonToken(lastValueToken)) {
193 fixes.push(fixer.remove(lastValueToken));
201 if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) {
204 loc: arrowBody.loc.start,
205 messageId: "expectedBlock",
208 const arrowToken = sourceCode.getTokenBefore(arrowBody, astUtils.isArrowToken);
209 const firstBodyToken = sourceCode.getTokenAfter(arrowToken);
210 const lastBodyToken = sourceCode.getLastToken(node);
211 const isParenthesisedObjectLiteral =
212 astUtils.isOpeningParenToken(firstBodyToken) &&
213 astUtils.isOpeningBraceToken(sourceCode.getTokenAfter(firstBodyToken));
215 // Wrap the value by a block and a return statement.
217 fixer.insertTextBefore(firstBodyToken, "{return "),
218 fixer.insertTextAfter(lastBodyToken, "}")
221 // If the value is object literal, remove parentheses which were forced by syntax.
222 if (isParenthesisedObjectLiteral) {
224 fixer.remove(firstBodyToken),
225 fixer.remove(findClosingParen(firstBodyToken))
237 "ArrowFunctionExpression:exit": validate