Skip to content

[eslint-factory] no-core-error-then-process-exitcode: match sibling's non-adjacent forward scan (+ top-level autofix) #47212

Description

@github-actions

Summary

The two sibling rules that flag core.error(...) followed by a failure signal diverge in detection strength, producing a false negative in the process.exitCode variant:

  • no-core-error-then-process-exit scans forward from core.error(...) (checkStatements, inner for (j = i+1; ...)) for a later process.exit(nonzero), stopping only at a core.setFailed(...) or a control-transfer statement. It correctly catches core.error(); <intervening stmt>; process.exit(1).
  • no-core-error-then-process-exitcode checks only the immediately adjacent statement: isCoreErrorStatement(current) && isProcessExitCodeNonZero(stmts[i+1]). Any intervening statement defeats detection.

This asymmetry is especially wrong for the exitCode variant: unlike process.exit(1), process.exitCode = 1 does not halt execution, so it is common to have statements around it. A benign line between the error and the assignment silently disables the rule:

core.error("deploy failed");
core.info("cleaning up");   // any intervening statement
process.exitCode = 1;       // NOT flagged today — false negative

The sibling no-core-error-then-process-exit would flag the analogous process.exit(1) case. (The existing valid test core.error("msg"); process.exit(1); at line 33 and the exports-break-adjacency test at line 31 confirm adjacency-only is the current, intentional-but-narrow behavior; there is no test for a benign intervening statement.)

Secondary gap: missing top-level autofix

no-core-error-then-process-exit offers its replaceWithSetFailed autofix at module top level (enclosingFn === nullcore.setFailed(args); with no return;). no-core-error-then-process-exitcode gates safeToFix on isFunctionNamedMain(enclosingFn), so at module top level it emits no suggestion — even though process.exitCode = 1 at top level is safely replaceable by core.setFailed(msg); (which also sets exitCode = 1). This is an avoidable parity gap.

Grounding

Soundness/consistency fix; no live core.error(); ...; process.exitCode = nonzero occurrence found in the current actions/setup/js/** corpus (the flagged pairs today are process.exit, e.g. convert_gateway_config_shared.cjs:48-49 and :70-71, correctly caught by the sibling). Filing to guard against regressions and to bring the two sibling rules to behavioral parity — cheap, self-contained.

Acceptance criteria

  • no-core-error-then-process-exitcode scans forward from core.error(...) for a subsequent process.exitCode = <nonzero literal>, stopping the scan at core.setFailed(...) or a control-transfer statement (mirroring the exit sibling's stop conditions).
  • New invalid test: core.error("x"); core.info("y"); process.exitCode = 1; reports once.
  • New valid tests preserved: core.error("x"); core.setFailed("y"); process.exitCode = 1; and core.error("x"); return; process.exitCode = 1; do NOT report (setFailed / control-transfer breaks the scan). Existing valid cases (exitCode = 0, variable RHS, +=, non-core object, exports-break-adjacency at module scope) stay green.
  • Non-adjacent reports omit the autofix (same convention the exit sibling uses for non-adjacent pairs) to avoid a fixer spanning intervening statements.
  • Add the module-top-level autofix (enclosingFn === null → replace with core.setFailed(args);, no return;) for the adjacent case, matching the exit sibling.
  • Update the rule docs to describe the forward-scan semantics.

Filed by the ESLint Refiner daily review.

Generated by 🤖 ESLint Refiner · age00 312 AIC · ⌖ 12.9 AIC · ⊞ 4.6K ·

  • expires on Jul 28, 2026, 10:18 PM UTC-08:00

Metadata

Metadata

Labels

cookieIssue Monster Loves Cookies!eslint

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions