@ethlete/eslint-plugin 1.0.0-next.20 → 1.0.0-next.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/configs/recommended.js +5 -13
- package/src/index.js +2 -0
- package/src/rules/no-async-await.js +80 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0-next.21
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`3d44765`](https://github.com/ethlete-io/ethdk/commit/3d44765647e6d47b558c1395fe5ebcb20ed3ef83) Thanks [@TomTomB](https://github.com/TomTomB)! - New rule `ethlete/no-async-await`, replacing the `no-restricted-syntax` selectors that could not be
|
|
8
|
+
toggled on their own. It also catches bare `await` and `for await…of`.
|
|
9
|
+
|
|
3
10
|
## 1.0.0-next.20
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -126,19 +126,8 @@ const recommendedTs = {
|
|
|
126
126
|
selector: 'PropertyDefinition > ArrowFunctionExpression',
|
|
127
127
|
message: 'No arrow function properties in classes. Use regular methods.',
|
|
128
128
|
},
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
selector: 'FunctionDeclaration[async=true]',
|
|
132
|
-
message: 'No async/await. Use RxJS for all async operations.',
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
selector: 'ArrowFunctionExpression[async=true]',
|
|
136
|
-
message: 'No async/await. Use RxJS for all async operations.',
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
selector: 'FunctionExpression[async=true]',
|
|
140
|
-
message: 'No async/await. Use RxJS for all async operations.',
|
|
141
|
-
},
|
|
129
|
+
// async/await is owned by ethlete/no-async-await, so a lib can keep it on while it works
|
|
130
|
+
// through the rest of this rule's backlog.
|
|
142
131
|
// No static class members — except ngTemplateContextGuard, which Angular's template
|
|
143
132
|
// type checker requires to be static (it types a structural directive's `let-` bindings)
|
|
144
133
|
{
|
|
@@ -253,6 +242,9 @@ const recommendedTs = {
|
|
|
253
242
|
// No .subscribe() inside a .pipe() callback
|
|
254
243
|
'ethlete/no-subscribe-in-pipe': 'error',
|
|
255
244
|
|
|
245
|
+
// No async/await — asynchronous work is modelled as cold Observables
|
|
246
|
+
'ethlete/no-async-await': 'error',
|
|
247
|
+
|
|
256
248
|
// No .subscribe() inside effect() or computed()
|
|
257
249
|
'ethlete/no-rxjs-in-effect': 'error',
|
|
258
250
|
|
package/src/index.js
CHANGED
|
@@ -58,6 +58,7 @@ const noLegacyPrepareWithoutInjector = require('./rules/no-legacy-prepare-withou
|
|
|
58
58
|
const requireFormSubmit = require('./rules/require-form-submit');
|
|
59
59
|
const noCdkImport = require('./rules/no-cdk-import');
|
|
60
60
|
const noLegacyQueryImport = require('./rules/no-legacy-query-import');
|
|
61
|
+
const noAsyncAwait = require('./rules/no-async-await');
|
|
61
62
|
const { recommendedTs, recommendedTemplate, recommendedSpec } = require('./configs/recommended');
|
|
62
63
|
|
|
63
64
|
/** @type {import('eslint').ESLint.Plugin} */
|
|
@@ -124,6 +125,7 @@ const plugin = {
|
|
|
124
125
|
'require-form-submit': requireFormSubmit,
|
|
125
126
|
'no-cdk-import': noCdkImport,
|
|
126
127
|
'no-legacy-query-import': noLegacyQueryImport,
|
|
128
|
+
'no-async-await': noAsyncAwait,
|
|
127
129
|
},
|
|
128
130
|
};
|
|
129
131
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Disallow async/await — asynchronous work is modelled with RxJS.
|
|
6
|
+
*
|
|
7
|
+
* Every async API in the SDK returns a cold Observable, not a Promise, so a consumer can compose,
|
|
8
|
+
* cancel and retry it. Bridge a browser Promise in with from(); never await it.
|
|
9
|
+
*
|
|
10
|
+
* BAD:
|
|
11
|
+
* async copy(text: string) {
|
|
12
|
+
* await navigator.clipboard.writeText(text);
|
|
13
|
+
* this.copied.set(true);
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* GOOD:
|
|
17
|
+
* copy(text: string) {
|
|
18
|
+
* return from(navigator.clipboard.writeText(text)).pipe(tap(() => this.copied.set(true)));
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Storybook's play function is called by the test runner and has to be a promise. */
|
|
23
|
+
const isStorybookPlayFunction = (node) => {
|
|
24
|
+
const { parent } = node;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
parent?.type === 'Property' &&
|
|
28
|
+
parent.value === node &&
|
|
29
|
+
parent.key.type === 'Identifier' &&
|
|
30
|
+
parent.key.name === 'play'
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
35
|
+
const noAsyncAwait = {
|
|
36
|
+
meta: {
|
|
37
|
+
type: 'problem',
|
|
38
|
+
docs: {
|
|
39
|
+
description: 'Disallow async / await — model asynchronous work as RxJS Observables.',
|
|
40
|
+
recommended: true,
|
|
41
|
+
},
|
|
42
|
+
messages: {
|
|
43
|
+
noAsync:
|
|
44
|
+
"Don't declare an 'async' function. Return a cold Observable instead — 'defer()' for laziness, 'from()' to bridge a Promise-based browser API.",
|
|
45
|
+
noAwait:
|
|
46
|
+
"Don't 'await'. Compose the value into the stream with 'switchMap' / 'mergeMap' / 'from()' instead, and read a one-shot result with 'firstValueFrom()' only in tests.",
|
|
47
|
+
noForAwait: "Don't use 'for await…of'. Bridge the async iterable into a stream and compose it with RxJS.",
|
|
48
|
+
},
|
|
49
|
+
schema: [],
|
|
50
|
+
},
|
|
51
|
+
create(context) {
|
|
52
|
+
const isExempt = (node) => {
|
|
53
|
+
for (let current = node; current; current = current.parent) {
|
|
54
|
+
if (isStorybookPlayFunction(current)) return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return false;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const reportAsync = (node) => {
|
|
61
|
+
if (!node.async || isExempt(node)) return;
|
|
62
|
+
|
|
63
|
+
context.report({ node, messageId: 'noAsync' });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
FunctionDeclaration: reportAsync,
|
|
68
|
+
FunctionExpression: reportAsync,
|
|
69
|
+
ArrowFunctionExpression: reportAsync,
|
|
70
|
+
AwaitExpression(node) {
|
|
71
|
+
if (!isExempt(node)) context.report({ node, messageId: 'noAwait' });
|
|
72
|
+
},
|
|
73
|
+
ForOfStatement(node) {
|
|
74
|
+
if (node.await && !isExempt(node)) context.report({ node, messageId: 'noForAwait' });
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
module.exports = noAsyncAwait;
|