1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | "use strict";
|
8 |
|
9 | const astUtils = require("./utils/ast-utils");
|
10 |
|
11 |
|
12 |
|
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 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | function getPrefix(node) {
|
60 | if (node.computed) {
|
61 | if (node.optional) {
|
62 | return "?.[";
|
63 | }
|
64 | return "[";
|
65 | }
|
66 | if (node.optional) {
|
67 | return "?.";
|
68 | }
|
69 | return ".";
|
70 | }
|
71 |
|
72 | |
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | function getPropertyText(node) {
|
79 | const prefix = getPrefix(node);
|
80 | const lines = sourceCode.getText(node.property).split(astUtils.LINEBREAK_MATCHER);
|
81 | const suffix = node.computed && lines.length === 1 ? "]" : "";
|
82 |
|
83 | return prefix + lines[0] + suffix;
|
84 | }
|
85 |
|
86 | return {
|
87 | "CallExpression:exit"(node) {
|
88 | const callee = astUtils.skipChainExpression(node.callee);
|
89 |
|
90 | if (callee.type !== "MemberExpression") {
|
91 | return;
|
92 | }
|
93 |
|
94 | let parent = astUtils.skipChainExpression(callee.object);
|
95 | let depth = 1;
|
96 |
|
97 | while (parent && parent.callee) {
|
98 | depth += 1;
|
99 | parent = astUtils.skipChainExpression(astUtils.skipChainExpression(parent.callee).object);
|
100 | }
|
101 |
|
102 | if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
|
103 | const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken);
|
104 |
|
105 | context.report({
|
106 | node: callee.property,
|
107 | loc: {
|
108 | start: firstTokenAfterObject.loc.start,
|
109 | end: callee.loc.end
|
110 | },
|
111 | messageId: "expected",
|
112 | data: {
|
113 | callee: getPropertyText(callee)
|
114 | },
|
115 | fix(fixer) {
|
116 | return fixer.insertTextBefore(firstTokenAfterObject, "\n");
|
117 | }
|
118 | });
|
119 | }
|
120 | }
|
121 | };
|
122 | }
|
123 | };
|