Skip to content

Commit d42dd58

Browse files
feat(no-redundant-notify): also catch unsubscribe (#113)
Adds `unsubscribe` to the logic that guards against sending redundant notifications. `unsubscribe` is already discouraged by `no-subject-unsubscribe`, but the subscriber argument of `new Observable` could also be affected. Also, `unsubscribe` technically isn't a "notification", but it's a rare enough use case that it's probably not worth splitting into a separate rule. Resolves #107
1 parent 3ca435b commit d42dd58

File tree

3 files changed

+111
-2
lines changed

3 files changed

+111
-2
lines changed

docs/rules/no-redundant-notify.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
<!-- end auto-generated rule header -->
88

9-
This rule effects failures if an attempt is made to send a notification to an observer after a `complete` or `error` notification has already been sent.
9+
This rule effects failures if an attempt is made to send a notification to an observer after a `complete` or `error` notification has already been sent,
10+
or if `unsubscribe` has been called.
1011

1112
Note that the rule _does not perform extensive analysis_. It uses a straightforward and limited approach to catch obviously redundant notifications.
1213

src/rules/no-redundant-notify.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const noRedundantNotifyRule = ruleCreator({
3232
const sourceCode = context.sourceCode;
3333
const { couldBeType } = getTypeServices(context);
3434
return {
35-
'ExpressionStatement[expression.callee.property.name=/^(complete|error)$/] + ExpressionStatement[expression.callee.property.name=/^(next|complete|error)$/]':
35+
'ExpressionStatement[expression.callee.property.name=/^(complete|error|unsubscribe)$/] + ExpressionStatement[expression.callee.property.name=/^(next|complete|error|unsubscribe)$/]':
3636
(node: es.ExpressionStatement) => {
3737
const parent = node.parent;
3838
if (!parent) {

tests/rules/no-redundant-notify.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ ruleTester({ types: true }).run('no-redundant-notify', noRedundantNotifyRule, {
2121
observer.error(new Error("Kaboom!"));
2222
})
2323
`,
24+
stripIndent`
25+
// observable next + unsubscribe
26+
import { Observable } from "rxjs";
27+
const observable = new Observable<number>(observer => {
28+
observer.next(42);
29+
observer.unsubscribe();
30+
})
31+
`,
2432
stripIndent`
2533
// subject next + complete
2634
import { Subject } from "rxjs";
@@ -35,6 +43,13 @@ ruleTester({ types: true }).run('no-redundant-notify', noRedundantNotifyRule, {
3543
subject.next(42);
3644
subject.error(new Error("Kaboom!"));
3745
`,
46+
stripIndent`
47+
// subject next + unsubscribe
48+
import { Subject } from "rxjs";
49+
const subject = new Subject<number>();
50+
subject.next(42);
51+
subject.unsubscribe();
52+
`,
3853
stripIndent`
3954
// different names with error
4055
import { Subject } from "rxjs";
@@ -51,6 +66,14 @@ ruleTester({ types: true }).run('no-redundant-notify', noRedundantNotifyRule, {
5166
a.complete();
5267
b.complete();
5368
`,
69+
stripIndent`
70+
// different names with unsubscribe
71+
import { Subject } from "rxjs";
72+
const a = new Subject<number>();
73+
const b = new Subject<number>();
74+
a.unsubscribe();
75+
b.unsubscribe();
76+
`,
5477
stripIndent`
5578
// non-observer
5679
import { Subject } from "rxjs";
@@ -139,6 +162,51 @@ ruleTester({ types: true }).run('no-redundant-notify', noRedundantNotifyRule, {
139162
});
140163
`,
141164
),
165+
fromFixture(
166+
stripIndent`
167+
// observable unsubscribe + next
168+
import { Observable } from "rxjs";
169+
const observable = new Observable<number>(observer => {
170+
observer.unsubscribe();
171+
observer.next(42);
172+
~~~~ [forbidden]
173+
})
174+
`,
175+
),
176+
fromFixture(
177+
stripIndent`
178+
// observable unsubscribe + complete
179+
import { Observable } from "rxjs";
180+
const observable = new Observable<number>(observer => {
181+
observer.unsubscribe();
182+
observer.complete();
183+
~~~~~~~~ [forbidden]
184+
})
185+
`,
186+
),
187+
fromFixture(
188+
stripIndent`
189+
// observable unsubscribe + error
190+
import { Observable } from "rxjs";
191+
const observable = new Observable<number>(observer => {
192+
observer.unsubscribe();
193+
observer.error(new Error("Kaboom!"));
194+
~~~~~ [forbidden]
195+
})
196+
`,
197+
),
198+
fromFixture(
199+
stripIndent`
200+
// observable unsubscribe + unsubscribe
201+
import { Observable } from "rxjs";
202+
const observable = new Observable<number>(observer => {
203+
observer.unsubscribe();
204+
observer.unsubscribe();
205+
~~~~~~~~~~~ [forbidden]
206+
})
207+
`,
208+
),
209+
142210
fromFixture(
143211
stripIndent`
144212
// subject complete + next
@@ -199,5 +267,45 @@ ruleTester({ types: true }).run('no-redundant-notify', noRedundantNotifyRule, {
199267
~~~~~ [forbidden]
200268
`,
201269
),
270+
fromFixture(
271+
stripIndent`
272+
// subject unsubscribe + next
273+
import { Subject } from "rxjs";
274+
const subject = new Subject<number>();
275+
subject.unsubscribe();
276+
subject.next(42);
277+
~~~~ [forbidden]
278+
`,
279+
),
280+
fromFixture(
281+
stripIndent`
282+
// subject unsubscribe + complete
283+
import { Subject } from "rxjs";
284+
const subject = new Subject<number>();
285+
subject.unsubscribe();
286+
subject.complete();
287+
~~~~~~~~ [forbidden]
288+
`,
289+
),
290+
fromFixture(
291+
stripIndent`
292+
// subject unsubscribe + error
293+
import { Subject } from "rxjs";
294+
const subject = new Subject<number>();
295+
subject.unsubscribe();
296+
subject.error(new Error("Kaboom!"));
297+
~~~~~ [forbidden]
298+
`,
299+
),
300+
fromFixture(
301+
stripIndent`
302+
// subject unsubscribe + unsubscribe
303+
import { Subject } from "rxjs";
304+
const subject = new Subject<number>();
305+
subject.unsubscribe();
306+
subject.unsubscribe();
307+
~~~~~~~~~~~ [forbidden]
308+
`,
309+
),
202310
],
203311
});

0 commit comments

Comments
 (0)