• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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