Skip to content

Commit ef2f886

Browse files
fix(no-ignored-replay-buffer): check config bufferSize (#12)
Fixes cartant#100 There's also a proposed fix PR in the upstream repo cartant#114 but I avoided looking at that solution to avoid any license issue, so this solution might be different. - Fix: if the `shareReplay` operator was passed an object config, then require `bufferSize` to be in that object. - Fix: the rule wasn't handling if `shareReplay` is imported under a namespace. - (Might need to review the entire project. Since rxjs now recommends importing from "rxjs" instead of "rxjs/operators", there's a risk that many rules also fail to account for that new paradigm. Note that this rule is the only rule that tests namespace imports for all cases, so we at least should expand that coverage/type of test.)
1 parent 6870c8a commit ef2f886

File tree

3 files changed

+70
-3
lines changed

3 files changed

+70
-3
lines changed

docs/rules/no-ignored-replay-buffer.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import { ReplaySubject } from "rxjs";
1515
const subject = new ReplaySubject<number>();
1616
```
1717

18+
```ts
19+
import { of, shareReplay } from "rxjs";
20+
of(42).pipe(shareReplay({ refCount: true }));
21+
```
22+
1823
Examples of **correct** code for this rule:
1924

2025
```ts
@@ -26,3 +31,8 @@ const subject = new ReplaySubject<number>(1);
2631
import { ReplaySubject } from "rxjs";
2732
const subject = new ReplaySubject<number>(Infinity);
2833
```
34+
35+
```ts
36+
import { of, shareReplay } from "rxjs";
37+
of(42).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
38+
```

src/rules/no-ignored-replay-buffer.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { TSESTree as es } from '@typescript-eslint/utils';
1+
import { AST_NODE_TYPES, TSESTree as es } from '@typescript-eslint/utils';
22
import { ruleCreator } from '../utils';
33

44
export const noIgnoredReplayBufferRule = ruleCreator({
@@ -17,16 +17,35 @@ export const noIgnoredReplayBufferRule = ruleCreator({
1717
},
1818
name: 'no-ignored-replay-buffer',
1919
create: (context) => {
20+
function checkShareReplayConfig(
21+
node: es.Identifier,
22+
shareReplayConfigArg: es.ObjectExpression,
23+
) {
24+
if (!shareReplayConfigArg.properties.some(p => p.type === AST_NODE_TYPES.Property && p.key.type === AST_NODE_TYPES.Identifier && p.key.name === 'bufferSize')) {
25+
context.report({
26+
messageId: 'forbidden',
27+
node,
28+
});
29+
}
30+
}
31+
2032
function checkNode(
21-
node: es.Node,
22-
{ arguments: args }: { arguments: es.Node[] },
33+
node: es.Identifier,
34+
{ arguments: args }: es.NewExpression | es.CallExpression,
2335
) {
2436
if (!args || args.length === 0) {
2537
context.report({
2638
messageId: 'forbidden',
2739
node,
2840
});
2941
}
42+
43+
if (node.name === 'shareReplay' && args?.length === 1) {
44+
const arg = args[0];
45+
if (arg.type === AST_NODE_TYPES.ObjectExpression) {
46+
checkShareReplayConfig(node, arg);
47+
}
48+
}
3049
}
3150

3251
return {
@@ -49,6 +68,13 @@ export const noIgnoredReplayBufferRule = ruleCreator({
4968
const callExpression = node.parent as es.CallExpression;
5069
checkNode(node, callExpression);
5170
},
71+
'CallExpression > MemberExpression > Identifier[name=/^(publishReplay|shareReplay)$/]': (
72+
node: es.Identifier,
73+
) => {
74+
const memberExpression = node.parent as es.MemberExpression;
75+
const callExpression = memberExpression.parent as es.CallExpression;
76+
checkNode(node, callExpression);
77+
},
5278
};
5379
},
5480
});

tests/rules/no-ignored-replay-buffer.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ ruleTester({ types: false }).run('no-ignored-replay-buffer', noIgnoredReplayBuff
2626
2727
const a = of(42).pipe(shareReplay(1));
2828
`,
29+
fromFixture(
30+
stripIndent`
31+
// shareReplay with config not ignored
32+
import { interval, shareReplay } from "rxjs";
33+
interval(1000).pipe(shareReplay({ bufferSize: 1, refCount: true }));
34+
`,
35+
),
2936
stripIndent`
3037
// namespace ReplaySubject not ignored
3138
import * as Rx from "rxjs";
@@ -47,6 +54,13 @@ ruleTester({ types: false }).run('no-ignored-replay-buffer', noIgnoredReplayBuff
4754
4855
const a = Rx.of(42).pipe(shareReplay(1));
4956
`,
57+
stripIndent`
58+
// namespace shareReplay with config not ignored
59+
import * as Rx from "rxjs";
60+
import { shareReplay } from "rxjs/operators";
61+
62+
const a = Rx.of(42).pipe(shareReplay({ bufferSize: 1, refCount: true }));
63+
`,
5064
stripIndent`
5165
// namespace class not ignored
5266
import * as Rx from "rxjs";
@@ -91,6 +105,15 @@ ruleTester({ types: false }).run('no-ignored-replay-buffer', noIgnoredReplayBuff
91105
~~~~~~~~~~~ [forbidden]
92106
`,
93107
),
108+
fromFixture(
109+
stripIndent`
110+
// shareReplay with config ignored
111+
import { of, shareReplay } from "rxjs";
112+
113+
const a = of(42).pipe(shareReplay({ refCount: true }));
114+
~~~~~~~~~~~ [forbidden]
115+
`,
116+
),
94117
fromFixture(
95118
stripIndent`
96119
// namespace ReplaySubject ignored
@@ -122,6 +145,14 @@ ruleTester({ types: false }).run('no-ignored-replay-buffer', noIgnoredReplayBuff
122145
~~~~~~~~~~~ [forbidden]
123146
`,
124147
),
148+
fromFixture(
149+
stripIndent`
150+
// namespace shareReplay with config ignored
151+
import * as Rx from "rxjs";
152+
const a = Rx.of(42).pipe(Rx.shareReplay({ refCount: true }));
153+
~~~~~~~~~~~ [forbidden]
154+
`,
155+
),
125156
fromFixture(
126157
stripIndent`
127158
// namespace class ignored

0 commit comments

Comments
 (0)