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 | ;
|
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 | *
|
57 | * @param {ASTNode} node - A MemberExpression node to get
|
58 | * @returns {string} The prefix of the node.
|
59 | */
|
60 | function getPrefix(node) {
|
61 | return node.computed ? "[" : ".";
|
62 | }
|
63 |
|
64 | /**
|
65 | * Gets the property text of a given MemberExpression node.
|
66 | * If the text is multiline, this returns only the first line.
|
67 | *
|
68 | * @param {ASTNode} node - A MemberExpression node to get.
|
69 | * @returns {string} The property text of the node.
|
70 | */
|
71 | function getPropertyText(node) {
|
72 | const prefix = getPrefix(node);
|
73 | const lines = sourceCode.getText(node.property).split(astUtils.LINEBREAK_MATCHER);
|
74 | const suffix = node.computed && lines.length === 1 ? "]" : "";
|
75 |
|
76 | return prefix + lines[0] + suffix;
|
77 | }
|
78 |
|
79 | return {
|
80 | "CallExpression:exit"(node) {
|
81 | if (!node.callee || node.callee.type !== "MemberExpression") {
|
82 | return;
|
83 | }
|
84 |
|
85 | const callee = node.callee;
|
86 | let parent = callee.object;
|
87 | let depth = 1;
|
88 |
|
89 | while (parent && parent.callee) {
|
90 | depth += 1;
|
91 | parent = parent.callee.object;
|
92 | }
|
93 |
|
94 | if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
|
95 | context.report({
|
96 | node: callee.property,
|
97 | loc: callee.property.loc.start,
|
98 | messageId: "expected",
|
99 | data: {
|
100 | callee: getPropertyText(callee)
|
101 | },
|
102 | fix(fixer) {
|
103 | const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken);
|
104 |
|
105 | return fixer.insertTextBefore(firstTokenAfterObject, "\n");
|
106 | }
|
107 | });
|
108 | }
|
109 | }
|
110 | };
|
111 | }
|
112 | };
|