Skip to content

Commit dbf109f

Browse files
authored
Merge pull request #357 from golopot/patch-5
refactor(`require-returns`): simplify util.hasReturnValue
2 parents 215fc28 + 0caab6a commit dbf109f

File tree

6 files changed

+198
-231
lines changed

6 files changed

+198
-231
lines changed

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6394,6 +6394,20 @@ function quux () {
63946394
}
63956395
// Settings: {"jsdoc":{"tagNamePreference":{"returns":false}}}
63966396
// Message: Unexpected tag `@returns`
6397+
6398+
/**
6399+
* @returns {string}
6400+
*/
6401+
function f () {
6402+
function g() {
6403+
return 'foo'
6404+
}
6405+
6406+
() => {
6407+
return 5
6408+
}
6409+
}
6410+
// Message: JSDoc @returns declaration present but return expression not available in function.
63976411
````
63986412

63996413
The following patterns are not considered problems:
@@ -6610,6 +6624,43 @@ function quux () {
66106624
return;
66116625
}
66126626

6627+
/**
6628+
* @returns {true}
6629+
*/
6630+
function quux () {
6631+
for (const a in b) {
6632+
return true;
6633+
}
6634+
}
6635+
6636+
/**
6637+
* @returns {true}
6638+
*/
6639+
function quux () {
6640+
for (let i=0; i<n; i+=1) {
6641+
return true;
6642+
}
6643+
}
6644+
6645+
/**
6646+
* @returns {true}
6647+
*/
6648+
function quux () {
6649+
while(true) {
6650+
return true
6651+
}
6652+
}
6653+
6654+
/**
6655+
* @returns {true}
6656+
*/
6657+
function quux () {
6658+
do {
6659+
return true
6660+
}
6661+
while(true)
6662+
}
6663+
66136664
/**
66146665
* @returns {true}
66156666
*/

src/iterateJsdoc.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,8 @@ const getUtils = (
201201
return jsdocUtils.hasDefinedTypeReturnTag(tag);
202202
};
203203

