| /** |
| * @fileoverview Rule to require braces in arrow function body. |
| * @author Alberto RodrÃguez |
| */ |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Requirements |
| //------------------------------------------------------------------------------ |
| |
| const astUtils = require("./utils/ast-utils"); |
| |
| //------------------------------------------------------------------------------ |
| // Rule Definition |
| //------------------------------------------------------------------------------ |
| |
| module.exports = { |
| meta: { |
| type: "suggestion", |
| |
| docs: { |
| description: "require braces around arrow function bodies", |
| category: "ECMAScript 6", |
| recommended: false, |
| url: "https://eslint.org/docs/rules/arrow-body-style" |
| }, |
| |
| schema: { |
| anyOf: [ |
| { |
| type: "array", |
| items: [ |
| { |
| enum: ["always", "never"] |
| } |
| ], |
| minItems: 0, |
| maxItems: 1 |
| }, |
| { |
| type: "array", |
| items: [ |
| { |
| enum: ["as-needed"] |
| }, |
| { |
| type: "object", |
| properties: { |
| requireReturnForObjectLiteral: { type: "boolean" } |
| }, |
| additionalProperties: false |
| } |
| ], |
| minItems: 0, |
| maxItems: 2 |
| } |
| ] |
| }, |
| |
| fixable: "code", |
| |
| messages: { |
| unexpectedOtherBlock: "Unexpected block statement surrounding arrow body.", |
| unexpectedEmptyBlock: "Unexpected block statement surrounding arrow body; put a value of `undefined` immediately after the `=>`.", |
| unexpectedObjectBlock: "Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the `=>`.", |
| unexpectedSingleBlock: "Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`.", |
| expectedBlock: "Expected block statement surrounding arrow body." |
| } |
| }, |
| |
| create(context) { |
| const options = context.options; |
| const always = options[0] === "always"; |
| const asNeeded = !options[0] || options[0] === "as-needed"; |
| const never = options[0] === "never"; |
| const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral; |
| const sourceCode = context.getSourceCode(); |
| |
| /** |
| * Checks whether the given node has ASI problem or not. |
| * @param {Token} token The token to check. |
| * @returns {boolean} `true` if it changes semantics if `;` or `}` followed by the token are removed. |
| */ |
| function hasASIProblem(token) { |
| return token && token.type === "Punctuator" && /^[([/`+-]/u.test(token.value); |
| } |
| |
| /** |
| * Gets the closing parenthesis which is the pair of the given opening parenthesis. |
| * @param {Token} token The opening parenthesis token to get. |
| * @returns {Token} The found closing parenthesis token. |
| */ |
| function findClosingParen(token) { |
| let node = sourceCode.getNodeByRangeIndex(token.range[1]); |
| |
| while (!astUtils.isParenthesised(sourceCode, node)) { |
| node = node.parent; |
| } |
| return sourceCode.getTokenAfter(node); |
| } |
| |
| /** |
| * Determines whether a arrow function body needs braces |
| * @param {ASTNode} node The arrow function node. |
| * @returns {void} |
| */ |
| function validate(node) { |
| const arrowBody = node.body; |
| |
| if (arrowBody.type === "BlockStatement") { |
| const blockBody = arrowBody.body; |
| |
| if (blockBody.length !== 1 && !never) { |
| return; |
| } |
| |
| if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" && |
| blockBody[0].argument && blockBody[0].argument.type === "ObjectExpression") { |
| return; |
| } |
| |
| if (never || asNeeded && blockBody[0].type === "ReturnStatement") { |
| let messageId; |
| |
| if (blockBody.length === 0) { |
| messageId = "unexpectedEmptyBlock"; |
| } else if (blockBody.length > 1) { |
| messageId = "unexpectedOtherBlock"; |
| } else if (blockBody[0].argument === null) { |
| messageId = "unexpectedSingleBlock"; |
| } else if (astUtils.isOpeningBraceToken(sourceCode.getFirstToken(blockBody[0], { skip: 1 }))) { |
| messageId = "unexpectedObjectBlock"; |
| } else { |
| messageId = "unexpectedSingleBlock"; |
| } |
| |
| context.report({ |
| node, |
| loc: arrowBody.loc.start, |
| messageId, |
| fix(fixer) { |
| const fixes = []; |
| |
| if (blockBody.length !== 1 || |
| blockBody[0].type !== "ReturnStatement" || |
| !blockBody[0].argument || |
| hasASIProblem(sourceCode.getTokenAfter(arrowBody)) |
| ) { |
| return fixes; |
| } |
| |
| const openingBrace = sourceCode.getFirstToken(arrowBody); |
| const closingBrace = sourceCode.getLastToken(arrowBody); |
| const firstValueToken = sourceCode.getFirstToken(blockBody[0], 1); |
| const lastValueToken = sourceCode.getLastToken(blockBody[0]); |
| const commentsExist = |
| sourceCode.commentsExistBetween(openingBrace, firstValueToken) || |
| sourceCode.commentsExistBetween(lastValueToken, closingBrace); |
| |
| /* |
| * Remove tokens around the return value. |
| * If comments don't exist, remove extra spaces as well. |
| */ |
| if (commentsExist) { |
| fixes.push( |
| fixer.remove(openingBrace), |
| fixer.remove(closingBrace), |
| fixer.remove(sourceCode.getTokenAfter(openingBrace)) // return keyword |
| ); |
| } else { |
| fixes.push( |
| fixer.removeRange([openingBrace.range[0], firstValueToken.range[0]]), |
| fixer.removeRange([lastValueToken.range[1], closingBrace.range[1]]) |
| ); |
| } |
| |
| /* |
| * If the first token of the reutrn value is `{`, |
| * enclose the return value by parentheses to avoid syntax error. |
| */ |
| if (astUtils.isOpeningBraceToken(firstValueToken)) { |
| fixes.push( |
| fixer.insertTextBefore(firstValueToken, "("), |
| fixer.insertTextAfter(lastValueToken, ")") |
| ); |
| } |
| |
| /* |
| * If the last token of the return statement is semicolon, remove it. |
| * Non-block arrow body is an expression, not a statement. |
| */ |
| if (astUtils.isSemicolonToken(lastValueToken)) { |
| fixes.push(fixer.remove(lastValueToken)); |
| } |
| |
| return fixes; |
| } |
| }); |
| } |
| } else { |
| if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) { |
| context.report({ |
| node, |
| loc: arrowBody.loc.start, |
| messageId: "expectedBlock", |
| fix(fixer) { |
| const fixes = []; |
| const arrowToken = sourceCode.getTokenBefore(arrowBody, astUtils.isArrowToken); |
| const firstBodyToken = sourceCode.getTokenAfter(arrowToken); |
| const lastBodyToken = sourceCode.getLastToken(node); |
| const isParenthesisedObjectLiteral = |
| astUtils.isOpeningParenToken(firstBodyToken) && |
| astUtils.isOpeningBraceToken(sourceCode.getTokenAfter(firstBodyToken)); |
| |
| // Wrap the value by a block and a return statement. |
| fixes.push( |
| fixer.insertTextBefore(firstBodyToken, "{return "), |
| fixer.insertTextAfter(lastBodyToken, "}") |
| ); |
| |
| // If the value is object literal, remove parentheses which were forced by syntax. |
| if (isParenthesisedObjectLiteral) { |
| fixes.push( |
| fixer.remove(firstBodyToken), |
| fixer.remove(findClosingParen(firstBodyToken)) |
| ); |
| } |
| |
| return fixes; |
| } |
| }); |
| } |
| } |
| } |
| |
| return { |
| "ArrowFunctionExpression:exit": validate |
| }; |
| } |
| }; |