Skip to content

Commit db5eefc

Browse files
authored
Fix min-chained-call-depth rule (#134)
1 parent 1c13dbd commit db5eefc

File tree

6 files changed

+111
-30
lines changed

6 files changed

+111
-30
lines changed

docs/rules/min-chained-call-depth.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,27 @@ expect(screen.getElementById("very-long-identifier"))
7474
.toBe(true);
7575
```
7676

77+
### `ignoreChainDeeperThan`
78+
79+
Chains that are deeper than the specified number are allowed to break line, default is 2.
80+
81+
#### ❌ Incorrect
82+
83+
```jsx
84+
Array(10)
85+
.fill(0)
86+
.map(foo => foo);
87+
```
88+
89+
#### ✅ Correct
90+
91+
```jsx
92+
/* eslint min-chained-call-depth: ["error", {"ignoreChainDeeperThan": 1}] */
93+
Array(10)
94+
.fill(0)
95+
.map(foo => foo);
96+
```
97+
7798
## Attributes
7899

79100
- [ ] ✅ Recommended

docs/rules/newline-per-chained-call.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ foo.bar.baz.qux;
6767

6868
These are the available options:
6969

70-
### `ignoreChainWithDepth`
70+
### `ignoreChainDeeperThan`
7171

7272
Specifies the maximum depth of chained allowed, default is 2.
7373

src/rules/min-chained-call-depth/index.test.ts

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ const ruleTester = new ESLintUtils.RuleTester({
1212

1313
ruleTester.run('min-chained-call-depth', minChainedCallDepth, {
1414
valid: [
15+
{
16+
code: 'Array(10)\n.fill(0)\n.map(foo => foo)\n.slice(1);',
17+
},
18+
{
19+
code: 'Array(10).fill(0)\n.map(foo => foo)\n.slice(1);',
20+
options: [
21+
{
22+
ignoreChainDeeperThan: 2,
23+
},
24+
],
25+
},
26+
{
27+
code: 'Array(10)\n.foo\n.fill(0)\n.map(foo => foo)\n.slice(1);',
28+
},
1529
{
1630
code: 'new StringSchema<ApiKeyPermission>()'
1731
+ '\n.required()'
@@ -54,41 +68,46 @@ ruleTester.run('min-chained-call-depth', minChainedCallDepth, {
5468
],
5569
invalid: [
5670
{
57-
code: 'a()\n.b()\n.c();',
58-
output: 'a().b()\n.c();',
71+
code: 'Array(10)\n.fill(0)\n.map(foo => foo);',
72+
output: 'Array(10).fill(0)\n.map(foo => foo);',
73+
options: [
74+
{
75+
ignoreChainDeeperThan: 3,
76+
},
77+
],
5978
errors: [
6079
{
6180
line: 1,
62-
column: 4,
81+
column: 10,
6382
messageId: 'unexpectedLineBreak',
6483
},
6584
],
6685
},
6786
{
68-
code: 'a()\n.b\n.c();',
69-
output: 'a().b\n.c();',
87+
code: 'Array(10)\n.fill(10);',
88+
output: 'Array(10).fill(10);',
7089
errors: [
7190
{
7291
line: 1,
73-
column: 4,
92+
column: 10,
7493
messageId: 'unexpectedLineBreak',
7594
},
7695
],
7796
},
7897
{
79-
code: 'a\n.b()\n.c();',
80-
output: 'a.b()\n.c();',
98+
code: 'a()\n.b()\n.c();',
99+
output: 'a().b()\n.c();',
81100
errors: [
82101
{
83102
line: 1,
84-
column: 2,
103+
column: 4,
85104
messageId: 'unexpectedLineBreak',
86105
},
87106
],
88107
},
89108
{
90-
code: 'a()\n.b()\n.c()\n.d();',
91-
output: 'a().b()\n.c()\n.d();',
109+
code: 'a()\n.b\n.c();',
110+
output: 'a().b\n.c();',
92111
errors: [
93112
{
94113
line: 1,
@@ -97,6 +116,17 @@ ruleTester.run('min-chained-call-depth', minChainedCallDepth, {
97116
},
98117
],
99118
},
119+
{
120+
code: 'a\n.b()\n.c();',
121+
output: 'a.b()\n.c();',
122+
errors: [
123+
{
124+
line: 1,
125+
column: 2,
126+
messageId: 'unexpectedLineBreak',
127+
},
128+
],
129+
},
100130
{
101131
code: 'a()\n.b();',
102132
output: 'a().b();',

src/rules/min-chained-call-depth/index.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ export const minChainedCallDepth = createRule({
2525
minimum: 1,
2626
default: 100,
2727
},
28+
ignoreChainDeeperThan: {
29+
type: 'integer',
30+
minimum: 1,
31+
maximum: 10,
32+
default: 2,
33+
},
2834
},
2935
additionalProperties: false,
3036
},
@@ -37,9 +43,13 @@ export const minChainedCallDepth = createRule({
3743
{
3844
maxLineLength: 100,
3945
},
46+
{
47+
ignoreChainDeeperThan: 3,
48+
},
4049
],
4150
create: context => {
4251
const sourceCode = context.getSourceCode();
52+
let maxDepth = 0;
4353

4454
function getDepth(node: TSESTree.MemberExpression | TSESTree.CallExpression): number {
4555
let depth = 0;
@@ -88,29 +98,49 @@ export const minChainedCallDepth = createRule({
8898
: node;
8999

90100
if (
91-
// If the callee is not a member expression, we can skip.
101+
// If the callee is not a member expression, skip.
92102
// For example, root level calls like `foo();`.
93103
callee.type !== AST_NODE_TYPES.MemberExpression
94-
// If the callee is a computed member expression, like `foo[bar]()`, we can skip.
104+
// If the callee is a computed member expression, like `foo[bar]()`, skip.
95105
|| callee.computed
96106
/* eslint-disable-next-line @typescript-eslint/ban-ts-comment --
97-
* NewExpression is a possible callee object type
98-
*/
107+
* NewExpression is a possible callee object type
108+
*/
99109
// @ts-ignore
100110
|| callee.object.type === AST_NODE_TYPES.NewExpression
101-
// If the callee is already in the same line as it's object, we can skip.
111+
// If the callee is already in the same line as it's object, skip.
102112
|| callee.object.loc.end.line === callee.property.loc.start.line
103113
) {
104114
return;
105115
}
106116

107-
// We only inline the first level of chained calls.
108-
// If the current call is nested inside another call, we can skip.
109-
if (getDepth(callee) > 1) {
117+
const currentDepth = getDepth(callee);
118+
119+
maxDepth = Math.max(maxDepth, currentDepth);
120+
121+
// Only affect the root level as the total depth is must be known.
122+
// If the current call is nested inside another call, skip.
123+
if (currentDepth > 1) {
124+
return;
125+
}
126+
127+
const {maxLineLength = 100, ignoreChainDeeperThan = 2} = context.options[0] ?? {};
128+
129+
// If the max depth is greater than ignore threshold, skip
130+
//
131+
// Example:
132+
// ```ts
133+
// Array(10)
134+
// .fill(0)
135+
// .map(x => x + 1)
136+
// .slice(0, 5);
137+
// ```
138+
// In this case the depth is 3, and the default value of ignoreChainDeeperThan is 2.
139+
// So the check can be skipped.
140+
if (maxDepth > ignoreChainDeeperThan) {
110141
return;
111142
}
112143

113-
const {maxLineLength = 100} = context.options[0] ?? {};
114144
const {property} = callee;
115145
const lastToken = sourceCode.getLastToken(node, {
116146
filter: token => token.loc.end.line === property.loc.start.line,

src/rules/newline-per-chained-call/index.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ ruleTester.run('newline-per-chained-call', newlinePerChainedCall, {
3232
code: 'const a = m1().m2.m3().m4;',
3333
options: [
3434
{
35-
ignoreChainWithDepth: 4,
35+
ignoreChainDeeperThan: 4,
3636
},
3737
],
3838
},
3939
{
4040
code: 'const a = m1().m2.m3().m4.m5().m6.m7().m8.m9();',
4141
options: [
4242
{
43-
ignoreChainWithDepth: 8,
43+
ignoreChainDeeperThan: 8,
4444
},
4545
],
4646
},
@@ -183,7 +183,7 @@ ruleTester.run('newline-per-chained-call', newlinePerChainedCall, {
183183
output: 'const a = m1()\n.m2\n.m3()\n.m4()\n.m5()\n.m6()\n.m7();',
184184
options: [
185185
{
186-
ignoreChainWithDepth: 3,
186+
ignoreChainDeeperThan: 3,
187187
},
188188
],
189189
errors: [
@@ -219,7 +219,7 @@ ruleTester.run('newline-per-chained-call', newlinePerChainedCall, {
219219
output: '(foo).bar()\n.biz()',
220220
options: [
221221
{
222-
ignoreChainWithDepth: 1,
222+
ignoreChainDeeperThan: 1,
223223
},
224224
],
225225
errors: [
@@ -235,7 +235,7 @@ ruleTester.run('newline-per-chained-call', newlinePerChainedCall, {
235235
output: 'foo.bar()\n. /* comment */ biz()',
236236
options: [
237237
{
238-
ignoreChainWithDepth: 1,
238+
ignoreChainDeeperThan: 1,
239239
},
240240
],
241241
errors: [
@@ -251,7 +251,7 @@ ruleTester.run('newline-per-chained-call', newlinePerChainedCall, {
251251
output: 'foo.bar() /* comment */ \n.biz()',
252252
options: [
253253
{
254-
ignoreChainWithDepth: 1,
254+
ignoreChainDeeperThan: 1,
255255
},
256256
],
257257
errors: [

src/rules/newline-per-chained-call/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const newlinePerChainedCall = createRule({
2121
{
2222
type: 'object',
2323
properties: {
24-
ignoreChainWithDepth: {
24+
ignoreChainDeeperThan: {
2525
type: 'integer',
2626
minimum: 1,
2727
maximum: 10,
@@ -37,12 +37,12 @@ export const newlinePerChainedCall = createRule({
3737
},
3838
defaultOptions: [
3939
{
40-
ignoreChainWithDepth: 2,
40+
ignoreChainDeeperThan: 2,
4141
},
4242
],
4343
create: context => {
4444
const options = context.options[0] ?? {};
45-
const ignoreChainWithDepth = options.ignoreChainWithDepth ?? 2;
45+
const ignoreChainWithDepth = options.ignoreChainDeeperThan ?? 2;
4646

4747
const sourceCode = context.getSourceCode();
4848

0 commit comments

Comments
 (0)