8ad88386c0f61db3030bb00dec3c9012c766362e
[dotfiles/.git] / newline-per-chained-call.js
1 /**
2  * @fileoverview Rule to ensure newline per method call when chaining calls
3  * @author Rajendra Patil
4  * @author Burak Yigit Kaya
5  */
6
7 "use strict";
8
9 const astUtils = require("./utils/ast-utils");
10
11 //------------------------------------------------------------------------------
12 // Rule Definition
13 //------------------------------------------------------------------------------
14
15 module.exports = {
16     meta: {
17         type: "layout",
18
19         docs: {
20             description: "require a newline after each call in a method chain",
21             category: "Stylistic Issues",
22             recommended: false,
23             url: "https://eslint.org/docs/rules/newline-per-chained-call"
24         },
25
26         fixable: "whitespace",
27
28         schema: [{
29             type: "object",
30             properties: {
31                 ignoreChainWithDepth: {
32                     type: "integer",
33                     minimum: 1,
34                     maximum: 10,
35                     default: 2
36                 }
37             },
38             additionalProperties: false
39         }],
40         messages: {
41             expected: "Expected line break before `{{callee}}`."
42         }
43     },
44
45     create(context) {
46
47         const options = context.options[0] || {},
48             ignoreChainWithDepth = options.ignoreChainWithDepth || 2;
49
50         const sourceCode = context.getSourceCode();
51
52         /**
53          * Get the prefix of a given MemberExpression node.
54          * If the MemberExpression node is a computed value it returns a
55          * left bracket. If not it returns a period.
56          * @param  {ASTNode} node A MemberExpression node to get
57          * @returns {string} The prefix of the node.
58          */
59         function getPrefix(node) {
60             return node.computed ? "[" : ".";
61         }
62
63         /**
64          * Gets the property text of a given MemberExpression node.
65          * If the text is multiline, this returns only the first line.
66          * @param {ASTNode} node A MemberExpression node to get.
67          * @returns {string} The property text of the node.
68          */
69         function getPropertyText(node) {
70             const prefix = getPrefix(node);
71             const lines = sourceCode.getText(node.property).split(astUtils.LINEBREAK_MATCHER);
72             const suffix = node.computed && lines.length === 1 ? "]" : "";
73
74             return prefix + lines[0] + suffix;
75         }
76
77         return {
78             "CallExpression:exit"(node) {
79                 if (!node.callee || node.callee.type !== "MemberExpression") {
80                     return;
81                 }
82
83                 const callee = node.callee;
84                 let parent = callee.object;
85                 let depth = 1;
86
87                 while (parent && parent.callee) {
88                     depth += 1;
89                     parent = parent.callee.object;
90                 }
91
92                 if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
93                     context.report({
94                         node: callee.property,
95                         loc: callee.property.loc.start,
96                         messageId: "expected",
97                         data: {
98                             callee: getPropertyText(callee)
99                         },
100                         fix(fixer) {
101                             const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken);
102
103                             return fixer.insertTextBefore(firstTokenAfterObject, "\n");
104                         }
105                     });
106                 }
107             }
108         };
109     }
110 };