Skip to content

Commit c3b56c6

Browse files
chiawendtl1bbcsg
authored andcommitted
refactor(require-returns): simplify util.hasReturnValue
1 parent e456c50 commit c3b56c6

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
@@ -218,8 +218,8 @@ const getUtils = (
218218
return jsdocUtils.hasDefinedTypeReturnTag(tag);
219219
};
220220

221-
utils.hasReturnValue = (ignoreAsync = false) => {
222-
return jsdocUtils.hasReturnValue(node, context, ignoreAsync);
221+
utils.hasReturnValue = () => {
222+
return jsdocUtils.hasReturnValue(node);
223223
};
224224

225225
utils.isAsync = () => {

src/jsdocUtils.js

Lines changed: 52 additions & 226 deletions
Original file line numberDiff line numberDiff line change
@@ -269,237 +269,63 @@ const isTagWithMandatoryNamepathOrType = (tagName) => {
269269
return tagsWithMandatoryNamepathOrType.includes(tagName);
270270
};
271271

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

505331
/** @param {string} tag */
@@ -584,8 +410,8 @@ const getTagsByType = (tags, tagPreference) => {
584410
});
585411

586412
return {
587-
tagsWithoutNames,
588-
tagsWithNames
413+
tagsWithNames,
414+
tagsWithoutNames
589415
};
590416
};
591417

0 commit comments

Comments
 (0)