|
1 |
| -import { |
2 |
| - TSESTree, |
3 |
| - AST_NODE_TYPES, |
4 |
| - AST_TOKEN_TYPES, |
5 |
| -} from '@typescript-eslint/experimental-utils'; |
| 1 | +import { TSESTree } from '@typescript-eslint/experimental-utils'; |
6 | 2 | import * as util from '../util';
|
| 3 | +import { |
| 4 | + checkFunctionReturnType, |
| 5 | + checkFunctionExpressionReturnType, |
| 6 | +} from '../util/explicitReturnTypeUtils'; |
7 | 7 |
|
8 | 8 | type Options = [
|
9 | 9 | {
|
@@ -60,303 +60,27 @@ export default util.createRule<Options, MessageIds>({
|
60 | 60 | create(context, [options]) {
|
61 | 61 | const sourceCode = context.getSourceCode();
|
62 | 62 |
|
63 |
| - /** |
64 |
| - * Returns start column position |
65 |
| - * @param node |
66 |
| - */ |
67 |
| - function getLocStart( |
68 |
| - node: |
69 |
| - | TSESTree.ArrowFunctionExpression |
70 |
| - | TSESTree.FunctionDeclaration |
71 |
| - | TSESTree.FunctionExpression, |
72 |
| - ): TSESTree.LineAndColumnData { |
73 |
| - /* highlight method name */ |
74 |
| - const parent = node.parent; |
75 |
| - if ( |
76 |
| - parent && |
77 |
| - (parent.type === AST_NODE_TYPES.MethodDefinition || |
78 |
| - (parent.type === AST_NODE_TYPES.Property && parent.method)) |
79 |
| - ) { |
80 |
| - return parent.loc.start; |
81 |
| - } |
82 |
| - |
83 |
| - return node.loc.start; |
84 |
| - } |
85 |
| - |
86 |
| - /** |
87 |
| - * Returns end column position |
88 |
| - * @param node |
89 |
| - */ |
90 |
| - function getLocEnd( |
91 |
| - node: |
92 |
| - | TSESTree.ArrowFunctionExpression |
93 |
| - | TSESTree.FunctionDeclaration |
94 |
| - | TSESTree.FunctionExpression, |
95 |
| - ): TSESTree.LineAndColumnData { |
96 |
| - /* highlight `=>` */ |
97 |
| - if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) { |
98 |
| - return sourceCode.getTokenBefore( |
99 |
| - node.body, |
100 |
| - token => |
101 |
| - token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=>', |
102 |
| - )!.loc.end; |
103 |
| - } |
104 |
| - |
105 |
| - return sourceCode.getTokenBefore(node.body!)!.loc.end; |
106 |
| - } |
107 |
| - |
108 |
| - /** |
109 |
| - * Checks if a node is a constructor. |
110 |
| - * @param node The node to check |
111 |
| - */ |
112 |
| - function isConstructor(node: TSESTree.Node | undefined): boolean { |
113 |
| - return ( |
114 |
| - !!node && |
115 |
| - node.type === AST_NODE_TYPES.MethodDefinition && |
116 |
| - node.kind === 'constructor' |
117 |
| - ); |
118 |
| - } |
119 |
| - |
120 |
| - /** |
121 |
| - * Checks if a node is a setter. |
122 |
| - */ |
123 |
| - function isSetter(node: TSESTree.Node | undefined): boolean { |
124 |
| - return ( |
125 |
| - !!node && |
126 |
| - (node.type === AST_NODE_TYPES.MethodDefinition || |
127 |
| - node.type === AST_NODE_TYPES.Property) && |
128 |
| - node.kind === 'set' |
129 |
| - ); |
130 |
| - } |
131 |
| - |
132 |
| - /** |
133 |
| - * Checks if a node is a variable declarator with a type annotation. |
134 |
| - * `const x: Foo = ...` |
135 |
| - */ |
136 |
| - function isVariableDeclaratorWithTypeAnnotation( |
137 |
| - node: TSESTree.Node, |
138 |
| - ): boolean { |
139 |
| - return ( |
140 |
| - node.type === AST_NODE_TYPES.VariableDeclarator && |
141 |
| - !!node.id.typeAnnotation |
142 |
| - ); |
143 |
| - } |
144 |
| - |
145 |
| - /** |
146 |
| - * Checks if a node is a class property with a type annotation. |
147 |
| - * `public x: Foo = ...` |
148 |
| - */ |
149 |
| - function isClassPropertyWithTypeAnnotation(node: TSESTree.Node): boolean { |
150 |
| - return ( |
151 |
| - node.type === AST_NODE_TYPES.ClassProperty && !!node.typeAnnotation |
152 |
| - ); |
153 |
| - } |
154 |
| - |
155 |
| - /** |
156 |
| - * Checks if a node belongs to: |
157 |
| - * new Foo(() => {}) |
158 |
| - * ^^^^^^^^ |
159 |
| - */ |
160 |
| - function isConstructorArgument(parent: TSESTree.Node): boolean { |
161 |
| - return parent.type === AST_NODE_TYPES.NewExpression; |
162 |
| - } |
163 |
| - |
164 |
| - /** |
165 |
| - * Checks if a node belongs to: |
166 |
| - * `const x: Foo = { prop: () => {} }` |
167 |
| - * `const x = { prop: () => {} } as Foo` |
168 |
| - * `const x = <Foo>{ prop: () => {} }` |
169 |
| - */ |
170 |
| - function isPropertyOfObjectWithType( |
171 |
| - property: TSESTree.Node | undefined, |
172 |
| - ): boolean { |
173 |
| - if (!property || property.type !== AST_NODE_TYPES.Property) { |
174 |
| - return false; |
175 |
| - } |
176 |
| - const objectExpr = property.parent; // this shouldn't happen, checking just in case |
177 |
| - /* istanbul ignore if */ if ( |
178 |
| - !objectExpr || |
179 |
| - objectExpr.type !== AST_NODE_TYPES.ObjectExpression |
180 |
| - ) { |
181 |
| - return false; |
182 |
| - } |
183 |
| - |
184 |
| - const parent = objectExpr.parent; // this shouldn't happen, checking just in case |
185 |
| - /* istanbul ignore if */ if (!parent) { |
186 |
| - return false; |
187 |
| - } |
188 |
| - |
189 |
| - return ( |
190 |
| - util.isTypeAssertion(parent) || |
191 |
| - isClassPropertyWithTypeAnnotation(parent) || |
192 |
| - isVariableDeclaratorWithTypeAnnotation(parent) || |
193 |
| - isFunctionArgument(parent) |
194 |
| - ); |
195 |
| - } |
196 |
| - |
197 |
| - /** |
198 |
| - * Checks if a function belongs to: |
199 |
| - * `() => () => ...` |
200 |
| - * `() => function () { ... }` |
201 |
| - * `() => { return () => ... }` |
202 |
| - * `() => { return function () { ... } }` |
203 |
| - * `function fn() { return () => ... }` |
204 |
| - * `function fn() { return function() { ... } }` |
205 |
| - */ |
206 |
| - function doesImmediatelyReturnFunctionExpression({ |
207 |
| - body, |
208 |
| - }: |
209 |
| - | TSESTree.ArrowFunctionExpression |
210 |
| - | TSESTree.FunctionDeclaration |
211 |
| - | TSESTree.FunctionExpression): boolean { |
212 |
| - // Should always have a body; really checking just in case |
213 |
| - /* istanbul ignore if */ if (!body) { |
214 |
| - return false; |
215 |
| - } |
216 |
| - |
217 |
| - // Check if body is a block with a single statement |
218 |
| - if ( |
219 |
| - body.type === AST_NODE_TYPES.BlockStatement && |
220 |
| - body.body.length === 1 |
221 |
| - ) { |
222 |
| - const [statement] = body.body; |
223 |
| - |
224 |
| - // Check if that statement is a return statement with an argument |
225 |
| - if ( |
226 |
| - statement.type === AST_NODE_TYPES.ReturnStatement && |
227 |
| - !!statement.argument |
228 |
| - ) { |
229 |
| - // If so, check that returned argument as body |
230 |
| - body = statement.argument; |
231 |
| - } |
232 |
| - } |
233 |
| - |
234 |
| - // Check if the body being returned is a function expression |
235 |
| - return ( |
236 |
| - body.type === AST_NODE_TYPES.ArrowFunctionExpression || |
237 |
| - body.type === AST_NODE_TYPES.FunctionExpression |
238 |
| - ); |
239 |
| - } |
240 |
| - |
241 |
| - /** |
242 |
| - * Checks if a node belongs to: |
243 |
| - * `foo(() => 1)` |
244 |
| - */ |
245 |
| - function isFunctionArgument( |
246 |
| - parent: TSESTree.Node, |
247 |
| - callee?: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, |
248 |
| - ): boolean { |
249 |
| - return ( |
250 |
| - (parent.type === AST_NODE_TYPES.CallExpression || |
251 |
| - parent.type === AST_NODE_TYPES.OptionalCallExpression) && |
252 |
| - // make sure this isn't an IIFE |
253 |
| - parent.callee !== callee |
254 |
| - ); |
255 |
| - } |
256 |
| - |
257 |
| - /** |
258 |
| - * Checks if a function belongs to: |
259 |
| - * `() => ({ action: 'xxx' }) as const` |
260 |
| - */ |
261 |
| - function returnsConstAssertionDirectly( |
262 |
| - node: TSESTree.ArrowFunctionExpression, |
263 |
| - ): boolean { |
264 |
| - const { body } = node; |
265 |
| - if (util.isTypeAssertion(body)) { |
266 |
| - const { typeAnnotation } = body; |
267 |
| - if (typeAnnotation.type === AST_NODE_TYPES.TSTypeReference) { |
268 |
| - const { typeName } = typeAnnotation; |
269 |
| - if ( |
270 |
| - typeName.type === AST_NODE_TYPES.Identifier && |
271 |
| - typeName.name === 'const' |
272 |
| - ) { |
273 |
| - return true; |
274 |
| - } |
275 |
| - } |
276 |
| - } |
277 |
| - |
278 |
| - return false; |
279 |
| - } |
280 |
| - |
281 |
| - /** |
282 |
| - * Checks if a function declaration/expression has a return type. |
283 |
| - */ |
284 |
| - function checkFunctionReturnType( |
285 |
| - node: |
286 |
| - | TSESTree.ArrowFunctionExpression |
287 |
| - | TSESTree.FunctionDeclaration |
288 |
| - | TSESTree.FunctionExpression, |
289 |
| - ): void { |
290 |
| - if ( |
291 |
| - options.allowHigherOrderFunctions && |
292 |
| - doesImmediatelyReturnFunctionExpression(node) |
293 |
| - ) { |
294 |
| - return; |
295 |
| - } |
296 |
| - |
297 |
| - if ( |
298 |
| - node.returnType || |
299 |
| - isConstructor(node.parent) || |
300 |
| - isSetter(node.parent) |
301 |
| - ) { |
302 |
| - return; |
303 |
| - } |
304 |
| - |
305 |
| - context.report({ |
306 |
| - node, |
307 |
| - loc: { start: getLocStart(node), end: getLocEnd(node) }, |
308 |
| - messageId: 'missingReturnType', |
309 |
| - }); |
310 |
| - } |
311 |
| - |
312 |
| - /** |
313 |
| - * Checks if a function declaration/expression has a return type. |
314 |
| - */ |
315 |
| - function checkFunctionExpressionReturnType( |
316 |
| - node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, |
317 |
| - ): void { |
318 |
| - // Should always have a parent; checking just in case |
319 |
| - /* istanbul ignore else */ if (node.parent) { |
320 |
| - if (options.allowTypedFunctionExpressions) { |
321 |
| - if ( |
322 |
| - util.isTypeAssertion(node.parent) || |
323 |
| - isVariableDeclaratorWithTypeAnnotation(node.parent) || |
324 |
| - isClassPropertyWithTypeAnnotation(node.parent) || |
325 |
| - isPropertyOfObjectWithType(node.parent) || |
326 |
| - isFunctionArgument(node.parent, node) || |
327 |
| - isConstructorArgument(node.parent) |
328 |
| - ) { |
329 |
| - return; |
330 |
| - } |
331 |
| - } |
332 |
| - |
333 |
| - if ( |
334 |
| - options.allowExpressions && |
335 |
| - node.parent.type !== AST_NODE_TYPES.VariableDeclarator && |
336 |
| - node.parent.type !== AST_NODE_TYPES.MethodDefinition && |
337 |
| - node.parent.type !== AST_NODE_TYPES.ExportDefaultDeclaration && |
338 |
| - node.parent.type !== AST_NODE_TYPES.ClassProperty |
339 |
| - ) { |
340 |
| - return; |
341 |
| - } |
342 |
| - } |
343 |
| - |
344 |
| - // https://github.com/typescript-eslint/typescript-eslint/issues/653 |
345 |
| - if ( |
346 |
| - node.type === AST_NODE_TYPES.ArrowFunctionExpression && |
347 |
| - options.allowDirectConstAssertionInArrowFunctions && |
348 |
| - returnsConstAssertionDirectly(node) |
349 |
| - ) { |
350 |
| - return; |
351 |
| - } |
352 |
| - |
353 |
| - checkFunctionReturnType(node); |
354 |
| - } |
355 |
| - |
356 | 63 | return {
|
357 |
| - ArrowFunctionExpression: checkFunctionExpressionReturnType, |
358 |
| - FunctionDeclaration: checkFunctionReturnType, |
359 |
| - FunctionExpression: checkFunctionExpressionReturnType, |
| 64 | + 'ArrowFunctionExpression, FunctionExpression'( |
| 65 | + node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, |
| 66 | + ): void { |
| 67 | + checkFunctionExpressionReturnType(node, options, sourceCode, loc => |
| 68 | + context.report({ |
| 69 | + node, |
| 70 | + loc, |
| 71 | + messageId: 'missingReturnType', |
| 72 | + }), |
| 73 | + ); |
| 74 | + }, |
| 75 | + FunctionDeclaration(node): void { |
| 76 | + checkFunctionReturnType(node, options, sourceCode, loc => |
| 77 | + context.report({ |
| 78 | + node, |
| 79 | + loc, |
| 80 | + messageId: 'missingReturnType', |
| 81 | + }), |
| 82 | + ); |
| 83 | + }, |
360 | 84 | };
|
361 | 85 | },
|
362 | 86 | });
|
0 commit comments