Skip to content

Commit 940ee0c

Browse files
feat(no-topromise): suggest an exact replacement for toPromise() (#184)
This adds the "most correct" suggestion to `no-topromise` for replacing `toPromise()`. The old `toPromise()` would resolve to `undefined`, while the new `lastValueFrom` rejects with `EmptyError`. So if a user followed the old top suggestion and replaced `toPromise` with `lastValueFrom`, they might be surprised to find `EmptyError` now thrown instead. This wouldn't be a problem for someone using the `strict` config, which includes `no-ignored-default-value`, but users of the `recommended` config wouldn't get that lint notification. Resolves #168
1 parent e46b766 commit 940ee0c

File tree

3 files changed

+78
-9
lines changed

3 files changed

+78
-9
lines changed

docs/rules/no-topromise.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010

1111
This rule effects failures if the `toPromise` method is used.
1212

13-
This rule provides two editor suggestions which replace `toPromise` with either:
13+
This rule provides three editor suggestions which replace `toPromise` with either:
1414

15-
- `lastValueFrom(...)`, which behaves closest to the behavior of `toPromise`,
15+
- `lastValueFrom(..., { defaultValue: undefined })`, which imitates the behavior of `toPromise`,
16+
- or `lastValueFrom(...)`, which throws `EmptyError` instead of defaulting to `undefined`,
1617
- or `firstValueFrom(...)`.
1718

1819
## When Not To Use It
@@ -26,6 +27,10 @@ Type checked lint rules are more powerful than traditional lint rules, but also
2627

2728
- [Conversion to Promises](https://rxjs.dev/deprecations/to-promise)
2829

30+
## Related To
31+
32+
- [`no-ignored-default-value`](./no-ignored-default-value.md)
33+
2934
## Resources
3035

3136
- [Rule source](https://github.com/JasonWeinzierl/eslint-plugin-rxjs-x/blob/main/src/rules/no-topromise.ts)

src/rules/no-topromise.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const noTopromiseRule = ruleCreator({
1313
hasSuggestions: true,
1414
messages: {
1515
forbidden: 'The toPromise method is forbidden.',
16+
suggestLastValueFromWithDefault: 'Use lastValueFrom(..., { defaultValue: undefined }) instead.',
1617
suggestLastValueFrom: 'Use lastValueFrom instead.',
1718
suggestFirstValueFrom: 'Use firstValueFrom instead.',
1819
},
@@ -37,6 +38,7 @@ export const noTopromiseRule = ruleCreator({
3738
callExpression: es.CallExpression,
3839
observableNode: es.Node,
3940
importDeclarations: es.ImportDeclaration[],
41+
{ withDefault }: { withDefault?: boolean } = {},
4042
) {
4143
return function* fix(fixer: TSESLint.RuleFixer) {
4244
let namespace = '';
@@ -74,7 +76,7 @@ export const noTopromiseRule = ruleCreator({
7476

7577
yield fixer.replaceText(
7678
callExpression,
77-
`${namespace}${functionName}(${context.sourceCode.getText(observableNode)})`,
79+
`${namespace}${functionName}(${context.sourceCode.getText(observableNode)}${withDefault ? ', { defaultValue: undefined }' : ''})`,
7880
);
7981
};
8082
}
@@ -99,6 +101,10 @@ export const noTopromiseRule = ruleCreator({
99101
messageId: 'forbidden',
100102
node: memberExpression.property,
101103
suggest: [
104+
{
105+
messageId: 'suggestLastValueFromWithDefault',
106+
fix: createFix('lastValueFrom', node, memberExpression.object, importDeclarations, { withDefault: true }),
107+
},
102108
{
103109
messageId: 'suggestLastValueFrom',
104110
fix: createFix('lastValueFrom', node, memberExpression.object, importDeclarations),

tests/rules/no-topromise.test.ts

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,19 @@ ruleTester({ types: true }).run('no-topromise', noTopromiseRule, {
3838
import { of } from "rxjs";
3939
const a = of("a");
4040
a.toPromise().then(value => console.log(value));
41-
~~~~~~~~~ [forbidden suggest 0 1]
41+
~~~~~~~~~ [forbidden suggest 0 1 2]
4242
`,
4343
{
4444
suggestions: [
45+
{
46+
messageId: 'suggestLastValueFromWithDefault',
47+
output: stripIndent`
48+
// observable toPromise
49+
import { of, lastValueFrom } from "rxjs";
50+
const a = of("a");
51+
lastValueFrom(a, { defaultValue: undefined }).then(value => console.log(value));
52+
`,
53+
},
4554
{
4655
messageId: 'suggestLastValueFrom',
4756
output: stripIndent`
@@ -69,10 +78,19 @@ ruleTester({ types: true }).run('no-topromise', noTopromiseRule, {
6978
import { Subject } from "rxjs";
7079
const a = new Subject<string>();
7180
a.toPromise().then(value => console.log(value));
72-
~~~~~~~~~ [forbidden suggest 0 1]
81+
~~~~~~~~~ [forbidden suggest 0 1 2]
7382
`,
7483
{
7584
suggestions: [
85+
{
86+
messageId: 'suggestLastValueFromWithDefault',
87+
output: stripIndent`
88+
// subject toPromise
89+
import { Subject, lastValueFrom } from "rxjs";
90+
const a = new Subject<string>();
91+
lastValueFrom(a, { defaultValue: undefined }).then(value => console.log(value));
92+
`,
93+
},
7694
{
7795
messageId: 'suggestLastValueFrom',
7896
output: stripIndent`
@@ -102,11 +120,22 @@ ruleTester({ types: true }).run('no-topromise', noTopromiseRule, {
102120
a
103121
.foo$
104122
.toPromise().then(value => console.log(value))
105-
~~~~~~~~~ [forbidden suggest 0 1]
123+
~~~~~~~~~ [forbidden suggest 0 1 2]
106124
.catch(error => console.error(error));
107125
`,
108126
{
109127
suggestions: [
128+
{
129+
messageId: 'suggestLastValueFromWithDefault',
130+
output: stripIndent`
131+
// weird whitespace
132+
import { of, lastValueFrom } from "rxjs";
133+
const a = { foo$: of("a") };
134+
lastValueFrom(a
135+
.foo$, { defaultValue: undefined }).then(value => console.log(value))
136+
.catch(error => console.error(error));
137+
`,
138+
},
110139
{
111140
messageId: 'suggestLastValueFrom',
112141
output: stripIndent`
@@ -138,10 +167,19 @@ ruleTester({ types: true }).run('no-topromise', noTopromiseRule, {
138167
import { lastValueFrom as lvf, of } from "rxjs";
139168
const a = of("a");
140169
a.toPromise().then(value => console.log(value));
141-
~~~~~~~~~ [forbidden suggest 0 1]
170+
~~~~~~~~~ [forbidden suggest 0 1 2]
142171
`,
143172
{
144173
suggestions: [
174+
{
175+
messageId: 'suggestLastValueFromWithDefault',
176+
output: stripIndent`
177+
// lastValueFrom already imported
178+
import { lastValueFrom as lvf, of } from "rxjs";
179+
const a = of("a");
180+
lvf(a, { defaultValue: undefined }).then(value => console.log(value));
181+
`,
182+
},
145183
{
146184
messageId: 'suggestLastValueFrom',
147185
output: stripIndent`
@@ -170,10 +208,21 @@ ruleTester({ types: true }).run('no-topromise', noTopromiseRule, {
170208
171209
const a = fromFetch("https://api.some.com");
172210
a.toPromise().then(value => console.log(value));
173-
~~~~~~~~~ [forbidden suggest 0 1]
211+
~~~~~~~~~ [forbidden suggest 0 1 2]
174212
`,
175213
{
176214
suggestions: [
215+
{
216+
messageId: 'suggestLastValueFromWithDefault',
217+
output: stripIndent`
218+
// rxjs not already imported
219+
import { fromFetch } from "rxjs/fetch";
220+
import { lastValueFrom } from "rxjs";
221+
222+
const a = fromFetch("https://api.some.com");
223+
lastValueFrom(a, { defaultValue: undefined }).then(value => console.log(value));
224+
`,
225+
},
177226
{
178227
messageId: 'suggestLastValueFrom',
179228
output: stripIndent`
@@ -205,10 +254,19 @@ ruleTester({ types: true }).run('no-topromise', noTopromiseRule, {
205254
import * as Rx from "rxjs";
206255
const a = Rx.of("a");
207256
a.toPromise().then(value => console.log(value));
208-
~~~~~~~~~ [forbidden suggest 0 1]
257+
~~~~~~~~~ [forbidden suggest 0 1 2]
209258
`,
210259
{
211260
suggestions: [
261+
{
262+
messageId: 'suggestLastValueFromWithDefault',
263+
output: stripIndent`
264+
// namespace import
265+
import * as Rx from "rxjs";
266+
const a = Rx.of("a");
267+
Rx.lastValueFrom(a, { defaultValue: undefined }).then(value => console.log(value));
268+
`,
269+
},
212270
{
213271
messageId: 'suggestLastValueFrom',
214272
output: stripIndent`

0 commit comments

Comments
 (0)