1/** 2 * @fileoverview Rule to require parens in arrow function arguments. 3 * @author Jxck 4 */ 5"use strict"; 6 7//------------------------------------------------------------------------------ 8// Requirements 9//------------------------------------------------------------------------------ 10 11const astUtils = require("./utils/ast-utils"); 12 13//------------------------------------------------------------------------------ 14// Helpers 15//------------------------------------------------------------------------------ 16 17/** 18 * Get location should be reported by AST node. 19 * @param {ASTNode} node AST Node. 20 * @returns {Location} Location information. 21 */ 22function getLocation(node) { 23 return { 24 start: node.params[0].loc.start, 25 end: node.params[node.params.length - 1].loc.end 26 }; 27} 28 29//------------------------------------------------------------------------------ 30// Rule Definition 31//------------------------------------------------------------------------------ 32 33module.exports = { 34 meta: { 35 type: "layout", 36 37 docs: { 38 description: "require parentheses around arrow function arguments", 39 category: "ECMAScript 6", 40 recommended: false, 41 url: "https://eslint.org/docs/rules/arrow-parens" 42 }, 43 44 fixable: "code", 45 46 schema: [ 47 { 48 enum: ["always", "as-needed"] 49 }, 50 { 51 type: "object", 52 properties: { 53 requireForBlockBody: { 54 type: "boolean", 55 default: false 56 } 57 }, 58 additionalProperties: false 59 } 60 ], 61 62 messages: { 63 unexpectedParens: "Unexpected parentheses around single function argument.", 64 expectedParens: "Expected parentheses around arrow function argument.", 65 66 unexpectedParensInline: "Unexpected parentheses around single function argument having a body with no curly braces.", 67 expectedParensBlock: "Expected parentheses around arrow function argument having a body with curly braces." 68 } 69 }, 70 71 create(context) { 72 const asNeeded = context.options[0] === "as-needed"; 73 const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true; 74 75 const sourceCode = context.getSourceCode(); 76 77 /** 78 * Determines whether a arrow function argument end with `)` 79 * @param {ASTNode} node The arrow function node. 80 * @returns {void} 81 */ 82 function parens(node) { 83 const isAsync = node.async; 84 const firstTokenOfParam = sourceCode.getFirstToken(node, isAsync ? 1 : 0); 85 86 /** 87 * Remove the parenthesis around a parameter 88 * @param {Fixer} fixer Fixer 89 * @returns {string} fixed parameter 90 */ 91 function fixParamsWithParenthesis(fixer) { 92 const paramToken = sourceCode.getTokenAfter(firstTokenOfParam); 93 94 /* 95 * ES8 allows Trailing commas in function parameter lists and calls 96 * https://github.com/eslint/eslint/issues/8834 97 */ 98 const closingParenToken = sourceCode.getTokenAfter(paramToken, astUtils.isClosingParenToken); 99 const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null; 100 const shouldAddSpaceForAsync = asyncToken && (asyncToken.range[1] === firstTokenOfParam.range[0]); 101 102 return fixer.replaceTextRange([ 103 firstTokenOfParam.range[0], 104 closingParenToken.range[1] 105 ], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`); 106 } 107 108 /** 109 * Checks whether there are comments inside the params or not. 110 * @returns {boolean} `true` if there are comments inside of parens, else `false` 111 */ 112 function hasCommentsInParens() { 113 if (astUtils.isOpeningParenToken(firstTokenOfParam)) { 114 const closingParenToken = sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken); 115 116 return closingParenToken && sourceCode.commentsExistBetween(firstTokenOfParam, closingParenToken); 117 } 118 return false; 119 120 } 121 122 if (hasCommentsInParens()) { 123 return; 124 } 125 126 // "as-needed", { "requireForBlockBody": true }: x => x 127 if ( 128 requireForBlockBody && 129 node.params[0].type === "Identifier" && 130 !node.params[0].typeAnnotation && 131 node.body.type !== "BlockStatement" && 132 !node.returnType 133 ) { 134 if (astUtils.isOpeningParenToken(firstTokenOfParam)) { 135 context.report({ 136 node, 137 messageId: "unexpectedParensInline", 138 loc: getLocation(node), 139 fix: fixParamsWithParenthesis 140 }); 141 } 142 return; 143 } 144 145 if ( 146 requireForBlockBody && 147 node.body.type === "BlockStatement" 148 ) { 149 if (!astUtils.isOpeningParenToken(firstTokenOfParam)) { 150 context.report({ 151 node, 152 messageId: "expectedParensBlock", 153 loc: getLocation(node), 154 fix(fixer) { 155 return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`); 156 } 157 }); 158 } 159 return; 160 } 161 162 // "as-needed": x => x 163 if (asNeeded && 164 node.params[0].type === "Identifier" && 165 !node.params[0].typeAnnotation && 166 !node.returnType 167 ) { 168 if (astUtils.isOpeningParenToken(firstTokenOfParam)) { 169 context.report({ 170 node, 171 messageId: "unexpectedParens", 172 loc: getLocation(node), 173 fix: fixParamsWithParenthesis 174 }); 175 } 176 return; 177 } 178 179 if (firstTokenOfParam.type === "Identifier") { 180 const after = sourceCode.getTokenAfter(firstTokenOfParam); 181 182 // (x) => x 183 if (after.value !== ")") { 184 context.report({ 185 node, 186 messageId: "expectedParens", 187 loc: getLocation(node), 188 fix(fixer) { 189 return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`); 190 } 191 }); 192 } 193 } 194 } 195 196 return { 197 "ArrowFunctionExpression[params.length=1]": parens 198 }; 199 } 200}; 201