From 53b614440e4c57ebe5a59650f0fc50b91c880fc4 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Sat, 15 Oct 2022 18:27:57 -0700 Subject: [PATCH 1/4] fix(`require-returns-check`); checks that all branches of final node return; fixes #892 --- .README/rules/require-returns-check.md | 5 +- README.md | 166 +++++++++++-- src/iterateJsdoc.js | 4 +- src/rules/requireReturnsCheck.js | 1 + src/utils/hasReturnValue.js | 137 ++++++++++- test/rules/assertions/requireReturnsCheck.js | 237 +++++++++++++++++-- 6 files changed, 498 insertions(+), 52 deletions(-) diff --git a/.README/rules/require-returns-check.md b/.README/rules/require-returns-check.md index 286fe546c..a570d82de 100644 --- a/.README/rules/require-returns-check.md +++ b/.README/rules/require-returns-check.md @@ -2,10 +2,11 @@ Requires a return statement (or non-`undefined` Promise resolve value) in function bodies if a `@returns` tag (without a `void` or `undefined` type) -is specified in the function's jsdoc comment. +is specified in the function's JSDoc comment. Will also report `@returns {void}` and `@returns {undefined}` if `exemptAsync` -is set to `false` no non-`undefined` returned or resolved value is found. +is set to `false` and a non-`undefined` value is returned or a resolved value +is found. Also reports if `@returns {never}` is discovered with a return value. Will also report if multiple `@returns` tags are present. diff --git a/README.md b/README.md index 71e36b074..2d1ef1183 100644 --- a/README.md +++ b/README.md @@ -17208,10 +17208,11 @@ The following patterns are not considered problems: Requires a return statement (or non-`undefined` Promise resolve value) in function bodies if a `@returns` tag (without a `void` or `undefined` type) -is specified in the function's jsdoc comment. +is specified in the function's JSDoc comment. Will also report `@returns {void}` and `@returns {undefined}` if `exemptAsync` -is set to `false` no non-`undefined` returned or resolved value is found. +is set to `false` and a non-`undefined` value is returned or a resolved value +is found. Also reports if `@returns {never}` is discovered with a return value. Will also report if multiple `@returns` tags are present. @@ -17437,6 +17438,17 @@ export function readFixture(path: string): void; export function readFixture(path: string); // Message: JSDoc @returns declaration present but return expression not available in function. +/** + * @returns {SomeType} + */ +function quux (path) { + if (true) { + return; + } + return 15; +}; +// Message: JSDoc @returns declaration present but return expression not available in function. + /** * Reads a test fixture. * @@ -17449,6 +17461,87 @@ export function readFixture(path: string): void { return; }; // Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux () { + if (true) { + return true; + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux () { + if (true) { + } else { + return; + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux (someVar) { + switch (someVar) { + case 1: + return true; + case 2: + return; + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {boolean} + */ +const quux = (someVar) => { + if (someVar) { + return true; + } +}; +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux () { + try { + return true; + } catch (error) { + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux () { + try { + return true; + } catch (error) { + return true; + } finally { + return; + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux () { + try { + } catch (error) { + } finally { + return true; + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. ```` The following patterns are not considered problems: @@ -17614,7 +17707,7 @@ function quux () { return true; } catch (err) { } - return; + return true; } /** @@ -17625,7 +17718,7 @@ function quux () { } finally { return true; } - return; + return true; } /** @@ -17633,7 +17726,7 @@ function quux () { */ function quux () { try { - return; + return true; } catch (err) { } return true; @@ -17648,7 +17741,7 @@ function quux () { } catch (err) { return true; } - return; + return true; } /** @@ -17659,7 +17752,7 @@ function quux () { case 'abc': return true; } - return; + return true; } /** @@ -17668,7 +17761,7 @@ function quux () { function quux () { switch (true) { case 'abc': - return; + return true; } return true; } @@ -17680,7 +17773,7 @@ function quux () { for (const i of abc) { return true; } - return; + return true; } /** @@ -17696,7 +17789,7 @@ function quux () { * @returns {true} */ function quux () { - for (let i=0; i { * @returns {void} The file contents as buffer. */ export function readFixture(path: string); + +/** + * @returns {SomeType} + */ +function quux (path) { + if (true) { + return 5; + } + return 15; +}; + +/** + * @returns {*} Foo. + */ +const quux = () => new Promise((resolve) => { + resolve(3); +}); + +/** + * @returns {*} Foo. + */ +const quux = function () { + return new Promise((resolve) => { + resolve(3); + }); +}; ```` diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index 23a921023..aff6dc77f 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -680,8 +680,8 @@ const getUtils = ( return jsdocUtils.hasDefinedTypeTag(tag); }; - utils.hasValueOrExecutorHasNonEmptyResolveValue = (anyPromiseAsReturn) => { - return jsdocUtils.hasValueOrExecutorHasNonEmptyResolveValue(node, anyPromiseAsReturn); + utils.hasValueOrExecutorHasNonEmptyResolveValue = (anyPromiseAsReturn, allBranches) => { + return jsdocUtils.hasValueOrExecutorHasNonEmptyResolveValue(node, anyPromiseAsReturn, allBranches); }; utils.hasYieldValue = () => { diff --git a/src/rules/requireReturnsCheck.js b/src/rules/requireReturnsCheck.js index 2cf01cb89..abc7c6c19 100755 --- a/src/rules/requireReturnsCheck.js +++ b/src/rules/requireReturnsCheck.js @@ -96,6 +96,7 @@ export default iterateJsdoc(({ ) && !utils.hasValueOrExecutorHasNonEmptyResolveValue( exemptAsync, + true, ) && (!exemptGenerators || !node.generator) ) { report(`JSDoc @${tagName} declaration present but return expression not available in function.`); diff --git a/src/utils/hasReturnValue.js b/src/utils/hasReturnValue.js index 28c5e3c24..528edc140 100644 --- a/src/utils/hasReturnValue.js +++ b/src/utils/hasReturnValue.js @@ -19,6 +19,92 @@ const undefinedKeywords = new Set([ 'TSVoidKeyword', 'TSUndefinedKeyword', 'TSNeverKeyword', ]); +/** + * Checks if a node has a return statement. Void return does not count. + * + * @param {object} node + * @param {PromiseFilter} promFilter + * @returns {boolean|Node} + */ +// eslint-disable-next-line complexity +const allBrancheshaveReturnValues = (node, promFilter) => { + if (!node) { + return false; + } + + switch (node.type) { + case 'TSDeclareFunction': + case 'TSFunctionType': + case 'TSMethodSignature': { + const type = node?.returnType?.typeAnnotation?.type; + return type && !undefinedKeywords.has(type); + } + + // case 'MethodDefinition': + // return allBrancheshaveReturnValues(node.value, promFilter); + case 'FunctionExpression': + case 'FunctionDeclaration': + case 'ArrowFunctionExpression': { + return node.expression && (!isNewPromiseExpression(node.body) || !isVoidPromise(node.body)) || + allBrancheshaveReturnValues(node.body, promFilter); + } + + case 'BlockStatement': { + const lastBodyNode = node.body.slice(-1)[0]; + return allBrancheshaveReturnValues(lastBodyNode, promFilter); + } + + case 'LabeledStatement': + case 'WhileStatement': + case 'DoWhileStatement': + case 'ForStatement': + case 'ForInStatement': + case 'ForOfStatement': + case 'WithStatement': { + return allBrancheshaveReturnValues(node.body, promFilter); + } + + case 'IfStatement': { + return allBrancheshaveReturnValues(node.consequent, promFilter) && allBrancheshaveReturnValues(node.alternate, promFilter); + } + + case 'TryStatement': { + return allBrancheshaveReturnValues(node.block, promFilter) && + allBrancheshaveReturnValues(node.handler && node.handler.body, promFilter) && + allBrancheshaveReturnValues(node.finalizer, promFilter); + } + + case 'SwitchStatement': { + return node.cases.every( + (someCase) => { + return someCase.consequent.every((nde) => { + return allBrancheshaveReturnValues(nde, promFilter); + }); + }, + ); + } + + case 'ReturnStatement': { + // void return does not count. + if (node.argument === null) { + return false; + } + + if (promFilter && isNewPromiseExpression(node.argument)) { + // Let caller decide how to filter, but this is, at the least, + // a return of sorts and truthy + return promFilter(node.argument); + } + + return true; + } + + default: { + return false; + } + } +}; + /** * @callback PromiseFilter * @param {object} node @@ -29,11 +115,12 @@ const undefinedKeywords = new Set([ * Checks if a node has a return statement. Void return does not count. * * @param {object} node + * @param {boolean} throwOnNullReturn * @param {PromiseFilter} promFilter * @returns {boolean|Node} */ // eslint-disable-next-line complexity -const hasReturnValue = (node, promFilter) => { +const hasReturnValue = (node, throwOnNullReturn, promFilter) => { if (!node) { return false; } @@ -47,17 +134,17 @@ const hasReturnValue = (node, promFilter) => { } case 'MethodDefinition': - return hasReturnValue(node.value, promFilter); + return hasReturnValue(node.value, throwOnNullReturn, promFilter); case 'FunctionExpression': case 'FunctionDeclaration': case 'ArrowFunctionExpression': { return node.expression && (!isNewPromiseExpression(node.body) || !isVoidPromise(node.body)) || - hasReturnValue(node.body, promFilter); + hasReturnValue(node.body, throwOnNullReturn, promFilter); } case 'BlockStatement': { return node.body.some((bodyNode) => { - return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, promFilter); + return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, throwOnNullReturn, promFilter); }); } @@ -68,24 +155,25 @@ const hasReturnValue = (node, promFilter) => { case 'ForInStatement': case 'ForOfStatement': case 'WithStatement': { - return hasReturnValue(node.body, promFilter); + return hasReturnValue(node.body, throwOnNullReturn, promFilter); } case 'IfStatement': { - return hasReturnValue(node.consequent, promFilter) || hasReturnValue(node.alternate, promFilter); + return hasReturnValue(node.consequent, throwOnNullReturn, promFilter) || + hasReturnValue(node.alternate, throwOnNullReturn, promFilter); } case 'TryStatement': { - return hasReturnValue(node.block, promFilter) || - hasReturnValue(node.handler && node.handler.body, promFilter) || - hasReturnValue(node.finalizer, promFilter); + return hasReturnValue(node.block, throwOnNullReturn, promFilter) || + hasReturnValue(node.handler && node.handler.body, throwOnNullReturn, promFilter) || + hasReturnValue(node.finalizer, throwOnNullReturn, promFilter); } case 'SwitchStatement': { return node.cases.some( (someCase) => { return someCase.consequent.some((nde) => { - return hasReturnValue(nde, promFilter); + return hasReturnValue(nde, throwOnNullReturn, promFilter); }); }, ); @@ -94,6 +182,10 @@ const hasReturnValue = (node, promFilter) => { case 'ReturnStatement': { // void return does not count. if (node.argument === null) { + if (throwOnNullReturn) { + throw new Error('Null return'); + } + return false; } @@ -320,10 +412,31 @@ const hasNonEmptyResolverCall = (node, resolverName) => { * * @param {object} node * @param {boolean} anyPromiseAsReturn + * @param {boolean} allBranches * @returns {boolean} */ -const hasValueOrExecutorHasNonEmptyResolveValue = (node, anyPromiseAsReturn) => { - return hasReturnValue(node, (prom) => { +const hasValueOrExecutorHasNonEmptyResolveValue = (node, anyPromiseAsReturn, allBranches) => { + const hasReturnMethod = allBranches ? + (nde, promiseFilter) => { + try { + hasReturnValue(nde, true, promiseFilter); + } catch (error) { + // istanbul ignore else + if (error.message === 'Null return') { + return false; + } + + // istanbul ignore next + throw error; + } + + return allBrancheshaveReturnValues(nde, promiseFilter); + } : + (nde, promiseFilter) => { + return hasReturnValue(nde, false, promiseFilter); + }; + + return hasReturnMethod(node, (prom) => { if (anyPromiseAsReturn) { return true; } diff --git a/test/rules/assertions/requireReturnsCheck.js b/test/rules/assertions/requireReturnsCheck.js index f64b19323..c6bf9e8f5 100755 --- a/test/rules/assertions/requireReturnsCheck.js +++ b/test/rules/assertions/requireReturnsCheck.js @@ -411,6 +411,25 @@ export default { ], parser: require.resolve('@typescript-eslint/parser'), }, + { + code: ` + /** + * @returns {SomeType} + */ + function quux (path) { + if (true) { + return; + } + return 15; + }; + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, { code: ` /** @@ -433,6 +452,143 @@ export default { ], parser: require.resolve('@typescript-eslint/parser'), }, + { + code: ` + /** + * @returns {true} + */ + function quux () { + if (true) { + return true; + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {true} + */ + function quux () { + if (true) { + } else { + return; + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {true} + */ + function quux (someVar) { + switch (someVar) { + case 1: + return true; + case 2: + return; + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {boolean} + */ + const quux = (someVar) => { + if (someVar) { + return true; + } + }; + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {true} + */ + function quux () { + try { + return true; + } catch (error) { + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {true} + */ + function quux () { + try { + return true; + } catch (error) { + return true; + } finally { + return; + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {true} + */ + function quux () { + try { + } catch (error) { + } finally { + return true; + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, ], valid: [ { @@ -676,7 +832,7 @@ export default { return true; } catch (err) { } - return; + return true; } `, }, @@ -690,7 +846,7 @@ export default { } finally { return true; } - return; + return true; } `, }, @@ -701,7 +857,7 @@ export default { */ function quux () { try { - return; + return true; } catch (err) { } return true; @@ -719,7 +875,7 @@ export default { } catch (err) { return true; } - return; + return true; } `, }, @@ -733,7 +889,7 @@ export default { case 'abc': return true; } - return; + return true; } `, }, @@ -745,7 +901,7 @@ export default { function quux () { switch (true) { case 'abc': - return; + return true; } return true; } @@ -760,7 +916,7 @@ export default { for (const i of abc) { return true; } - return; + return true; } `, }, @@ -782,7 +938,7 @@ export default { * @returns {true} */ function quux () { - for (let i=0; i new Promise((resolve) => { + resolve(3); + }); + `, + }, + { + code: ` + /** + * @returns {*} Foo. + */ + const quux = function () { + return new Promise((resolve) => { + resolve(3); + }); + }; + `, + }, ], }; From ac7ebe3762e67b2e40bbb21f8e2e1e2afe358bf7 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Fri, 21 Oct 2022 14:39:42 -0700 Subject: [PATCH 2/4] chore: update devDeps. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 02624d5ff..01b43541f 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "description": "JSDoc linting rules for ESLint.", "devDependencies": { "@babel/cli": "^7.19.3", - "@babel/core": "^7.19.3", + "@babel/core": "^7.19.6", "@babel/eslint-parser": "^7.19.1", "@babel/node": "^7.19.1", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -32,7 +32,7 @@ "chai": "^4.3.6", "cross-env": "^7.0.3", "decamelize": "^5.0.1", - "eslint": "^8.25.0", + "eslint": "^8.26.0", "eslint-config-canonical": "~33.0.1", "gitdown": "^3.1.5", "glob": "^8.0.3", From bba377e8c1e62a77ad9189794a6f474399304ed2 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Sat, 22 Oct 2022 08:55:30 -0700 Subject: [PATCH 3/4] Revert "fix(`require-returns-check`); checks that all branches of final node return; fixes #892" This reverts commit 53b614440e4c57ebe5a59650f0fc50b91c880fc4. --- .README/rules/require-returns-check.md | 5 +- README.md | 166 ++----------- src/iterateJsdoc.js | 4 +- src/rules/requireReturnsCheck.js | 1 - src/utils/hasReturnValue.js | 137 +---------- test/rules/assertions/requireReturnsCheck.js | 237 ++----------------- 6 files changed, 52 insertions(+), 498 deletions(-) diff --git a/.README/rules/require-returns-check.md b/.README/rules/require-returns-check.md index a570d82de..286fe546c 100644 --- a/.README/rules/require-returns-check.md +++ b/.README/rules/require-returns-check.md @@ -2,11 +2,10 @@ Requires a return statement (or non-`undefined` Promise resolve value) in function bodies if a `@returns` tag (without a `void` or `undefined` type) -is specified in the function's JSDoc comment. +is specified in the function's jsdoc comment. Will also report `@returns {void}` and `@returns {undefined}` if `exemptAsync` -is set to `false` and a non-`undefined` value is returned or a resolved value -is found. Also reports if `@returns {never}` is discovered with a return value. +is set to `false` no non-`undefined` returned or resolved value is found. Will also report if multiple `@returns` tags are present. diff --git a/README.md b/README.md index 2d1ef1183..71e36b074 100644 --- a/README.md +++ b/README.md @@ -17208,11 +17208,10 @@ The following patterns are not considered problems: Requires a return statement (or non-`undefined` Promise resolve value) in function bodies if a `@returns` tag (without a `void` or `undefined` type) -is specified in the function's JSDoc comment. +is specified in the function's jsdoc comment. Will also report `@returns {void}` and `@returns {undefined}` if `exemptAsync` -is set to `false` and a non-`undefined` value is returned or a resolved value -is found. Also reports if `@returns {never}` is discovered with a return value. +is set to `false` no non-`undefined` returned or resolved value is found. Will also report if multiple `@returns` tags are present. @@ -17438,17 +17437,6 @@ export function readFixture(path: string): void; export function readFixture(path: string); // Message: JSDoc @returns declaration present but return expression not available in function. -/** - * @returns {SomeType} - */ -function quux (path) { - if (true) { - return; - } - return 15; -}; -// Message: JSDoc @returns declaration present but return expression not available in function. - /** * Reads a test fixture. * @@ -17461,87 +17449,6 @@ export function readFixture(path: string): void { return; }; // Message: JSDoc @returns declaration present but return expression not available in function. - -/** - * @returns {true} - */ -function quux () { - if (true) { - return true; - } -} -// Message: JSDoc @returns declaration present but return expression not available in function. - -/** - * @returns {true} - */ -function quux () { - if (true) { - } else { - return; - } -} -// Message: JSDoc @returns declaration present but return expression not available in function. - -/** - * @returns {true} - */ -function quux (someVar) { - switch (someVar) { - case 1: - return true; - case 2: - return; - } -} -// Message: JSDoc @returns declaration present but return expression not available in function. - -/** - * @returns {boolean} - */ -const quux = (someVar) => { - if (someVar) { - return true; - } -}; -// Message: JSDoc @returns declaration present but return expression not available in function. - -/** - * @returns {true} - */ -function quux () { - try { - return true; - } catch (error) { - } -} -// Message: JSDoc @returns declaration present but return expression not available in function. - -/** - * @returns {true} - */ -function quux () { - try { - return true; - } catch (error) { - return true; - } finally { - return; - } -} -// Message: JSDoc @returns declaration present but return expression not available in function. - -/** - * @returns {true} - */ -function quux () { - try { - } catch (error) { - } finally { - return true; - } -} -// Message: JSDoc @returns declaration present but return expression not available in function. ```` The following patterns are not considered problems: @@ -17707,7 +17614,7 @@ function quux () { return true; } catch (err) { } - return true; + return; } /** @@ -17718,7 +17625,7 @@ function quux () { } finally { return true; } - return true; + return; } /** @@ -17726,7 +17633,7 @@ function quux () { */ function quux () { try { - return true; + return; } catch (err) { } return true; @@ -17741,7 +17648,7 @@ function quux () { } catch (err) { return true; } - return true; + return; } /** @@ -17752,7 +17659,7 @@ function quux () { case 'abc': return true; } - return true; + return; } /** @@ -17761,7 +17668,7 @@ function quux () { function quux () { switch (true) { case 'abc': - return true; + return; } return true; } @@ -17773,7 +17680,7 @@ function quux () { for (const i of abc) { return true; } - return true; + return; } /** @@ -17789,16 +17696,7 @@ function quux () { * @returns {true} */ function quux () { - for (const a of b) { - return true; - } -} - -/** - * @returns {true} - */ -function quux () { - loop: for (const a of b) { + for (let i=0; i { * @returns {void} The file contents as buffer. */ export function readFixture(path: string); - -/** - * @returns {SomeType} - */ -function quux (path) { - if (true) { - return 5; - } - return 15; -}; - -/** - * @returns {*} Foo. - */ -const quux = () => new Promise((resolve) => { - resolve(3); -}); - -/** - * @returns {*} Foo. - */ -const quux = function () { - return new Promise((resolve) => { - resolve(3); - }); -}; ```` diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index aff6dc77f..23a921023 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -680,8 +680,8 @@ const getUtils = ( return jsdocUtils.hasDefinedTypeTag(tag); }; - utils.hasValueOrExecutorHasNonEmptyResolveValue = (anyPromiseAsReturn, allBranches) => { - return jsdocUtils.hasValueOrExecutorHasNonEmptyResolveValue(node, anyPromiseAsReturn, allBranches); + utils.hasValueOrExecutorHasNonEmptyResolveValue = (anyPromiseAsReturn) => { + return jsdocUtils.hasValueOrExecutorHasNonEmptyResolveValue(node, anyPromiseAsReturn); }; utils.hasYieldValue = () => { diff --git a/src/rules/requireReturnsCheck.js b/src/rules/requireReturnsCheck.js index abc7c6c19..2cf01cb89 100755 --- a/src/rules/requireReturnsCheck.js +++ b/src/rules/requireReturnsCheck.js @@ -96,7 +96,6 @@ export default iterateJsdoc(({ ) && !utils.hasValueOrExecutorHasNonEmptyResolveValue( exemptAsync, - true, ) && (!exemptGenerators || !node.generator) ) { report(`JSDoc @${tagName} declaration present but return expression not available in function.`); diff --git a/src/utils/hasReturnValue.js b/src/utils/hasReturnValue.js index 528edc140..28c5e3c24 100644 --- a/src/utils/hasReturnValue.js +++ b/src/utils/hasReturnValue.js @@ -19,92 +19,6 @@ const undefinedKeywords = new Set([ 'TSVoidKeyword', 'TSUndefinedKeyword', 'TSNeverKeyword', ]); -/** - * Checks if a node has a return statement. Void return does not count. - * - * @param {object} node - * @param {PromiseFilter} promFilter - * @returns {boolean|Node} - */ -// eslint-disable-next-line complexity -const allBrancheshaveReturnValues = (node, promFilter) => { - if (!node) { - return false; - } - - switch (node.type) { - case 'TSDeclareFunction': - case 'TSFunctionType': - case 'TSMethodSignature': { - const type = node?.returnType?.typeAnnotation?.type; - return type && !undefinedKeywords.has(type); - } - - // case 'MethodDefinition': - // return allBrancheshaveReturnValues(node.value, promFilter); - case 'FunctionExpression': - case 'FunctionDeclaration': - case 'ArrowFunctionExpression': { - return node.expression && (!isNewPromiseExpression(node.body) || !isVoidPromise(node.body)) || - allBrancheshaveReturnValues(node.body, promFilter); - } - - case 'BlockStatement': { - const lastBodyNode = node.body.slice(-1)[0]; - return allBrancheshaveReturnValues(lastBodyNode, promFilter); - } - - case 'LabeledStatement': - case 'WhileStatement': - case 'DoWhileStatement': - case 'ForStatement': - case 'ForInStatement': - case 'ForOfStatement': - case 'WithStatement': { - return allBrancheshaveReturnValues(node.body, promFilter); - } - - case 'IfStatement': { - return allBrancheshaveReturnValues(node.consequent, promFilter) && allBrancheshaveReturnValues(node.alternate, promFilter); - } - - case 'TryStatement': { - return allBrancheshaveReturnValues(node.block, promFilter) && - allBrancheshaveReturnValues(node.handler && node.handler.body, promFilter) && - allBrancheshaveReturnValues(node.finalizer, promFilter); - } - - case 'SwitchStatement': { - return node.cases.every( - (someCase) => { - return someCase.consequent.every((nde) => { - return allBrancheshaveReturnValues(nde, promFilter); - }); - }, - ); - } - - case 'ReturnStatement': { - // void return does not count. - if (node.argument === null) { - return false; - } - - if (promFilter && isNewPromiseExpression(node.argument)) { - // Let caller decide how to filter, but this is, at the least, - // a return of sorts and truthy - return promFilter(node.argument); - } - - return true; - } - - default: { - return false; - } - } -}; - /** * @callback PromiseFilter * @param {object} node @@ -115,12 +29,11 @@ const allBrancheshaveReturnValues = (node, promFilter) => { * Checks if a node has a return statement. Void return does not count. * * @param {object} node - * @param {boolean} throwOnNullReturn * @param {PromiseFilter} promFilter * @returns {boolean|Node} */ // eslint-disable-next-line complexity -const hasReturnValue = (node, throwOnNullReturn, promFilter) => { +const hasReturnValue = (node, promFilter) => { if (!node) { return false; } @@ -134,17 +47,17 @@ const hasReturnValue = (node, throwOnNullReturn, promFilter) => { } case 'MethodDefinition': - return hasReturnValue(node.value, throwOnNullReturn, promFilter); + return hasReturnValue(node.value, promFilter); case 'FunctionExpression': case 'FunctionDeclaration': case 'ArrowFunctionExpression': { return node.expression && (!isNewPromiseExpression(node.body) || !isVoidPromise(node.body)) || - hasReturnValue(node.body, throwOnNullReturn, promFilter); + hasReturnValue(node.body, promFilter); } case 'BlockStatement': { return node.body.some((bodyNode) => { - return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, throwOnNullReturn, promFilter); + return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, promFilter); }); } @@ -155,25 +68,24 @@ const hasReturnValue = (node, throwOnNullReturn, promFilter) => { case 'ForInStatement': case 'ForOfStatement': case 'WithStatement': { - return hasReturnValue(node.body, throwOnNullReturn, promFilter); + return hasReturnValue(node.body, promFilter); } case 'IfStatement': { - return hasReturnValue(node.consequent, throwOnNullReturn, promFilter) || - hasReturnValue(node.alternate, throwOnNullReturn, promFilter); + return hasReturnValue(node.consequent, promFilter) || hasReturnValue(node.alternate, promFilter); } case 'TryStatement': { - return hasReturnValue(node.block, throwOnNullReturn, promFilter) || - hasReturnValue(node.handler && node.handler.body, throwOnNullReturn, promFilter) || - hasReturnValue(node.finalizer, throwOnNullReturn, promFilter); + return hasReturnValue(node.block, promFilter) || + hasReturnValue(node.handler && node.handler.body, promFilter) || + hasReturnValue(node.finalizer, promFilter); } case 'SwitchStatement': { return node.cases.some( (someCase) => { return someCase.consequent.some((nde) => { - return hasReturnValue(nde, throwOnNullReturn, promFilter); + return hasReturnValue(nde, promFilter); }); }, ); @@ -182,10 +94,6 @@ const hasReturnValue = (node, throwOnNullReturn, promFilter) => { case 'ReturnStatement': { // void return does not count. if (node.argument === null) { - if (throwOnNullReturn) { - throw new Error('Null return'); - } - return false; } @@ -412,31 +320,10 @@ const hasNonEmptyResolverCall = (node, resolverName) => { * * @param {object} node * @param {boolean} anyPromiseAsReturn - * @param {boolean} allBranches * @returns {boolean} */ -const hasValueOrExecutorHasNonEmptyResolveValue = (node, anyPromiseAsReturn, allBranches) => { - const hasReturnMethod = allBranches ? - (nde, promiseFilter) => { - try { - hasReturnValue(nde, true, promiseFilter); - } catch (error) { - // istanbul ignore else - if (error.message === 'Null return') { - return false; - } - - // istanbul ignore next - throw error; - } - - return allBrancheshaveReturnValues(nde, promiseFilter); - } : - (nde, promiseFilter) => { - return hasReturnValue(nde, false, promiseFilter); - }; - - return hasReturnMethod(node, (prom) => { +const hasValueOrExecutorHasNonEmptyResolveValue = (node, anyPromiseAsReturn) => { + return hasReturnValue(node, (prom) => { if (anyPromiseAsReturn) { return true; } diff --git a/test/rules/assertions/requireReturnsCheck.js b/test/rules/assertions/requireReturnsCheck.js index c6bf9e8f5..f64b19323 100755 --- a/test/rules/assertions/requireReturnsCheck.js +++ b/test/rules/assertions/requireReturnsCheck.js @@ -411,25 +411,6 @@ export default { ], parser: require.resolve('@typescript-eslint/parser'), }, - { - code: ` - /** - * @returns {SomeType} - */ - function quux (path) { - if (true) { - return; - } - return 15; - }; - `, - errors: [ - { - line: 2, - message: 'JSDoc @returns declaration present but return expression not available in function.', - }, - ], - }, { code: ` /** @@ -452,143 +433,6 @@ export default { ], parser: require.resolve('@typescript-eslint/parser'), }, - { - code: ` - /** - * @returns {true} - */ - function quux () { - if (true) { - return true; - } - } - `, - errors: [ - { - line: 2, - message: 'JSDoc @returns declaration present but return expression not available in function.', - }, - ], - }, - { - code: ` - /** - * @returns {true} - */ - function quux () { - if (true) { - } else { - return; - } - } - `, - errors: [ - { - line: 2, - message: 'JSDoc @returns declaration present but return expression not available in function.', - }, - ], - }, - { - code: ` - /** - * @returns {true} - */ - function quux (someVar) { - switch (someVar) { - case 1: - return true; - case 2: - return; - } - } - `, - errors: [ - { - line: 2, - message: 'JSDoc @returns declaration present but return expression not available in function.', - }, - ], - }, - { - code: ` - /** - * @returns {boolean} - */ - const quux = (someVar) => { - if (someVar) { - return true; - } - }; - `, - errors: [ - { - line: 2, - message: 'JSDoc @returns declaration present but return expression not available in function.', - }, - ], - }, - { - code: ` - /** - * @returns {true} - */ - function quux () { - try { - return true; - } catch (error) { - } - } - `, - errors: [ - { - line: 2, - message: 'JSDoc @returns declaration present but return expression not available in function.', - }, - ], - }, - { - code: ` - /** - * @returns {true} - */ - function quux () { - try { - return true; - } catch (error) { - return true; - } finally { - return; - } - } - `, - errors: [ - { - line: 2, - message: 'JSDoc @returns declaration present but return expression not available in function.', - }, - ], - }, - { - code: ` - /** - * @returns {true} - */ - function quux () { - try { - } catch (error) { - } finally { - return true; - } - } - `, - errors: [ - { - line: 2, - message: 'JSDoc @returns declaration present but return expression not available in function.', - }, - ], - }, ], valid: [ { @@ -832,7 +676,7 @@ export default { return true; } catch (err) { } - return true; + return; } `, }, @@ -846,7 +690,7 @@ export default { } finally { return true; } - return true; + return; } `, }, @@ -857,7 +701,7 @@ export default { */ function quux () { try { - return true; + return; } catch (err) { } return true; @@ -875,7 +719,7 @@ export default { } catch (err) { return true; } - return true; + return; } `, }, @@ -889,7 +733,7 @@ export default { case 'abc': return true; } - return true; + return; } `, }, @@ -901,7 +745,7 @@ export default { function quux () { switch (true) { case 'abc': - return true; + return; } return true; } @@ -916,7 +760,7 @@ export default { for (const i of abc) { return true; } - return true; + return; } `, }, @@ -938,19 +782,7 @@ export default { * @returns {true} */ function quux () { - for (const a of b) { - return true; - } - } - `, - }, - { - code: ` - /** - * @returns {true} - */ - function quux () { - loop: for (const a of b) { + for (let i=0; i new Promise((resolve) => { - resolve(3); - }); - `, - }, - { - code: ` - /** - * @returns {*} Foo. - */ - const quux = function () { - return new Promise((resolve) => { - resolve(3); - }); - }; - `, - }, ], }; From bdd6af25d877d1e714458e7ad9b6b2d284ebc83a Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Sat, 22 Oct 2022 08:55:39 -0700 Subject: [PATCH 4/4] fix(`require-returns-check`): checks that all branches of final node return; fixes #892 This release expressed as a reversion of a commit (bba377e8c1e62a77ad9189794a6f474399304ed2) in order to fix the commit message to properly trigger a release. --- .README/rules/require-returns-check.md | 5 +- README.md | 166 +++++++++++-- src/iterateJsdoc.js | 4 +- src/rules/requireReturnsCheck.js | 1 + src/utils/hasReturnValue.js | 137 ++++++++++- test/rules/assertions/requireReturnsCheck.js | 237 +++++++++++++++++-- 6 files changed, 498 insertions(+), 52 deletions(-) diff --git a/.README/rules/require-returns-check.md b/.README/rules/require-returns-check.md index 286fe546c..a570d82de 100644 --- a/.README/rules/require-returns-check.md +++ b/.README/rules/require-returns-check.md @@ -2,10 +2,11 @@ Requires a return statement (or non-`undefined` Promise resolve value) in function bodies if a `@returns` tag (without a `void` or `undefined` type) -is specified in the function's jsdoc comment. +is specified in the function's JSDoc comment. Will also report `@returns {void}` and `@returns {undefined}` if `exemptAsync` -is set to `false` no non-`undefined` returned or resolved value is found. +is set to `false` and a non-`undefined` value is returned or a resolved value +is found. Also reports if `@returns {never}` is discovered with a return value. Will also report if multiple `@returns` tags are present. diff --git a/README.md b/README.md index 71e36b074..2d1ef1183 100644 --- a/README.md +++ b/README.md @@ -17208,10 +17208,11 @@ The following patterns are not considered problems: Requires a return statement (or non-`undefined` Promise resolve value) in function bodies if a `@returns` tag (without a `void` or `undefined` type) -is specified in the function's jsdoc comment. +is specified in the function's JSDoc comment. Will also report `@returns {void}` and `@returns {undefined}` if `exemptAsync` -is set to `false` no non-`undefined` returned or resolved value is found. +is set to `false` and a non-`undefined` value is returned or a resolved value +is found. Also reports if `@returns {never}` is discovered with a return value. Will also report if multiple `@returns` tags are present. @@ -17437,6 +17438,17 @@ export function readFixture(path: string): void; export function readFixture(path: string); // Message: JSDoc @returns declaration present but return expression not available in function. +/** + * @returns {SomeType} + */ +function quux (path) { + if (true) { + return; + } + return 15; +}; +// Message: JSDoc @returns declaration present but return expression not available in function. + /** * Reads a test fixture. * @@ -17449,6 +17461,87 @@ export function readFixture(path: string): void { return; }; // Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux () { + if (true) { + return true; + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux () { + if (true) { + } else { + return; + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux (someVar) { + switch (someVar) { + case 1: + return true; + case 2: + return; + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {boolean} + */ +const quux = (someVar) => { + if (someVar) { + return true; + } +}; +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux () { + try { + return true; + } catch (error) { + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux () { + try { + return true; + } catch (error) { + return true; + } finally { + return; + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. + +/** + * @returns {true} + */ +function quux () { + try { + } catch (error) { + } finally { + return true; + } +} +// Message: JSDoc @returns declaration present but return expression not available in function. ```` The following patterns are not considered problems: @@ -17614,7 +17707,7 @@ function quux () { return true; } catch (err) { } - return; + return true; } /** @@ -17625,7 +17718,7 @@ function quux () { } finally { return true; } - return; + return true; } /** @@ -17633,7 +17726,7 @@ function quux () { */ function quux () { try { - return; + return true; } catch (err) { } return true; @@ -17648,7 +17741,7 @@ function quux () { } catch (err) { return true; } - return; + return true; } /** @@ -17659,7 +17752,7 @@ function quux () { case 'abc': return true; } - return; + return true; } /** @@ -17668,7 +17761,7 @@ function quux () { function quux () { switch (true) { case 'abc': - return; + return true; } return true; } @@ -17680,7 +17773,7 @@ function quux () { for (const i of abc) { return true; } - return; + return true; } /** @@ -17696,7 +17789,7 @@ function quux () { * @returns {true} */ function quux () { - for (let i=0; i { * @returns {void} The file contents as buffer. */ export function readFixture(path: string); + +/** + * @returns {SomeType} + */ +function quux (path) { + if (true) { + return 5; + } + return 15; +}; + +/** + * @returns {*} Foo. + */ +const quux = () => new Promise((resolve) => { + resolve(3); +}); + +/** + * @returns {*} Foo. + */ +const quux = function () { + return new Promise((resolve) => { + resolve(3); + }); +}; ```` diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index 23a921023..aff6dc77f 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -680,8 +680,8 @@ const getUtils = ( return jsdocUtils.hasDefinedTypeTag(tag); }; - utils.hasValueOrExecutorHasNonEmptyResolveValue = (anyPromiseAsReturn) => { - return jsdocUtils.hasValueOrExecutorHasNonEmptyResolveValue(node, anyPromiseAsReturn); + utils.hasValueOrExecutorHasNonEmptyResolveValue = (anyPromiseAsReturn, allBranches) => { + return jsdocUtils.hasValueOrExecutorHasNonEmptyResolveValue(node, anyPromiseAsReturn, allBranches); }; utils.hasYieldValue = () => { diff --git a/src/rules/requireReturnsCheck.js b/src/rules/requireReturnsCheck.js index 2cf01cb89..abc7c6c19 100755 --- a/src/rules/requireReturnsCheck.js +++ b/src/rules/requireReturnsCheck.js @@ -96,6 +96,7 @@ export default iterateJsdoc(({ ) && !utils.hasValueOrExecutorHasNonEmptyResolveValue( exemptAsync, + true, ) && (!exemptGenerators || !node.generator) ) { report(`JSDoc @${tagName} declaration present but return expression not available in function.`); diff --git a/src/utils/hasReturnValue.js b/src/utils/hasReturnValue.js index 28c5e3c24..528edc140 100644 --- a/src/utils/hasReturnValue.js +++ b/src/utils/hasReturnValue.js @@ -19,6 +19,92 @@ const undefinedKeywords = new Set([ 'TSVoidKeyword', 'TSUndefinedKeyword', 'TSNeverKeyword', ]); +/** + * Checks if a node has a return statement. Void return does not count. + * + * @param {object} node + * @param {PromiseFilter} promFilter + * @returns {boolean|Node} + */ +// eslint-disable-next-line complexity +const allBrancheshaveReturnValues = (node, promFilter) => { + if (!node) { + return false; + } + + switch (node.type) { + case 'TSDeclareFunction': + case 'TSFunctionType': + case 'TSMethodSignature': { + const type = node?.returnType?.typeAnnotation?.type; + return type && !undefinedKeywords.has(type); + } + + // case 'MethodDefinition': + // return allBrancheshaveReturnValues(node.value, promFilter); + case 'FunctionExpression': + case 'FunctionDeclaration': + case 'ArrowFunctionExpression': { + return node.expression && (!isNewPromiseExpression(node.body) || !isVoidPromise(node.body)) || + allBrancheshaveReturnValues(node.body, promFilter); + } + + case 'BlockStatement': { + const lastBodyNode = node.body.slice(-1)[0]; + return allBrancheshaveReturnValues(lastBodyNode, promFilter); + } + + case 'LabeledStatement': + case 'WhileStatement': + case 'DoWhileStatement': + case 'ForStatement': + case 'ForInStatement': + case 'ForOfStatement': + case 'WithStatement': { + return allBrancheshaveReturnValues(node.body, promFilter); + } + + case 'IfStatement': { + return allBrancheshaveReturnValues(node.consequent, promFilter) && allBrancheshaveReturnValues(node.alternate, promFilter); + } + + case 'TryStatement': { + return allBrancheshaveReturnValues(node.block, promFilter) && + allBrancheshaveReturnValues(node.handler && node.handler.body, promFilter) && + allBrancheshaveReturnValues(node.finalizer, promFilter); + } + + case 'SwitchStatement': { + return node.cases.every( + (someCase) => { + return someCase.consequent.every((nde) => { + return allBrancheshaveReturnValues(nde, promFilter); + }); + }, + ); + } + + case 'ReturnStatement': { + // void return does not count. + if (node.argument === null) { + return false; + } + + if (promFilter && isNewPromiseExpression(node.argument)) { + // Let caller decide how to filter, but this is, at the least, + // a return of sorts and truthy + return promFilter(node.argument); + } + + return true; + } + + default: { + return false; + } + } +}; + /** * @callback PromiseFilter * @param {object} node @@ -29,11 +115,12 @@ const undefinedKeywords = new Set([ * Checks if a node has a return statement. Void return does not count. * * @param {object} node + * @param {boolean} throwOnNullReturn * @param {PromiseFilter} promFilter * @returns {boolean|Node} */ // eslint-disable-next-line complexity -const hasReturnValue = (node, promFilter) => { +const hasReturnValue = (node, throwOnNullReturn, promFilter) => { if (!node) { return false; } @@ -47,17 +134,17 @@ const hasReturnValue = (node, promFilter) => { } case 'MethodDefinition': - return hasReturnValue(node.value, promFilter); + return hasReturnValue(node.value, throwOnNullReturn, promFilter); case 'FunctionExpression': case 'FunctionDeclaration': case 'ArrowFunctionExpression': { return node.expression && (!isNewPromiseExpression(node.body) || !isVoidPromise(node.body)) || - hasReturnValue(node.body, promFilter); + hasReturnValue(node.body, throwOnNullReturn, promFilter); } case 'BlockStatement': { return node.body.some((bodyNode) => { - return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, promFilter); + return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, throwOnNullReturn, promFilter); }); } @@ -68,24 +155,25 @@ const hasReturnValue = (node, promFilter) => { case 'ForInStatement': case 'ForOfStatement': case 'WithStatement': { - return hasReturnValue(node.body, promFilter); + return hasReturnValue(node.body, throwOnNullReturn, promFilter); } case 'IfStatement': { - return hasReturnValue(node.consequent, promFilter) || hasReturnValue(node.alternate, promFilter); + return hasReturnValue(node.consequent, throwOnNullReturn, promFilter) || + hasReturnValue(node.alternate, throwOnNullReturn, promFilter); } case 'TryStatement': { - return hasReturnValue(node.block, promFilter) || - hasReturnValue(node.handler && node.handler.body, promFilter) || - hasReturnValue(node.finalizer, promFilter); + return hasReturnValue(node.block, throwOnNullReturn, promFilter) || + hasReturnValue(node.handler && node.handler.body, throwOnNullReturn, promFilter) || + hasReturnValue(node.finalizer, throwOnNullReturn, promFilter); } case 'SwitchStatement': { return node.cases.some( (someCase) => { return someCase.consequent.some((nde) => { - return hasReturnValue(nde, promFilter); + return hasReturnValue(nde, throwOnNullReturn, promFilter); }); }, ); @@ -94,6 +182,10 @@ const hasReturnValue = (node, promFilter) => { case 'ReturnStatement': { // void return does not count. if (node.argument === null) { + if (throwOnNullReturn) { + throw new Error('Null return'); + } + return false; } @@ -320,10 +412,31 @@ const hasNonEmptyResolverCall = (node, resolverName) => { * * @param {object} node * @param {boolean} anyPromiseAsReturn + * @param {boolean} allBranches * @returns {boolean} */ -const hasValueOrExecutorHasNonEmptyResolveValue = (node, anyPromiseAsReturn) => { - return hasReturnValue(node, (prom) => { +const hasValueOrExecutorHasNonEmptyResolveValue = (node, anyPromiseAsReturn, allBranches) => { + const hasReturnMethod = allBranches ? + (nde, promiseFilter) => { + try { + hasReturnValue(nde, true, promiseFilter); + } catch (error) { + // istanbul ignore else + if (error.message === 'Null return') { + return false; + } + + // istanbul ignore next + throw error; + } + + return allBrancheshaveReturnValues(nde, promiseFilter); + } : + (nde, promiseFilter) => { + return hasReturnValue(nde, false, promiseFilter); + }; + + return hasReturnMethod(node, (prom) => { if (anyPromiseAsReturn) { return true; } diff --git a/test/rules/assertions/requireReturnsCheck.js b/test/rules/assertions/requireReturnsCheck.js index f64b19323..c6bf9e8f5 100755 --- a/test/rules/assertions/requireReturnsCheck.js +++ b/test/rules/assertions/requireReturnsCheck.js @@ -411,6 +411,25 @@ export default { ], parser: require.resolve('@typescript-eslint/parser'), }, + { + code: ` + /** + * @returns {SomeType} + */ + function quux (path) { + if (true) { + return; + } + return 15; + }; + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, { code: ` /** @@ -433,6 +452,143 @@ export default { ], parser: require.resolve('@typescript-eslint/parser'), }, + { + code: ` + /** + * @returns {true} + */ + function quux () { + if (true) { + return true; + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {true} + */ + function quux () { + if (true) { + } else { + return; + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {true} + */ + function quux (someVar) { + switch (someVar) { + case 1: + return true; + case 2: + return; + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {boolean} + */ + const quux = (someVar) => { + if (someVar) { + return true; + } + }; + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {true} + */ + function quux () { + try { + return true; + } catch (error) { + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {true} + */ + function quux () { + try { + return true; + } catch (error) { + return true; + } finally { + return; + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, + { + code: ` + /** + * @returns {true} + */ + function quux () { + try { + } catch (error) { + } finally { + return true; + } + } + `, + errors: [ + { + line: 2, + message: 'JSDoc @returns declaration present but return expression not available in function.', + }, + ], + }, ], valid: [ { @@ -676,7 +832,7 @@ export default { return true; } catch (err) { } - return; + return true; } `, }, @@ -690,7 +846,7 @@ export default { } finally { return true; } - return; + return true; } `, }, @@ -701,7 +857,7 @@ export default { */ function quux () { try { - return; + return true; } catch (err) { } return true; @@ -719,7 +875,7 @@ export default { } catch (err) { return true; } - return; + return true; } `, }, @@ -733,7 +889,7 @@ export default { case 'abc': return true; } - return; + return true; } `, }, @@ -745,7 +901,7 @@ export default { function quux () { switch (true) { case 'abc': - return; + return true; } return true; } @@ -760,7 +916,7 @@ export default { for (const i of abc) { return true; } - return; + return true; } `, }, @@ -782,7 +938,7 @@ export default { * @returns {true} */ function quux () { - for (let i=0; i new Promise((resolve) => { + resolve(3); + }); + `, + }, + { + code: ` + /** + * @returns {*} Foo. + */ + const quux = function () { + return new Promise((resolve) => { + resolve(3); + }); + }; + `, + }, ], };