/
no-unsafe-return.ts
187 lines (171 loc) · 6.04 KB
/
no-unsafe-return.ts
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
import {
TSESTree,
AST_NODE_TYPES,
} from '@typescript-eslint/experimental-utils';
import * as tsutils from 'tsutils';
import * as util from '../util';
import { getThisExpression } from '../util';
export default util.createRule({
name: 'no-unsafe-return',
meta: {
type: 'problem',
docs: {
description: 'Disallows returning any from a function',
category: 'Possible Errors',
recommended: 'error',
requiresTypeChecking: true,
},
messages: {
unsafeReturn: 'Unsafe return of an `{{type}}` typed value.',
unsafeReturnThis: [
'Unsafe return of an `{{type}}` typed value. `this` is typed as `any`.',
'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.',
].join('\n'),
unsafeReturnAssignment:
'Unsafe return of type `{{sender}}` from function with return type `{{receiver}}`.',
},
schema: [],
},
defaultOptions: [],
create(context) {
const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context);
const checker = program.getTypeChecker();
const compilerOptions = program.getCompilerOptions();
const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled(
compilerOptions,
'noImplicitThis',
);
function getParentFunctionNode(
node: TSESTree.Node,
):
| TSESTree.ArrowFunctionExpression
| TSESTree.FunctionDeclaration
| TSESTree.FunctionExpression
| null {
let current = node.parent;
while (current) {
if (
current.type === AST_NODE_TYPES.ArrowFunctionExpression ||
current.type === AST_NODE_TYPES.FunctionDeclaration ||
current.type === AST_NODE_TYPES.FunctionExpression
) {
return current;
}
current = current.parent;
}
// this shouldn't happen in correct code, but someone may attempt to parse bad code
// the parser won't error, so we shouldn't throw here
/* istanbul ignore next */ return null;
}
function checkReturn(
returnNode: TSESTree.Node,
reportingNode: TSESTree.Node = returnNode,
): void {
const tsNode = esTreeNodeToTSNodeMap.get(returnNode);
const anyType = util.isAnyOrAnyArrayTypeDiscriminated(tsNode, checker);
const functionNode = getParentFunctionNode(returnNode);
/* istanbul ignore if */ if (!functionNode) {
return;
}
// function has an explicit return type, so ensure it's a safe return
const returnNodeType = util.getConstrainedTypeAtLocation(
checker,
esTreeNodeToTSNodeMap.get(returnNode),
);
const functionTSNode = esTreeNodeToTSNodeMap.get(functionNode);
// function expressions will not have their return type modified based on receiver typing
// so we have to use the contextual typing in these cases, i.e.
// const foo1: () => Set<string> = () => new Set<any>();
// the return type of the arrow function is Set<any> even though the variable is typed as Set<string>
let functionType = tsutils.isExpression(functionTSNode)
? util.getContextualType(checker, functionTSNode)
: checker.getTypeAtLocation(functionTSNode);
if (!functionType) {
functionType = checker.getTypeAtLocation(functionTSNode);
}
// If there is an explicit type annotation *and* that type matches the actual
// function return type, we shouldn't complain (it's intentional, even if unsafe)
if (functionTSNode.type) {
for (const signature of functionType.getCallSignatures()) {
if (returnNodeType === signature.getReturnType()) {
return;
}
}
}
if (anyType !== util.AnyType.Safe) {
// Allow cases when the declared return type of the function is either unknown or unknown[]
// and the function is returning any or any[].
for (const signature of functionType.getCallSignatures()) {
const functionReturnType = signature.getReturnType();
if (
anyType === util.AnyType.Any &&
util.isTypeUnknownType(functionReturnType)
) {
return;
}
if (
anyType === util.AnyType.AnyArray &&
util.isTypeUnknownArrayType(functionReturnType, checker)
) {
return;
}
}
let messageId: 'unsafeReturn' | 'unsafeReturnThis' = 'unsafeReturn';
if (!isNoImplicitThis) {
// `return this`
const thisExpression = getThisExpression(returnNode);
if (
thisExpression &&
util.isTypeAnyType(
util.getConstrainedTypeAtLocation(
checker,
esTreeNodeToTSNodeMap.get(thisExpression),
),
)
) {
messageId = 'unsafeReturnThis';
}
}
// If the function return type was not unknown/unknown[], mark usage as unsafeReturn.
return context.report({
node: reportingNode,
messageId,
data: {
type: anyType === util.AnyType.Any ? 'any' : 'any[]',
},
});
}
for (const signature of functionType.getCallSignatures()) {
const functionReturnType = signature.getReturnType();
const result = util.isUnsafeAssignment(
returnNodeType,
functionReturnType,
checker,
returnNode,
);
if (!result) {
return;
}
const { sender, receiver } = result;
return context.report({
node: reportingNode,
messageId: 'unsafeReturnAssignment',
data: {
sender: checker.typeToString(sender),
receiver: checker.typeToString(receiver),
},
});
}
}
return {
ReturnStatement(node): void {
const argument = node.argument;
if (!argument) {
return;
}
checkReturn(argument, node);
},
'ArrowFunctionExpression > :not(BlockStatement).body': checkReturn,
};
},
});