]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
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) { | |
6f036462 TL |
60 | if (node.computed) { |
61 | if (node.optional) { | |
62 | return "?.["; | |
63 | } | |
64 | return "["; | |
65 | } | |
66 | if (node.optional) { | |
67 | return "?."; | |
68 | } | |
69 | return "."; | |
eb39fafa DC |
70 | } |
71 | ||
72 | /** | |
73 | * Gets the property text of a given MemberExpression node. | |
74 | * If the text is multiline, this returns only the first line. | |
75 | * @param {ASTNode} node A MemberExpression node to get. | |
76 | * @returns {string} The property text of the node. | |
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) { | |
6f036462 TL |
88 | const callee = astUtils.skipChainExpression(node.callee); |
89 | ||
90 | if (callee.type !== "MemberExpression") { | |
eb39fafa DC |
91 | return; |
92 | } | |
93 | ||
6f036462 | 94 | let parent = astUtils.skipChainExpression(callee.object); |
eb39fafa DC |
95 | let depth = 1; |
96 | ||
97 | while (parent && parent.callee) { | |
98 | depth += 1; | |
6f036462 | 99 | parent = astUtils.skipChainExpression(astUtils.skipChainExpression(parent.callee).object); |
eb39fafa DC |
100 | } |
101 | ||
102 | if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) { | |
56c4a2cb DC |
103 | const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken); |
104 | ||
eb39fafa DC |
105 | context.report({ |
106 | node: callee.property, | |
56c4a2cb DC |
107 | loc: { |
108 | start: firstTokenAfterObject.loc.start, | |
109 | end: callee.loc.end | |
110 | }, | |
eb39fafa DC |
111 | messageId: "expected", |
112 | data: { | |
113 | callee: getPropertyText(callee) | |
114 | }, | |
115 | fix(fixer) { | |
eb39fafa DC |
116 | return fixer.insertTextBefore(firstTokenAfterObject, "\n"); |
117 | } | |
118 | }); | |
119 | } | |
120 | } | |
121 | }; | |
122 | } | |
123 | }; |