From fa0a38f49baeff2b043987085d1ca51a6142b950 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 10:05:24 +0000 Subject: [PATCH 1/3] Initial plan From 7dcce2da305470b204b9b7701fb170bf17a53b1e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 10:12:08 +0000 Subject: [PATCH 2/3] fix(eslint): fix switch fall-through FN and function decl FP in require-return-after-core-setfailed Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- ...equire-return-after-core-setfailed.test.ts | 29 +++++++++++++++++++ .../require-return-after-core-setfailed.ts | 12 +++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/eslint-factory/src/rules/require-return-after-core-setfailed.test.ts b/eslint-factory/src/rules/require-return-after-core-setfailed.test.ts index 701adce267c..2a0d31bc64c 100644 --- a/eslint-factory/src/rules/require-return-after-core-setfailed.test.ts +++ b/eslint-factory/src/rules/require-return-after-core-setfailed.test.ts @@ -379,4 +379,33 @@ doMore();`, invalid: [], }); }); + + it("valid: trailing hoisted function declaration is not a continuation (FP fix)", () => { + ruleTester.run("require-return-after-core-setfailed", requireReturnAfterCoreSetFailedRule, { + valid: [ + // FunctionDeclaration is hoisted and has no sequential runtime effect + `core.setFailed("bad"); function helper() {}`, + ], + invalid: [], + }); + }); + + it("invalid: bare switch-case fall-through after setFailed is flagged (FN fix)", () => { + ruleTester.run("require-return-after-core-setfailed", requireReturnAfterCoreSetFailedRule, { + valid: [ + // Terminating break prevents fall-through — must remain valid + `switch (x) { case 1: core.setFailed("bad"); break; case 2: doMore(); }`, + ], + invalid: [ + { + code: `switch (x) { case 1: core.setFailed("bad"); case 2: doMore(); }`, + errors: [{ messageId: "missingReturnAfterSetFailed" }], + }, + { + code: `function f(x) { switch (x) { case 1: core.setFailed("bad"); case 2: doMore(); } }`, + errors: [{ messageId: "missingReturnAfterSetFailed", suggestions: [{ messageId: "addReturn", output: `function f(x) { switch (x) { case 1: core.setFailed("bad"); return; case 2: doMore(); } }` }] }], + }, + ], + }); + }); }); diff --git a/eslint-factory/src/rules/require-return-after-core-setfailed.ts b/eslint-factory/src/rules/require-return-after-core-setfailed.ts index 652fd375a27..4b7927708be 100644 --- a/eslint-factory/src/rules/require-return-after-core-setfailed.ts +++ b/eslint-factory/src/rules/require-return-after-core-setfailed.ts @@ -60,7 +60,11 @@ function isExecutableStatement(node: TSESTree.ProgramStatement): node is TSESTre node.type !== AST_NODE_TYPES.ExportAllDeclaration && node.type !== AST_NODE_TYPES.ExportDefaultDeclaration && node.type !== AST_NODE_TYPES.ExportNamedDeclaration && - node.type !== AST_NODE_TYPES.TSModuleDeclaration + node.type !== AST_NODE_TYPES.TSModuleDeclaration && + // Hoisted declarations have no sequential runtime effect — not a continuation + node.type !== AST_NODE_TYPES.FunctionDeclaration && + node.type !== AST_NODE_TYPES.TSInterfaceDeclaration && + node.type !== AST_NODE_TYPES.TSTypeAliasDeclaration ); } @@ -337,6 +341,12 @@ export const requireReturnAfterCoreSetFailedRule = createRule({ // The main case is BlockStatement above. SwitchCase(node: TSESTree.SwitchCase) { checkStatementList(node.consequent, isCoreSetFailedStatement, report); + // Fall-through: when setFailed is the last consequent statement with no terminator, + // execution falls through to the next case — same pattern as BlockStatement above. + const lastStmt = node.consequent[node.consequent.length - 1]; + if (lastStmt && isCoreSetFailedStatement(lastStmt)) { + checkNestedContinuation(lastStmt); + } }, WhileStatement(node: TSESTree.WhileStatement) { checkDirectControlBody(node.body); From 7c5032dd957b4d4899eeb76dff88cbd446fdb822 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:53:55 +0000 Subject: [PATCH 3/3] fix(eslint): filter non-executable stmts in switch fall-through continuation search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `findContinuationOutsideBlock` checks later switch cases for a fall-through continuation, it previously returned the raw first statement of each case—which could be a hoisted `FunctionDeclaration`. This caused a false positive for: switch (x) { case 1: core.setFailed("bad"); case 2: function helper() {} } Fix: use `find(s => isExecutableStatement(...))` so that cases whose only statements are hoisted declarations are skipped, and we keep looking through subsequent cases before returning null. Also adds a regression test for this pattern. Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .../src/rules/require-return-after-core-setfailed.test.ts | 2 ++ .../src/rules/require-return-after-core-setfailed.ts | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/eslint-factory/src/rules/require-return-after-core-setfailed.test.ts b/eslint-factory/src/rules/require-return-after-core-setfailed.test.ts index 2a0d31bc64c..22aefa1d0bb 100644 --- a/eslint-factory/src/rules/require-return-after-core-setfailed.test.ts +++ b/eslint-factory/src/rules/require-return-after-core-setfailed.test.ts @@ -395,6 +395,8 @@ doMore();`, valid: [ // Terminating break prevents fall-through — must remain valid `switch (x) { case 1: core.setFailed("bad"); break; case 2: doMore(); }`, + // Fall-through to a case that only contains hoisted declarations — no executable continuation + `switch (x) { case 1: core.setFailed("bad"); case 2: function helper() {} }`, ], invalid: [ { diff --git a/eslint-factory/src/rules/require-return-after-core-setfailed.ts b/eslint-factory/src/rules/require-return-after-core-setfailed.ts index 4b7927708be..d9c3a0b2579 100644 --- a/eslint-factory/src/rules/require-return-after-core-setfailed.ts +++ b/eslint-factory/src/rules/require-return-after-core-setfailed.ts @@ -135,8 +135,10 @@ function findContinuationOutsideBlock(setFailedNode: TSESTree.Statement, ancesto if (currentIndex >= 0) { for (let nextCaseIndex = currentIndex + 1; nextCaseIndex < ancestor.cases.length; nextCaseIndex++) { const nextCase = ancestor.cases[nextCaseIndex]; - const nextStmt = nextCase.consequent[0]; - if (nextStmt) { + // Skip hoisted declarations (FunctionDeclaration, etc.) — they have no sequential + // runtime effect and should not count as fall-through continuations. + const nextStmt = nextCase.consequent.find(s => isExecutableStatement(s as TSESTree.ProgramStatement)); + if (nextStmt !== undefined) { return nextStmt; } }