Skip to content

Commit dc1f875

Browse files
committedOct 22, 2022
fix(require-returns-check): check child nodes of consequents; fixes #923
1 parent 64b1ead commit dc1f875

File tree

3 files changed

+146
-46
lines changed

3 files changed

+146
-46
lines changed
 

‎README.md

+41
Original file line numberDiff line numberDiff line change
@@ -17572,6 +17572,34 @@ function quux () {
1757217572
throw new Error('def');
1757317573
}
1757417574
// Message: JSDoc @returns declaration present but return expression not available in function.
17575+
17576+
/**
17577+
* @returns Baz.
17578+
*/
17579+
function foo() {
17580+
switch (true) {
17581+
default:
17582+
switch (false) {
17583+
default: return;
17584+
}
17585+
return "baz";
17586+
}
17587+
};
17588+
// Message: JSDoc @returns declaration present but return expression not available in function.
17589+
17590+
/**
17591+
* @returns Baz.
17592+
*/
17593+
function foo() {
17594+
switch (true) {
17595+
default:
17596+
switch (false) {
17597+
default: return;
17598+
}
17599+
return "baz";
17600+
}
17601+
};
17602+
// Message: JSDoc @returns declaration present but return expression not available in function.
1757517603
````
1757617604

1757717605
The following patterns are not considered problems:
@@ -18034,6 +18062,19 @@ function quux () {
1803418062

1803518063
throw new Error('Fail');
1803618064
}
18065+
18066+
/**
18067+
* @returns Baz.
18068+
*/
18069+
function foo() {
18070+
switch (true) {
18071+
default:
18072+
switch (false) {
18073+
default: break;
18074+
}
18075+
return "baz";
18076+
}
18077+
};
1803718078
````
1803818079

1803918080

‎src/utils/hasReturnValue.js

