Skip to content

Commit f92bab4

Browse files
authored
Merge pull request #53 from simplabs/no-native-promise-helpers
Implement `no-native-promise-helpers` rule
2 parents b133f6d + 3df31bc commit f92bab4

File tree

3 files changed

+253
-0
lines changed

3 files changed

+253
-0
lines changed

rules/no-native-promise-helpers.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
3+
const HELPERS = ['all', 'race', 'allSettled'];
4+
5+
module.exports = {
6+
create(context) {
7+
let parentTask = null;
8+
9+
return {
10+
FunctionExpression(node) {
11+
if (parentTask) return;
12+
13+
let { generator, parent } = node;
14+
if (!generator) return;
15+
if (parent.type !== 'CallExpression' || parent.arguments[0] !== node) return;
16+
17+
let { callee } = parent;
18+
if (callee.type !== 'Identifier' || callee.name !== 'task') return;
19+
20+
parentTask = node;
21+
},
22+
23+
'FunctionExpression:exit'(node) {
24+
if (node === parentTask) {
25+
parentTask = null;
26+
}
27+
},
28+
29+
CallExpression(node) {
30+
if (!parentTask) return;
31+
32+
let { callee } = node;
33+
if (callee.type !== 'MemberExpression') return;
34+
35+
let { object, property } = callee;
36+
if (object.type !== 'Identifier' || object.name !== 'Promise') return;
37+
if (property.type !== 'Identifier' || !HELPERS.includes(property.name)) return;
38+
39+
context.report({
40+
node,
41+
message: `Use \`import { ${property.name} } from 'ember-concurrency';\` instead of \`Promise.${property.name}()\``,
42+
});
43+
},
44+
};
45+
},
46+
};

rules/no-native-promise-helpers.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# no-native-promise-helpers
2+
3+
`Promise.all()` and `Promise.race()` do not cancel their child promises or
4+
tasks, because they are not aware of cancellation in the way that
5+
ember-concurrency has implemented it. ember-concurrency does provide
6+
alternative, cancellation-aware `all()` and `race()` implementations though,
7+
but they need to be explicitly imported.
8+
9+
see <http://ember-concurrency.com/api/global.html>
10+
11+
This rule warns about usage of `Promise.all()` and `Promise.race()` inside of
12+
ember-concurrency tasks.
13+
14+
## Examples
15+
16+
This rule **forbids** the following:
17+
18+
```js
19+
submitTask: task(function*() {
20+
yield Promise.all([
21+
this.saveTask.perform(),
22+
this.loadingSpinnerTask.perform(),
23+
]);
24+
})
25+
```
26+
27+
This rule **allows** the following:
28+
29+
```js
30+
import { all } from 'ember-concurrency';
31+
32+
submitTask: task(function*() {
33+
yield all([
34+
this.saveTask.perform(),
35+
this.loadingSpinnerTask.perform(),
36+
]);
37+
})
38+
```
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
const { RuleTester } = require('eslint');
2+
3+
const rule = require('./no-native-promise-helpers');
4+
5+
let ruleTester = new RuleTester({
6+
parser: require.resolve('babel-eslint'),
7+
parserOptions: {
8+
ecmaVersion: 2018,
9+
sourceType: 'module',
10+
},
11+
});
12+
13+
ruleTester.run('no-native-promise-helpers', rule, {
14+
valid: [
15+
`
16+
import { all } from 'ember-concurrency';
17+
18+
export default Component.extend({
19+
submitTask: task(function*() {
20+
yield all([
21+
this.saveTask.perform(),
22+
this.loadingSpinnerTask.perform(),
23+
]);
24+
})
25+
});
26+
`,
27+
`
28+
import { all } from 'ember-concurrency';
29+
30+
export default class extends Component {
31+
@task(function*() {
32+
yield all([
33+
this.saveTask.perform(),
34+
this.loadingSpinnerTask.perform(),
35+
]);
36+
})
37+
submitTask;
38+
}
39+
`,
40+
`
41+
import { all } from 'ember-concurrency';
42+
43+
export default Component.extend({
44+
async submit() {
45+
await Promise.all([foo(), bar()]);
46+
},
47+
});
48+
`,
49+
`
50+
import { all } from 'ember-concurrency';
51+
52+
export default class extends Component {
53+
async submit() {
54+
await Promise.all([foo(), bar()]);
55+
}
56+
}
57+
`,
58+
`
59+
import { all } from 'ember-concurrency';
60+
61+
export default class extends Component {
62+
*submit() {
63+
yield Promise.all([foo(), bar()]);
64+
}
65+
}
66+
`,
67+
`
68+
export default Component.extend({
69+
submitTask: foo(function*() {
70+
yield Promise.all([
71+
this.saveTask.perform(),
72+
this.loadingSpinnerTask.perform(),
73+
]);
74+
})
75+
});
76+
`,
77+
],
78+
79+
invalid: [
80+
{
81+
code: `
82+
export default Component.extend({
83+
submitTask: task(function*() {
84+
yield Promise.all([
85+
this.saveTask.perform(),
86+
this.loadingSpinnerTask.perform(),
87+
]);
88+
})
89+
});
90+
`,
91+
errors: [
92+
{
93+
message: "Use `import { all } from 'ember-concurrency';` instead of `Promise.all()`",
94+
line: 4,
95+
column: 19,
96+
endLine: 7,
97+
endColumn: 15,
98+
},
99+
],
100+
},
101+
{
102+
code: `
103+
export default class extends Component {
104+
@task(function*() {
105+
yield Promise.all([
106+
this.saveTask.perform(),
107+
this.loadingSpinnerTask.perform(),
108+
]);
109+
})
110+
submitTask;
111+
}
112+
`,
113+
errors: [
114+
{
115+
message: "Use `import { all } from 'ember-concurrency';` instead of `Promise.all()`",
116+
line: 4,
117+
column: 19,
118+
endLine: 7,
119+
endColumn: 15,
120+
},
121+
],
122+
},
123+
{
124+
code: `
125+
export default class extends Component {
126+
@task(function*() {
127+
yield Promise.race([
128+
this.saveTask.perform(),
129+
this.loadingSpinnerTask.perform(),
130+
]);
131+
})
132+
submitTask;
133+
}
134+
`,
135+
errors: [
136+
{
137+
message: "Use `import { race } from 'ember-concurrency';` instead of `Promise.race()`",
138+
line: 4,
139+
column: 19,
140+
endLine: 7,
141+
endColumn: 15,
142+
},
143+
],
144+
},
145+
{
146+
code: `
147+
export default class extends Component {
148+
@task(function*() {
149+
yield Promise.allSettled([
150+
this.saveTask.perform(),
151+
this.loadingSpinnerTask.perform(),
152+
]);
153+
})
154+
submitTask;
155+
}
156+
`,
157+
errors: [
158+
{
159+
message:
160+
"Use `import { allSettled } from 'ember-concurrency';` instead of `Promise.allSettled()`",
161+
line: 4,
162+
column: 19,
163+
endLine: 7,
164+
endColumn: 15,
165+
},
166+
],
167+
},
168+
],
169+
});

0 commit comments

Comments
 (0)