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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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