+45-46
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ const undefinedKeywords = new Set([
2323
* Checks if a node has a return statement. Void return does not count.
2424
*
2525
* @param {object} node
26+
* @param {boolean} throwOnNullReturn
2627
* @param {PromiseFilter} promFilter
2728
* @returns {boolean|Node}
2829
*/
2930
// eslint-disable-next-line complexity
30-
const allBrancheshaveReturnValues = (node, promFilter) => {
31+
const hasReturnValue = (node, throwOnNullReturn, promFilter) => {
3132
if (!node) {
3233
return false;
3334
}
@@ -40,18 +41,19 @@ const allBrancheshaveReturnValues = (node, promFilter) => {
4041
return type && !undefinedKeywords.has(type);
4142
}
4243

43-
// case 'MethodDefinition':
44-
// return allBrancheshaveReturnValues(node.value, promFilter);
44+
case 'MethodDefinition':
45+
return hasReturnValue(node.value, throwOnNullReturn, promFilter);
4546
case 'FunctionExpression':
4647
case 'FunctionDeclaration':
4748
case 'ArrowFunctionExpression': {
4849
return node.expression && (!isNewPromiseExpression(node.body) || !isVoidPromise(node.body)) ||
49-
allBrancheshaveReturnValues(node.body, promFilter);
50+
hasReturnValue(node.body, throwOnNullReturn, promFilter);
5051
}
5152

5253
case 'BlockStatement': {
53-
const lastBodyNode = node.body.slice(-1)[0];
54-
return allBrancheshaveReturnValues(lastBodyNode, promFilter);
54+
return node.body.some((bodyNode) => {
55+
return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, throwOnNullReturn, promFilter);
56+
});
5557
}
5658

5759
case 'LabeledStatement':
@@ -61,36 +63,37 @@ const allBrancheshaveReturnValues = (node, promFilter) => {
6163
case 'ForInStatement':
6264
case 'ForOfStatement':
6365
case 'WithStatement': {
64-
return allBrancheshaveReturnValues(node.body, promFilter);
66+
return hasReturnValue(node.body, throwOnNullReturn, promFilter);
6567
}
6668

6769
case 'IfStatement': {
68-
return allBrancheshaveReturnValues(node.consequent, promFilter) && allBrancheshaveReturnValues(node.alternate, promFilter);
70+
return hasReturnValue(node.consequent, throwOnNullReturn, promFilter) ||
71+
hasReturnValue(node.alternate, throwOnNullReturn, promFilter);
6972
}
7073

7174
case 'TryStatement': {
72-
return allBrancheshaveReturnValues(node.block, promFilter) &&
73-
allBrancheshaveReturnValues(node.handler && node.handler.body, promFilter) &&
74-
allBrancheshaveReturnValues(node.finalizer, promFilter);
75+
return hasReturnValue(node.block, throwOnNullReturn, promFilter) ||
76+
hasReturnValue(node.handler && node.handler.body, throwOnNullReturn, promFilter) ||
77+
hasReturnValue(node.finalizer, throwOnNullReturn, promFilter);
7578
}
7679

7780
case 'SwitchStatement': {
78-
return node.cases.every(
81+
return node.cases.some(
7982
(someCase) => {
80-
return someCase.consequent.every((nde) => {
81-
return allBrancheshaveReturnValues(nde, promFilter);
83+
return someCase.consequent.some((nde) => {
84+
return hasReturnValue(nde, throwOnNullReturn, promFilter);
8285
});
8386
},
8487
);
8588
}
8689

87-
case 'ThrowStatement': {
88-
return true;
89-
}
90-
9190
case 'ReturnStatement': {
9291
// void return does not count.
9392
if (node.argument === null) {
93+
if (throwOnNullReturn) {
94+
throw new Error('Null return');
95+
}
96+
9497
return false;
9598
}
9699

@@ -109,22 +112,15 @@ const allBrancheshaveReturnValues = (node, promFilter) => {
109112
}
110113
};
111114

112-
/**
113-
* @callback PromiseFilter
114-
* @param {object} node
115-
* @returns {boolean}
116-
*/
117-
118115
/**
119116
* Checks if a node has a return statement. Void return does not count.
120117
*
121118
* @param {object} node
122-
* @param {boolean} throwOnNullReturn
123119
* @param {PromiseFilter} promFilter
124120
* @returns {boolean|Node}
125121
*/
126122
// eslint-disable-next-line complexity
127-
const hasReturnValue = (node, throwOnNullReturn, promFilter) => {
123+
const allBrancheshaveReturnValues = (node, promFilter) => {
128124
if (!node) {
129125
return false;
130126
}
@@ -137,19 +133,18 @@ const hasReturnValue = (node, throwOnNullReturn, promFilter) => {
137133
return type && !undefinedKeywords.has(type);
138134
}
139135

140-
case 'MethodDefinition':
141-
return hasReturnValue(node.value, throwOnNullReturn, promFilter);
136+
// case 'MethodDefinition':
137+
// return allBrancheshaveReturnValues(node.value, promFilter);
142138
case 'FunctionExpression':
143139
case 'FunctionDeclaration':
144140
case 'ArrowFunctionExpression': {
145141
return node.expression && (!isNewPromiseExpression(node.body) || !isVoidPromise(node.body)) ||
146-
hasReturnValue(node.body, throwOnNullReturn, promFilter);
142+
allBrancheshaveReturnValues(node.body, promFilter);
147143
}
148144

149145
case 'BlockStatement': {
150-
return node.body.some((bodyNode) => {
151-
return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, throwOnNullReturn, promFilter);
152-
});
146+
const lastBodyNode = node.body.slice(-1)[0];
147+
return allBrancheshaveReturnValues(lastBodyNode, promFilter);
153148
}
154149

155150
case 'LabeledStatement':
@@ -159,37 +154,35 @@ const hasReturnValue = (node, throwOnNullReturn, promFilter) => {
159154
case 'ForInStatement':
160155
case 'ForOfStatement':
161156
case 'WithStatement': {
162-
return hasReturnValue(node.body, throwOnNullReturn, promFilter);
157+
return allBrancheshaveReturnValues(node.body, promFilter);
163158
}
164159

165160
case 'IfStatement': {
166-
return hasReturnValue(node.consequent, throwOnNullReturn, promFilter) ||
167-
hasReturnValue(node.alternate, throwOnNullReturn, promFilter);
161+
return allBrancheshaveReturnValues(node.consequent, promFilter) && allBrancheshaveReturnValues(node.alternate, promFilter);
168162
}
169163

170164
case 'TryStatement': {
171-
return hasReturnValue(node.block, throwOnNullReturn, promFilter) ||
172-
hasReturnValue(node.handler && node.handler.body, throwOnNullReturn, promFilter) ||
173-
hasReturnValue(node.finalizer, throwOnNullReturn, promFilter);
165+
return allBrancheshaveReturnValues(node.block, promFilter) &&
166+
allBrancheshaveReturnValues(node.handler && node.handler.body, promFilter) &&
167+
allBrancheshaveReturnValues(node.finalizer, promFilter);
174168
}
175169

176170
case 'SwitchStatement': {
177-
return node.cases.some(
171+
return node.cases.every(
178172
(someCase) => {
179-
return someCase.consequent.some((nde) => {
180-
return hasReturnValue(nde, throwOnNullReturn, promFilter);
181-
});
173+
const nde = someCase.consequent.slice(-1)[0];
174+
return allBrancheshaveReturnValues(nde, promFilter);
182175
},
183176
);
184177
}
185178

179+
case 'ThrowStatement': {
180+
return true;
181+
}
182+
186183
case 'ReturnStatement': {
187184
// void return does not count.
188185
if (node.argument === null) {
189-
if (throwOnNullReturn) {
190-
throw new Error('Null return');
191-
}
192-
193186
return false;
194187
}
195188

@@ -208,6 +201,12 @@ const hasReturnValue = (node, throwOnNullReturn, promFilter) => {
208201
}
209202
};
210203

204+
/**
205+
* @callback PromiseFilter
206+
* @param {object} node
207+
* @returns {boolean}
208+
*/
209+
211210
/**
212211
* Avoids further checking child nodes if a nested function shadows the
213212
* resolver, but otherwise, if name is used (by call or passed in as an

‎test/rules/assertions/requireReturnsCheck.js

+60
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,50 @@ export default {
630630
],
631631
ignoreReadme: true,
632632
},
633+
{
634+
code: `
635+
/**
636+
* @returns Baz.
637+
*/
638+
function foo() {
639+
switch (true) {
640+
default:
641+
switch (false) {
642+
default: return;
643+
}
644+
return "baz";
645+
}
646+
};
647+
`,
648+
errors: [
649+
{
650+
line: 2,
651+
message: 'JSDoc @returns declaration present but return expression not available in function.',
652+
},
653+
],
654+
},
655+
{
656+
code: `
657+
/**
658+
* @returns Baz.
659+
*/
660+
function foo() {
661+
switch (true) {
662+
default:
663+
switch (false) {
664+
default: return;
665+
}
666+
return "baz";
667+
}
668+
};
669+
`,
670+
errors: [
671+
{
672+
line: 2,
673+
message: 'JSDoc @returns declaration present but return expression not available in function.',
674+
},
675+
],
676+
},
633677
],
634678
valid: [
635679
{
@@ -1315,5 +1359,21 @@ export default {
13151359
}
13161360
`,
13171361
},
1362+
{
1363+
code: `
1364+
/**
1365+
* @returns Baz.
1366+
*/
1367+
function foo() {
1368+
switch (true) {
1369+
default:
1370+
switch (false) {
1371+
default: break;
1372+
}
1373+
return "baz";
1374+
}
1375+
};
1376+
`,
1377+
},
13181378
],
13191379
};

0 commit comments

Comments
 (0)
Please sign in to comment.