204-
utils.hasReturnValue = (ignoreAsync = false) => {
205-
return jsdocUtils.hasReturnValue(node, context, ignoreAsync);
204+
utils.hasReturnValue = () => {
205+
return jsdocUtils.hasReturnValue(node);
206206
};
207207

208208
utils.isAsync = () => {

src/jsdocUtils.js

Lines changed: 52 additions & 226 deletions
Original file line numberDiff line numberDiff line change
@@ -238,237 +238,63 @@ const isTagWithType = (tagName) => {
238238
return tagsWithTypes.includes(tagName);
239239
};
240240

241-
const LOOP_STATEMENTS = ['WhileStatement', 'DoWhileStatement', 'ForStatement', 'ForInStatement', 'ForOfStatement'];
242-
243-
const STATEMENTS_WITH_CHILDREN = [
244-
'@loop',
245-
'SwitchStatement',
246-
'IfStatement',
247-
'BlockStatement',
248-
'TryStatement',
249-
'WithStatement'
250-
];
251-
252-
const RETURNFREE_STATEMENTS = [
253-
'VariableDeclaration',
254-
'ThrowStatement',
255-
'FunctionDeclaration',
256-
'BreakStatement',
257-
'ContinueStatement',
258-
'LabeledStatement',
259-
'DebuggerStatement',
260-
'EmptyStatement',
261-
'ThrowStatement',
262-
'ExpressionStatement'
263-
];
264-
265-
const ENTRY_POINTS = ['FunctionDeclaration', 'ArrowFunctionExpression', 'FunctionExpression'];
266-
267-
/* eslint-disable sort-keys */
268-
const lookupTable = {
269-
ReturnStatement: {
270-
is (node) {
271-
return node.type === 'ReturnStatement';
272-
},
273-
check (node) {
274-
/* istanbul ignore next */
275-
if (!lookupTable.ReturnStatement.is(node)) {
276-
return false;
277-
}
278-
279-
// A return without any arguments just exits the function
280-
// and is typically not documented at all in jsdoc.
281-
if (node.argument === null) {
282-
return false;
283-
}
284-
285-
return true;
286-
}
287-
},
288-
WithStatement: {
289-
is (node) {
290-
return node.type === 'WithStatement';
291-
},
292-
check (node, context) {
293-
return lookupTable.BlockStatement.check(node.body, context);
294-
}
295-
},
296-
IfStatement: {
297-
is (node) {
298-
return node.type === 'IfStatement';
299-
},
300-
check (node) {
301-
/* istanbul ignore next */
302-
if (!lookupTable.IfStatement.is(node)) {
303-
return false;
304-
}
305-
306-
if (lookupTable['@default'].check(node.consequent)) {
307-
return true;
308-
}
309-
310-
if (node.alternate && lookupTable['@default'].check(node.alternate)) {
311-
return true;
312-
}
313-
314-
return false;
315-
}
316-
},
317-
'@loop': {
318-
is (node) {
319-
return LOOP_STATEMENTS.includes(node.type);
320-
},
321-
check (node) {
322-
return lookupTable['@default'].check(node.body);
323-
}
324-
},
325-
SwitchStatement: {
326-
is (node) {
327-
return node.type === 'SwitchStatement';
328-
},
329-
check (node) {
330-
for (const item of node.cases) {
331-
for (const statement of item.consequent) {
332-
if (lookupTable['@default'].check(statement)) {
333-
return true;
334-
}
335-
}
336-
}
337-
338-
return false;
339-
}
340-
},
341-
TryStatement: {
342-
is (node) {
343-
return node.type === 'TryStatement';
344-
},
345-
check (node) {
346-
/* istanbul ignore next */
347-
if (!lookupTable.TryStatement.is(node)) {
348-
return false;
349-
}
350-
351-
if (lookupTable.BlockStatement.check(node.block)) {
352-
return true;
353-
}
354-
355-
if (node.handler && node.handler.body) {
356-
if (lookupTable['@default'].check(node.handler.body)) {
357-
return true;
358-
}
359-
}
360-
if (lookupTable.BlockStatement.check(node.finalizer)) {
361-
return true;
362-
}
363-
364-
return false;
365-
}
366-
},
367-
BlockStatement: {
368-
is (node) {
369-
return node.type === 'BlockStatement';
370-
},
371-
check (node, context) {
372-
// E.g. the catch block statement is optional.
373-
/* istanbul ignore next */
374-
if (typeof node === 'undefined' || node === null) {
375-
return false;
376-
}
377-
378-
/* istanbul ignore next */
379-
if (!lookupTable.BlockStatement.is(node)) {
380-
return false;
381-
}
382-
383-
for (const item of node.body) {
384-
if (lookupTable['@default'].check(item, context)) {
385-
return true;
386-
}
387-
}
388-
389-
return false;
390-
}
391-
},
392-
FunctionExpression: {
393-
is (node) {
394-
return node.type === 'FunctionExpression';
395-
},
396-
check (node, context, ignoreAsync) {
397-
return !ignoreAsync && node.async || lookupTable.BlockStatement.check(node.body, context);
398-
}
399-
},
400-
ArrowFunctionExpression: {
401-
is (node) {
402-
return node.type === 'ArrowFunctionExpression';
403-
},
404-
check (node, context, ignoreAsync) {
405-
// An expression always has a return value.
406-
return node.expression ||
407-
!ignoreAsync && node.async ||
408-
lookupTable.BlockStatement.check(node.body, context);
409-
}
410-
},
411-
FunctionDeclaration: {
412-
is (node) {
413-
return node.type === 'FunctionDeclaration';
414-
},
415-
check (node, context, ignoreAsync) {
416-
return !ignoreAsync && node.async || lookupTable.BlockStatement.check(node.body, context);
417-
}
418-
},
419-
'@default': {
420-
check (node, context) {
421-
// In case it is a `ReturnStatement`, we found what we were looking for
422-
if (lookupTable.ReturnStatement.is(node)) {
423-
return lookupTable.ReturnStatement.check(node, context);
424-
}
425-
426-
// In case the element has children, we need to traverse them.
427-
// Examples are BlockStatement, Choices, TryStatement, Loops, ...
428-
for (const item of STATEMENTS_WITH_CHILDREN) {
429-
if (lookupTable[item].is(node)) {
430-
return lookupTable[item].check(node, context);
431-
}
432-
}
433-
434-
// Everything else cannot return anything.
435-
/* istanbul ignore next */
436-
if (RETURNFREE_STATEMENTS.includes(node.type)) {
437-
return false;
438-
}
439-
440-
/* istanbul ignore next */
441-
// If we end up here, we stumbled upon an unknown element.
442-
// Most likely it is enough to add it to the blacklist.
443-
//
444-
// throw new Error('Unknown node type: ' + node.type);
445-
return false;
446-
}
447-
}
448-
};
449-
450241
/**
451-
* Checks if the source code returns a return value.
452-
* It traverses the parsed source code and returns as
453-
* soon as it stumbles upon the first return statement.
242+
* Checks if a node has a return statement. Void return does not count.
454243
*
455244
* @param {object} node
456-
* the node which should be checked.
457-
* @param {object} context
458-
* @param {boolean} ignoreAsync
459-
* ignore implicit async return.
460245
* @returns {boolean}
461-
* true in case the code returns a return value
462246
*/
463-
const hasReturnValue = (node, context, ignoreAsync) => {
464-
// Loop through all of our entry points
465-
for (const item of ENTRY_POINTS) {
466-
if (lookupTable[item].is(node)) {
467-
return lookupTable[item].check(node, context, ignoreAsync);
247+
// eslint-disable-next-line complexity
248+
const hasReturnValue = (node) => {
249+
if (!node) {
250+
return false;
251+
}
252+
switch (node.type) {
253+
case 'FunctionExpression':
254+
case 'FunctionDeclaration':
255+
case 'ArrowFunctionExpression': {
256+
return node.expression || hasReturnValue(node.body);
257+
}
258+
case 'BlockStatement': {
259+
return node.body.some((bodyNode) => {
260+
return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode);
261+
});
262+
}
263+
case 'WhileStatement':
264+
case 'DoWhileStatement':
265+
case 'ForStatement':
266+
case 'ForInStatement':
267+
case 'ForOfStatement':
268+
case 'WithStatement': {
269+
return hasReturnValue(node.body);
270+
}
271+
case 'IfStatement': {
272+
return hasReturnValue(node.consequent) || hasReturnValue(node.alternate);
273+
}
274+
case 'TryStatement': {
275+
return hasReturnValue(node.block) ||
276+
hasReturnValue(node.handler && node.handler.body) ||
277+
hasReturnValue(node.finalizer);
278+
}
279+
case 'SwitchStatement': {
280+
return node.cases.some(
281+
(someCase) => {
282+
return someCase.consequent.some(hasReturnValue);
283+
}
284+
);
285+
}
286+
case 'ReturnStatement': {
287+
// void return does not count.
288+
if (node.argument === null) {
289+
return false;
468290
}
291+
292+
return true;
293+
}
294+
default: {
295+
return false;
296+
}
469297
}
470-
/* istanbul ignore next */
471-
throw new Error(`Unknown element ${node.type}`);
472298
};
473299

474300
/** @param {string} tag */
@@ -553,8 +379,8 @@ const getTagsByType = (tags, tagPreference) => {
553379
});
554380

555381
return {
556-
tagsWithoutNames,
557-
tagsWithNames
382+
tagsWithNames,
383+
tagsWithoutNames
558384
};
559385
};
560386

0 commit comments

Comments
 (0)