Skip to content

Commit b8448ff

Browse files
snitin315fasttime
andauthored
feat: correct no-useless-return behaviour in try statements (#16996)
* feat: correct `no-useless-return` behaviour in try statements * test: add more test cases for no-useless-return * refactor: fix formatting * refactor: fix formatting * fix: check all try block statements in the list * Fix a false positive --------- Co-authored-by: Francesco Trotta <github@fasttime.org>
1 parent 37432f2 commit b8448ff

File tree

2 files changed

+198
-20
lines changed

2 files changed

+198
-20
lines changed

lib/rules/no-useless-return.js

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ module.exports = {
8282

8383
create(context) {
8484
const segmentInfoMap = new WeakMap();
85-
const usedUnreachableSegments = new WeakSet();
8685
const sourceCode = context.sourceCode;
8786
let scopeInfo = null;
8887

@@ -152,24 +151,44 @@ module.exports = {
152151
* This behavior would simulate code paths for the case that the return
153152
* statement does not exist.
154153
* @param {CodePathSegment} segment The segment to get return statements.
154+
* @param {Set<CodePathSegment>} usedUnreachableSegments A set of segments that have already been traversed in this call.
155155
* @returns {void}
156156
*/
157-
function markReturnStatementsOnSegmentAsUsed(segment) {
157+
function markReturnStatementsOnSegmentAsUsed(segment, usedUnreachableSegments) {
158158
if (!segment.reachable) {
159159
usedUnreachableSegments.add(segment);
160160
segment.allPrevSegments
161161
.filter(isReturned)
162162
.filter(prevSegment => !usedUnreachableSegments.has(prevSegment))
163-
.forEach(markReturnStatementsOnSegmentAsUsed);
163+
.forEach(prevSegment => markReturnStatementsOnSegmentAsUsed(prevSegment, usedUnreachableSegments));
164164
return;
165165
}
166166

167167
const info = segmentInfoMap.get(segment);
168168

169-
for (const node of info.uselessReturns) {
169+
info.uselessReturns = info.uselessReturns.filter(node => {
170+
if (scopeInfo.traversedTryBlockStatements && scopeInfo.traversedTryBlockStatements.length > 0) {
171+
const returnInitialRange = node.range[0];
172+
const returnFinalRange = node.range[1];
173+
174+
const areBlocksInRange = scopeInfo.traversedTryBlockStatements.some(tryBlockStatement => {
175+
const blockInitialRange = tryBlockStatement.range[0];
176+
const blockFinalRange = tryBlockStatement.range[1];
177+
178+
return (
179+
returnInitialRange >= blockInitialRange &&
180+
returnFinalRange <= blockFinalRange
181+
);
182+
});
183+
184+
if (areBlocksInRange) {
185+
return true;
186+
}
187+
}
188+
170189
remove(scopeInfo.uselessReturns, node);
171-
}
172-
info.uselessReturns = [];
190+
return false;
191+
});
173192
}
174193

175194
/**
@@ -188,7 +207,7 @@ module.exports = {
188207
scopeInfo
189208
.codePath
190209
.currentSegments
191-
.forEach(markReturnStatementsOnSegmentAsUsed);
210+
.forEach(segment => markReturnStatementsOnSegmentAsUsed(segment, new Set()));
192211
}
193212

194213
//----------------------------------------------------------------------
@@ -202,6 +221,7 @@ module.exports = {
202221
scopeInfo = {
203222
upper: scopeInfo,
204223
uselessReturns: [],
224+
traversedTryBlockStatements: [],
205225
codePath
206226
};
207227
},
@@ -275,6 +295,14 @@ module.exports = {
275295
scopeInfo.uselessReturns.push(node);
276296
},
277297

298+
"TryStatement > BlockStatement.block:exit"(node) {
299+
scopeInfo.traversedTryBlockStatements.push(node);
300+
},
301+
302+
"TryStatement:exit"() {
303+
scopeInfo.traversedTryBlockStatements.pop();
304+
},
305+
278306
/*
279307
* Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement.
280308
* Removes return statements of the current segments from the useless return statement list.

tests/lib/rules/no-useless-return.js

Lines changed: 163 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ const rule = require("../../../lib/rules/no-useless-return"),
1919
const ruleTester = new RuleTester();
2020

2121
ruleTester.run("no-useless-return", rule, {
22-
2322
valid: [
2423
"function foo() { return 5; }",
2524
"function foo() { return null; }",
@@ -96,6 +95,26 @@ ruleTester.run("no-useless-return", rule, {
9695
}
9796
}
9897
`,
98+
`
99+
function foo() {
100+
try {
101+
bar();
102+
return;
103+
} catch (err) {}
104+
baz();
105+
}
106+
`,
107+
`
108+
function foo() {
109+
if (something) {
110+
try {
111+
bar();
112+
return;
113+
} catch (err) {}
114+
}
115+
baz();
116+
}
117+
`,
99118
`
100119
function foo() {
101120
return;
@@ -176,6 +195,19 @@ ruleTester.run("no-useless-return", rule, {
176195
}
177196
console.log(arg);
178197
}
198+
`,
199+
200+
// https://github.com/eslint/eslint/pull/16996#discussion_r1138622844
201+
`
202+
function foo() {
203+
try {
204+
bar();
205+
return;
206+
} finally {
207+
baz();
208+
}
209+
qux();
210+
}
179211
`
180212
],
181213

@@ -386,12 +418,120 @@ ruleTester.run("no-useless-return", rule, {
386418
}
387419
`
388420
},
389-
390-
/*
391-
* FIXME: Re-add this case (removed due to https://github.com/eslint/eslint/issues/7481):
392-
* https://github.com/eslint/eslint/blob/261d7287820253408ec87c344beccdba2fe829a4/tests/lib/rules/no-useless-return.js#L308-L329
393-
*/
394-
421+
{
422+
code: `
423+
function foo() {
424+
try {
425+
foo();
426+
return;
427+
} catch (err) {
428+
return 5;
429+
}
430+
}
431+
`,
432+
output: `
433+
function foo() {
434+
try {
435+
foo();
436+
437+
} catch (err) {
438+
return 5;
439+
}
440+
}
441+
`
442+
},
443+
{
444+
code: `
445+
function foo() {
446+
if (something) {
447+
try {
448+
bar();
449+
return;
450+
} catch (err) {}
451+
}
452+
}
453+
`,
454+
output: `
455+
function foo() {
456+
if (something) {
457+
try {
458+
bar();
459+
460+
} catch (err) {}
461+
}
462+
}
463+
`
464+
},
465+
{
466+
code: `
467+
function foo() {
468+
try {
469+
return;
470+
} catch (err) {
471+
foo();
472+
}
473+
}
474+
`,
475+
output: `
476+
function foo() {
477+
try {
478+
479+
} catch (err) {
480+
foo();
481+
}
482+
}
483+
`
484+
},
485+
{
486+
code: `
487+
function foo() {
488+
try {
489+
return;
490+
} finally {
491+
bar();
492+
}
493+
}
494+
`,
495+
output: `
496+
function foo() {
497+
try {
498+
499+
} finally {
500+
bar();
501+
}
502+
}
503+
`
504+
},
505+
{
506+
code: `
507+
function foo() {
508+
try {
509+
bar();
510+
} catch (e) {
511+
try {
512+
baz();
513+
return;
514+
} catch (e) {
515+
qux();
516+
}
517+
}
518+
}
519+
`,
520+
output: `
521+
function foo() {
522+
try {
523+
bar();
524+
} catch (e) {
525+
try {
526+
baz();
527+
528+
} catch (e) {
529+
qux();
530+
}
531+
}
532+
}
533+
`
534+
},
395535
{
396536
code: `
397537
function foo() {
@@ -438,11 +578,21 @@ ruleTester.run("no-useless-return", rule, {
438578
{
439579
code: "function foo() { return; return; }",
440580
output: "function foo() { return; }",
441-
errors: [{
442-
messageId: "unnecessaryReturn",
443-
type: "ReturnStatement",
444-
column: 18
445-
}]
581+
errors: [
582+
{
583+
messageId: "unnecessaryReturn",
584+
type: "ReturnStatement",
585+
column: 18
586+
}
587+
]
446588
}
447-
].map(invalidCase => Object.assign({ errors: [{ messageId: "unnecessaryReturn", type: "ReturnStatement" }] }, invalidCase))
589+
].map(invalidCase =>
590+
Object.assign(
591+
{
592+
errors: [
593+
{ messageId: "unnecessaryReturn", type: "ReturnStatement" }
594+
]
595+
},
596+
invalidCase
597+
))
448598
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy