• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Rule to enforce line breaks between arguments of a function call
3 * @author Alexey Gonchar <https://github.com/finico>
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Rule Definition
10//------------------------------------------------------------------------------
11
12module.exports = {
13    meta: {
14        type: "layout",
15
16        docs: {
17            description: "enforce line breaks between arguments of a function call",
18            category: "Stylistic Issues",
19            recommended: false,
20            url: "https://eslint.org/docs/rules/function-call-argument-newline"
21        },
22
23        fixable: "whitespace",
24
25        schema: [
26            {
27                enum: ["always", "never", "consistent"]
28            }
29        ],
30
31        messages: {
32            unexpectedLineBreak: "There should be no line break here.",
33            missingLineBreak: "There should be a line break after this argument."
34        }
35    },
36
37    create(context) {
38        const sourceCode = context.getSourceCode();
39
40        const checkers = {
41            unexpected: {
42                messageId: "unexpectedLineBreak",
43                check: (prevToken, currentToken) => prevToken.loc.end.line !== currentToken.loc.start.line,
44                createFix: (token, tokenBefore) => fixer =>
45                    fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ")
46            },
47            missing: {
48                messageId: "missingLineBreak",
49                check: (prevToken, currentToken) => prevToken.loc.end.line === currentToken.loc.start.line,
50                createFix: (token, tokenBefore) => fixer =>
51                    fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n")
52            }
53        };
54
55        /**
56         * Check all arguments for line breaks in the CallExpression
57         * @param {CallExpression} node node to evaluate
58         * @param {{ messageId: string, check: Function }} checker selected checker
59         * @returns {void}
60         * @private
61         */
62        function checkArguments(node, checker) {
63            for (let i = 1; i < node.arguments.length; i++) {
64                const prevArgToken = sourceCode.getLastToken(node.arguments[i - 1]);
65                const currentArgToken = sourceCode.getFirstToken(node.arguments[i]);
66
67                if (checker.check(prevArgToken, currentArgToken)) {
68                    const tokenBefore = sourceCode.getTokenBefore(
69                        currentArgToken,
70                        { includeComments: true }
71                    );
72
73                    const hasLineCommentBefore = tokenBefore.type === "Line";
74
75                    context.report({
76                        node,
77                        loc: {
78                            start: tokenBefore.loc.end,
79                            end: currentArgToken.loc.start
80                        },
81                        messageId: checker.messageId,
82                        fix: hasLineCommentBefore ? null : checker.createFix(currentArgToken, tokenBefore)
83                    });
84                }
85            }
86        }
87
88        /**
89         * Check if open space is present in a function name
90         * @param {CallExpression} node node to evaluate
91         * @returns {void}
92         * @private
93         */
94        function check(node) {
95            if (node.arguments.length < 2) {
96                return;
97            }
98
99            const option = context.options[0] || "always";
100
101            if (option === "never") {
102                checkArguments(node, checkers.unexpected);
103            } else if (option === "always") {
104                checkArguments(node, checkers.missing);
105            } else if (option === "consistent") {
106                const firstArgToken = sourceCode.getLastToken(node.arguments[0]);
107                const secondArgToken = sourceCode.getFirstToken(node.arguments[1]);
108
109                if (firstArgToken.loc.end.line === secondArgToken.loc.start.line) {
110                    checkArguments(node, checkers.unexpected);
111                } else {
112                    checkArguments(node, checkers.missing);
113                }
114            }
115        }
116
117        return {
118            CallExpression: check,
119            NewExpression: check
120        };
121    }
122};
123