forked from typescript-eslint/typescript-eslint
/
await-promise.js
124 lines (109 loc) · 2.95 KB
/
await-promise.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
/**
* @fileoverview Disallows awaiting a value that is not a Promise
* @author Josh Goldberg
*/
'use strict';
const tsutils = require('tsutils');
const ts = require('typescript');
const util = require('../util');
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
const defaultOptions = [
{
allowedPromiseNames: []
}
];
/**
* @type {import("eslint").Rule.RuleModule}
*/
module.exports = {
meta: {
docs: {
description: 'Disallows awaiting a value that is not a Promise',
category: 'Functionality',
recommended: 'error',
extraDescription: [util.tslintRule('await-promise')],
url: util.metaDocsUrl('await-promise')
},
fixable: null,
messages: {
await: 'Invalid `await` of a non-Promise value.',
forOf: 'Invalid `for-await-of` of a non-AsyncIterable value.'
},
schema: [
{
type: 'object',
properties: {
allowedPromiseNames: {
type: 'array',
items: {
type: 'string'
}
}
},
additionalProperties: false
}
],
type: 'problem'
},
create(context) {
const options = util.applyDefault(defaultOptions, context.options)[0];
const allowedAsyncIterableNames = new Set([
'AsyncIterable',
'AsyncIterableIterator'
]);
const allowedPromiseNames = new Set([
'Promise',
...options.allowedPromiseNames
]);
const parserServices = util.getParserServices(context);
const checker = parserServices.program.getTypeChecker();
function validateNode(node, allowedSymbolNames, messageId) {
const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);
const type = checker.getTypeAtLocation(originalNode.expression);
if (!containsType(type, allowedSymbolNames)) {
context.report({
messageId,
node
});
}
}
return {
AwaitExpression(node) {
validateNode(node, allowedPromiseNames, 'await');
},
ForOfStatement(node) {
if (node.await) {
validateNode(node, allowedAsyncIterableNames, 'forOf');
}
}
};
}
};
/**
* @param {string} type Type being awaited upon.
* @param {Set<string>} allowedNames Symbol names being checked for.
*/
function containsType(type, allowedNames) {
if (tsutils.isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown)) {
return true;
}
if (tsutils.isTypeReference(type)) {
type = type.target;
}
if (
typeof type.symbol !== 'undefined' &&
allowedNames.has(type.symbol.name)
) {
return true;
}
if (tsutils.isUnionOrIntersectionType(type)) {
return type.types.some(t => containsType(t, allowedNames));
}
const bases = type.getBaseTypes();
return (
typeof bases !== 'undefined' &&
bases.some(t => containsType(t, allowedNames))
);
}