UNPKG

7.7 kBJavaScriptView Raw
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 * 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.
21 */
22function hasBlockBody(node) {
23 return node.body.type === "BlockStatement";
24}
25
26//------------------------------------------------------------------------------
27// Rule Definition
28//------------------------------------------------------------------------------
29
30module.exports = {
31 meta: {
32 type: "layout",
33
34 docs: {
35 description: "require parentheses around arrow function arguments",
36 category: "ECMAScript 6",
37 recommended: false,
38 url: "https://eslint.org/docs/rules/arrow-parens"
39 },
40
41 fixable: "code",
42
43 schema: [
44 {
45 enum: ["always", "as-needed"]
46 },
47 {
48 type: "object",
49 properties: {
50 requireForBlockBody: {
51 type: "boolean",
52 default: false
53 }
54 },
55 additionalProperties: false
56 }
57 ],
58
59 messages: {
60 unexpectedParens: "Unexpected parentheses around single function argument.",
61 expectedParens: "Expected parentheses around arrow function argument.",
62
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."
65 }
66 },
67
68 create(context) {
69 const asNeeded = context.options[0] === "as-needed";
70 const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true;
71
72 const sourceCode = context.getSourceCode();
73
74 /**
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.
79 */
80 function findOpeningParenOfParams(node) {
81 const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]);
82
83 if (
84 tokenBeforeParams &&
85 astUtils.isOpeningParenToken(tokenBeforeParams) &&
86 node.range[0] <= tokenBeforeParams.range[0]
87 ) {
88 return tokenBeforeParams;
89 }
90
91 return null;
92 }
93
94 /**
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.
99 */
100 function getClosingParenOfParams(node) {
101 return sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
102 }
103
104 /**
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.
110 */
111 function hasCommentsInParensOfParams(node, openingParen) {
112 return sourceCode.commentsExistBetween(openingParen, getClosingParenOfParams(node));
113 }
114
115 /**
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.
123 */
124 function hasUnexpectedTokensBeforeOpeningParen(node, openingParen) {
125 const expectedCount = node.async ? 1 : 0;
126
127 return sourceCode.getFirstToken(node, { skip: expectedCount }) !== openingParen;
128 }
129
130 return {
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;
136
137 if (shouldHaveParens && !hasParens) {
138 context.report({
139 node,
140 messageId: requireForBlockBody ? "expectedParensBlock" : "expectedParens",
141 loc: param.loc,
142 *fix(fixer) {
143 yield fixer.insertTextBefore(param, "(");
144 yield fixer.insertTextAfter(param, ")");
145 }
146 });
147 }
148
149 if (
150 !shouldHaveParens &&
151 hasParens &&
152 param.type === "Identifier" &&
153 !param.typeAnnotation &&
154 !node.returnType &&
155 !hasCommentsInParensOfParams(node, openingParen) &&
156 !hasUnexpectedTokensBeforeOpeningParen(node, openingParen)
157 ) {
158 context.report({
159 node,
160 messageId: requireForBlockBody ? "unexpectedParensInline" : "unexpectedParens",
161 loc: param.loc,
162 *fix(fixer) {
163 const tokenBeforeOpeningParen = sourceCode.getTokenBefore(openingParen);
164 const closingParen = getClosingParenOfParams(node);
165
166 if (
167 tokenBeforeOpeningParen &&
168 tokenBeforeOpeningParen.range[1] === openingParen.range[0] &&
169 !astUtils.canTokensBeAdjacent(tokenBeforeOpeningParen, sourceCode.getFirstToken(param))
170 ) {
171 yield fixer.insertTextBefore(openingParen, " ");
172 }
173
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]]);
177 }
178 });
179 }
180 }
181 };
182 }
183};