2 * @fileoverview Rule to require parens in arrow function arguments.
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const astUtils = require("./utils/ast-utils");
13 //------------------------------------------------------------------------------
15 //------------------------------------------------------------------------------
18 * Determines if the given arrow function has block body.
19 * @param {ASTNode} node `ArrowFunctionExpression` node.
20 * @returns {boolean} `true` if the function has block body.
22 function hasBlockBody(node) {
23 return node.body.type === "BlockStatement";
26 //------------------------------------------------------------------------------
28 //------------------------------------------------------------------------------
35 description: "require parentheses around arrow function arguments",
36 category: "ECMAScript 6",
38 url: "https://eslint.org/docs/rules/arrow-parens"
45 enum: ["always", "as-needed"]
50 requireForBlockBody: {
55 additionalProperties: false
60 unexpectedParens: "Unexpected parentheses around single function argument.",
61 expectedParens: "Expected parentheses around arrow function argument.",
63 unexpectedParensInline: "Unexpected parentheses around single function argument having a body with no curly braces.",
64 expectedParensBlock: "Expected parentheses around arrow function argument having a body with curly braces."
69 const asNeeded = context.options[0] === "as-needed";
70 const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true;
72 const sourceCode = context.getSourceCode();
75 * Finds opening paren of parameters for the given arrow function, if it exists.
76 * It is assumed that the given arrow function has exactly one parameter.
77 * @param {ASTNode} node `ArrowFunctionExpression` node.
78 * @returns {Token|null} the opening paren, or `null` if the given arrow function doesn't have parens of parameters.
80 function findOpeningParenOfParams(node) {
81 const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]);
85 astUtils.isOpeningParenToken(tokenBeforeParams) &&
86 node.range[0] <= tokenBeforeParams.range[0]
88 return tokenBeforeParams;
95 * Finds closing paren of parameters for the given arrow function.
96 * It is assumed that the given arrow function has parens of parameters and that it has exactly one parameter.
97 * @param {ASTNode} node `ArrowFunctionExpression` node.
98 * @returns {Token} the closing paren of parameters.
100 function getClosingParenOfParams(node) {
101 return sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
105 * Determines whether the given arrow function has comments inside parens of parameters.
106 * It is assumed that the given arrow function has parens of parameters.
107 * @param {ASTNode} node `ArrowFunctionExpression` node.
108 * @param {Token} openingParen Opening paren of parameters.
109 * @returns {boolean} `true` if the function has at least one comment inside of parens of parameters.
111 function hasCommentsInParensOfParams(node, openingParen) {
112 return sourceCode.commentsExistBetween(openingParen, getClosingParenOfParams(node));
116 * Determines whether the given arrow function has unexpected tokens before opening paren of parameters,
117 * in which case it will be assumed that the existing parens of parameters are necessary.
118 * Only tokens within the range of the arrow function (tokens that are part of the arrow function) are taken into account.
119 * Example: <T>(a) => b
120 * @param {ASTNode} node `ArrowFunctionExpression` node.
121 * @param {Token} openingParen Opening paren of parameters.
122 * @returns {boolean} `true` if the function has at least one unexpected token.
124 function hasUnexpectedTokensBeforeOpeningParen(node, openingParen) {
125 const expectedCount = node.async ? 1 : 0;
127 return sourceCode.getFirstToken(node, { skip: expectedCount }) !== openingParen;
131 "ArrowFunctionExpression[params.length=1]"(node) {
132 const shouldHaveParens = !asNeeded || requireForBlockBody && hasBlockBody(node);
133 const openingParen = findOpeningParenOfParams(node);
134 const hasParens = openingParen !== null;
135 const [param] = node.params;
137 if (shouldHaveParens && !hasParens) {
140 messageId: requireForBlockBody ? "expectedParensBlock" : "expectedParens",
143 yield fixer.insertTextBefore(param, "(");
144 yield fixer.insertTextAfter(param, ")");
152 param.type === "Identifier" &&
153 !param.typeAnnotation &&
155 !hasCommentsInParensOfParams(node, openingParen) &&
156 !hasUnexpectedTokensBeforeOpeningParen(node, openingParen)
160 messageId: requireForBlockBody ? "unexpectedParensInline" : "unexpectedParens",
163 const tokenBeforeOpeningParen = sourceCode.getTokenBefore(openingParen);
164 const closingParen = getClosingParenOfParams(node);
167 tokenBeforeOpeningParen &&
168 tokenBeforeOpeningParen.range[1] === openingParen.range[0] &&
169 !astUtils.canTokensBeAdjacent(tokenBeforeOpeningParen, sourceCode.getFirstToken(param))
171 yield fixer.insertTextBefore(openingParen, " ");
174 // remove parens, whitespace inside parens, and possible trailing comma
175 yield fixer.removeRange([openingParen.range[0], param.range[0]]);
176 yield fixer.removeRange([param.range[1], closingParen.range[1]]);