-
-
Notifications
You must be signed in to change notification settings - Fork 367
/
prefer-top-level-await.js
133 lines (121 loc) · 3.3 KB
/
prefer-top-level-await.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
'use strict';
const {findVariable, getFunctionHeadLocation} = require('eslint-utils');
const {matches, memberExpressionSelector} = require('./selectors/index.js');
const ERROR_PROMISE = 'promise';
const ERROR_IIFE = 'iife';
const ERROR_IDENTIFIER = 'identifier';
const SUGGESTION_ADD_AWAIT = 'add-await';
const messages = {
[ERROR_PROMISE]: 'Prefer top-level await over using a promise chain.',
[ERROR_IIFE]: 'Prefer top-level await over an async IIFE.',
[ERROR_IDENTIFIER]: 'Prefer top-level await over an async function `{{name}}` call.',
[SUGGESTION_ADD_AWAIT]: 'Insert `await`.',
};
const promiseMethods = ['then', 'catch', 'finally'];
const topLevelCallExpression = 'CallExpression:not(:function *)';
const iife = [
topLevelCallExpression,
matches([
'[callee.type="FunctionExpression"]',
'[callee.type="ArrowFunctionExpression"]',
]),
'[callee.async!=false]',
'[callee.generator!=true]',
].join('');
const promise = [
topLevelCallExpression,
memberExpressionSelector({
path: 'callee',
properties: promiseMethods,
includeOptional: true,
}),
].join('');
const identifier = [
topLevelCallExpression,
'[callee.type="Identifier"]',
].join('');
const isPromiseMethodCalleeObject = node =>
node.parent.type === 'MemberExpression'
&& node.parent.object === node
&& !node.parent.computed
&& node.parent.property.type === 'Identifier'
&& promiseMethods.includes(node.parent.property.name)
&& node.parent.parent.type === 'CallExpression'
&& node.parent.parent.callee === node.parent;
const isAwaitArgument = node => {
if (node.parent.type === 'ChainExpression') {
node = node.parent;
}
return node.parent.type === 'AwaitExpression' && node.parent.argument === node;
};
/** @param {import('eslint').Rule.RuleContext} context */
function create(context) {
return {
[promise](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
return;
}
return {
node: node.callee.property,
messageId: ERROR_PROMISE,
};
},
[iife](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
return;
}
return {
node,
loc: getFunctionHeadLocation(node.callee, context.getSourceCode()),
messageId: ERROR_IIFE,
};
},
[identifier](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
return;
}
const variable = findVariable(context.getScope(), node.callee);
if (!variable || variable.defs.length !== 1) {
return;
}
const [definition] = variable.defs;
const value = definition.type === 'Variable' && definition.kind === 'const'
? definition.node.init
: definition.node;
if (
!(
(
value.type === 'ArrowFunctionExpression'
|| value.type === 'FunctionExpression'
|| value.type === 'FunctionDeclaration'
) && !value.generator && value.async
)
) {
return;
}
return {
node,
messageId: ERROR_IDENTIFIER,
data: {name: node.callee.name},
suggest: [
{
messageId: SUGGESTION_ADD_AWAIT,
fix: fixer => fixer.insertTextBefore(node, 'await '),
},
],
};
},
};
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Prefer top-level await over top-level promises and async function calls.',
},
hasSuggestions: true,
messages,
},
};