Skip to content

Commit 6cc1d61

Browse files
committed
feat: Add no-subscribe-in-pipe rule to forbid calling subscribe within pipe operators
1 parent f453132 commit 6cc1d61

File tree

2 files changed

+266
-0
lines changed

2 files changed

+266
-0
lines changed

src/rules/no-subscribe-in-pipe.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* @license Use of this source code is governed by an MIT-style license that
3+
* can be found in the LICENSE file at https://github.com/cartant/eslint-plugin-rxjs
4+
*/
5+
6+
import { TSESTree as es } from "@typescript-eslint/experimental-utils";
7+
import { getParent, getTypeServices } from "eslint-etc";
8+
import { ruleCreator } from "../utils";
9+
10+
const rule = ruleCreator({
11+
defaultOptions: [],
12+
meta: {
13+
docs: {
14+
description:
15+
"Forbids the calling of `subscribe` within any RxJS operator inside a `pipe`.",
16+
recommended: "error",
17+
},
18+
fixable: undefined,
19+
hasSuggestions: false,
20+
messages: {
21+
forbidden: "Subscribe calls within pipe operators are forbidden.",
22+
},
23+
schema: [],
24+
type: "problem",
25+
},
26+
name: "no-subscribe-in-pipe",
27+
create: (context) => {
28+
const { couldBeObservable, couldBeType } = getTypeServices(context);
29+
30+
function isWithinPipe(node: es.Node): boolean {
31+
let parent = getParent(node);
32+
while (parent) {
33+
if (
34+
parent.type === "CallExpression" &&
35+
parent.callee.type === "MemberExpression" &&
36+
parent.callee.property.type === "Identifier" &&
37+
parent.callee.property.name === "pipe"
38+
) {
39+
return true;
40+
}
41+
parent = getParent(parent);
42+
}
43+
return false;
44+
}
45+
46+
return {
47+
"CallExpression > MemberExpression[property.name='subscribe']": (
48+
node: es.MemberExpression
49+
) => {
50+
if (
51+
!couldBeObservable(node.object) &&
52+
!couldBeType(node.object, "Subscribable")
53+
) {
54+
return;
55+
}
56+
57+
if (isWithinPipe(node)) {
58+
context.report({
59+
messageId: "forbidden",
60+
node: node.property,
61+
});
62+
}
63+
},
64+
};
65+
},
66+
});
67+
68+
export = rule;
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { stripIndent } from "common-tags";
2+
import { fromFixture } from "eslint-etc";
3+
import rule = require("../../source/rules/no-subscribe-in-pipe");
4+
import { ruleTester } from "../utils";
5+
6+
ruleTester({ types: true }).run("no-subscribe-in-pipe", rule, {
7+
valid: [
8+
stripIndent`
9+
// subscribe outside of pipe
10+
import { of } from "rxjs";
11+
of(47).subscribe(value => {
12+
console.log(value);
13+
});
14+
`,
15+
stripIndent`
16+
// pipe without subscribe
17+
import { of } from "rxjs";
18+
import { map } from "rxjs/operators";
19+
of(47).pipe(
20+
map(x => x * 2)
21+
).subscribe(value => {
22+
console.log(value);
23+
});
24+
`,
25+
stripIndent`
26+
// nested pipes without subscribe
27+
import { of } from "rxjs";
28+
import { map, mergeMap } from "rxjs/operators";
29+
of(47).pipe(
30+
mergeMap(x => of(x).pipe(
31+
map(y => y * 2)
32+
))
33+
).subscribe(value => {
34+
console.log(value);
35+
});
36+
`,
37+
stripIndent`
38+
// subscribe in a function outside pipe
39+
import { of } from "rxjs";
40+
import { map } from "rxjs/operators";
41+
const logValue = (value) => of(value).subscribe(console.log);
42+
of(47).pipe(
43+
map(x => x * 2)
44+
).subscribe(logValue);
45+
`,
46+
stripIndent`
47+
// subscribe method on a non-Observable object inside pipe
48+
import { of } from "rxjs";
49+
import { map } from "rxjs/operators";
50+
const customObject = { subscribe: () => {} };
51+
of(47).pipe(
52+
map(x => {
53+
customObject.subscribe();
54+
return x * 2;
55+
})
56+
).subscribe();
57+
`,
58+
stripIndent`
59+
// subscribe as a variable name inside pipe
60+
import { of } from "rxjs";
61+
import { map } from "rxjs/operators";
62+
of(47).pipe(
63+
map(x => {
64+
const subscribe = 5;
65+
return x * subscribe;
66+
})
67+
).subscribe();
68+
`,
69+
stripIndent`
70+
// subscribe in a comment inside pipe
71+
import { of } from "rxjs";
72+
import { map } from "rxjs/operators";
73+
of(47).pipe(
74+
map(x => {
75+
// of(x).subscribe(console.log);
76+
return x * 2;
77+
})
78+
).subscribe();
79+
`,
80+
stripIndent`
81+
// subscribe as a string inside pipe
82+
import { of } from "rxjs";
83+
import { map } from "rxjs/operators";
84+
of(47).pipe(
85+
map(x => {
86+
console.log("subscribe");
87+
return x * 2;
88+
})
89+
).subscribe();
90+
`,
91+
],
92+
invalid: [
93+
fromFixture(
94+
stripIndent`
95+
// subscribe inside map operator
96+
import { of } from "rxjs";
97+
import { map } from "rxjs/operators";
98+
of(47).pipe(
99+
map(x => {
100+
of(x).subscribe(console.log);
101+
~~~~~~~~~ [forbidden]
102+
return x * 2;
103+
})
104+
).subscribe();
105+
`
106+
),
107+
fromFixture(
108+
stripIndent`
109+
// subscribe inside mergeMap operator
110+
import { of } from "rxjs";
111+
import { mergeMap } from "rxjs/operators";
112+
of(47).pipe(
113+
mergeMap(x => of(x).pipe(
114+
map(y => {
115+
of(y).subscribe(console.log);
116+
~~~~~~~~~ [forbidden]
117+
return y * 2;
118+
})
119+
))
120+
).subscribe();
121+
`
122+
),
123+
fromFixture(
124+
stripIndent`
125+
// subscribe inside tap operator
126+
import { of } from "rxjs";
127+
import { tap } from "rxjs/operators";
128+
of(47).pipe(
129+
tap(x => {
130+
of(x).subscribe(console.log);
131+
~~~~~~~~~ [forbidden]
132+
})
133+
).subscribe();
134+
`
135+
),
136+
fromFixture(
137+
stripIndent`
138+
// subscribe inside switchMap operator
139+
import { of } from "rxjs";
140+
import { switchMap } from "rxjs/operators";
141+
of(47).pipe(
142+
switchMap(x => {
143+
of(x).subscribe(console.log);
144+
~~~~~~~~~ [forbidden]
145+
return of(x * 2);
146+
})
147+
).subscribe();
148+
`
149+
),
150+
fromFixture(
151+
stripIndent`
152+
// subscribe inside nested pipes
153+
import { of } from "rxjs";
154+
import { map, mergeMap } from "rxjs/operators";
155+
of(47).pipe(
156+
mergeMap(x => of(x).pipe(
157+
map(y => {
158+
of(y).subscribe(console.log);
159+
~~~~~~~~~ [forbidden]
160+
return y * 2;
161+
})
162+
))
163+
).subscribe();
164+
`
165+
),
166+
fromFixture(
167+
stripIndent`
168+
// subscribe inside a deeply nested function in pipe
169+
import { of } from "rxjs";
170+
import { map } from "rxjs/operators";
171+
of(47).pipe(
172+
map(x => {
173+
const nestedFunc = () => {
174+
const deeplyNested = () => {
175+
of(x).subscribe(console.log);
176+
~~~~~~~~~ [forbidden]
177+
};
178+
deeplyNested();
179+
};
180+
nestedFunc();
181+
return x * 2;
182+
})
183+
).subscribe();
184+
`
185+
),
186+
fromFixture(
187+
stripIndent`
188+
// subscribe in a ternary operator in pipe
189+
import { of } from "rxjs";
190+
import { map } from "rxjs/operators";
191+
of(47).pipe(
192+
map(x => x > 50 ? x : of(x).subscribe(console.log))
193+
~~~~~~~~~ [forbidden]
194+
).subscribe();
195+
`
196+
),
197+
],
198+
});

0 commit comments

Comments
 (0)