/** * @fileoverview Rule to require parens in arrow function arguments. * @author Jxck */ "use strict"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** * Get location should be reported by AST node. * @param {ASTNode} node AST Node. * @returns {Location} Location information. */ function getLocation(node) { return { start: node.params[0].loc.start, end: node.params[node.params.length - 1].loc.end }; } //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ module.exports = { meta: { type: "layout", docs: { description: "require parentheses around arrow function arguments", category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/arrow-parens" }, fixable: "code", schema: [ { enum: ["always", "as-needed"] }, { type: "object", properties: { requireForBlockBody: { type: "boolean", default: false } }, additionalProperties: false } ], messages: { unexpectedParens: "Unexpected parentheses around single function argument.", expectedParens: "Expected parentheses around arrow function argument.", unexpectedParensInline: "Unexpected parentheses around single function argument having a body with no curly braces.", expectedParensBlock: "Expected parentheses around arrow function argument having a body with curly braces." } }, create(context) { const asNeeded = context.options[0] === "as-needed"; const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true; const sourceCode = context.getSourceCode(); /** * Determines whether a arrow function argument end with `)` * @param {ASTNode} node The arrow function node. * @returns {void} */ function parens(node) { const isAsync = node.async; const firstTokenOfParam = sourceCode.getFirstToken(node, isAsync ? 1 : 0); /** * Remove the parenthesis around a parameter * @param {Fixer} fixer Fixer * @returns {string} fixed parameter */ function fixParamsWithParenthesis(fixer) { const paramToken = sourceCode.getTokenAfter(firstTokenOfParam); /* * ES8 allows Trailing commas in function parameter lists and calls * https://github.com/eslint/eslint/issues/8834 */ const closingParenToken = sourceCode.getTokenAfter(paramToken, astUtils.isClosingParenToken); const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null; const shouldAddSpaceForAsync = asyncToken && (asyncToken.range[1] === firstTokenOfParam.range[0]); return fixer.replaceTextRange([ firstTokenOfParam.range[0], closingParenToken.range[1] ], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`); } /** * Checks whether there are comments inside the params or not. * @returns {boolean} `true` if there are comments inside of parens, else `false` */ function hasCommentsInParens() { if (astUtils.isOpeningParenToken(firstTokenOfParam)) { const closingParenToken = sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken); return closingParenToken && sourceCode.commentsExistBetween(firstTokenOfParam, closingParenToken); } return false; } if (hasCommentsInParens()) { return; } // "as-needed", { "requireForBlockBody": true }: x => x if ( requireForBlockBody && node.params[0].type === "Identifier" && !node.params[0].typeAnnotation && node.body.type !== "BlockStatement" && !node.returnType ) { if (astUtils.isOpeningParenToken(firstTokenOfParam)) { context.report({ node, messageId: "unexpectedParensInline", loc: getLocation(node), fix: fixParamsWithParenthesis }); } return; } if ( requireForBlockBody && node.body.type === "BlockStatement" ) { if (!astUtils.isOpeningParenToken(firstTokenOfParam)) { context.report({ node, messageId: "expectedParensBlock", loc: getLocation(node), fix(fixer) { return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`); } }); } return; } // "as-needed": x => x if (asNeeded && node.params[0].type === "Identifier" && !node.params[0].typeAnnotation && !node.returnType ) { if (astUtils.isOpeningParenToken(firstTokenOfParam)) { context.report({ node, messageId: "unexpectedParens", loc: getLocation(node), fix: fixParamsWithParenthesis }); } return; } if (firstTokenOfParam.type === "Identifier") { const after = sourceCode.getTokenAfter(firstTokenOfParam); // (x) => x if (after.value !== ")") { context.report({ node, messageId: "expectedParens", loc: getLocation(node), fix(fixer) { return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`); } }); } } } return { "ArrowFunctionExpression[params.length=1]": parens }; } };