Skip to content

require-invalid-date-check-before-compare: isNaN(getTime()) validation ignores position/reachability relative to the comparison #51525

Description

@github-actions

Rule

require-invalid-date-check-before-compare (eslint-factory/src/rules/require-invalid-date-check-before-compare.ts) — first review since it shipped; no prior issues filed.

What the rule does

Flags a relational comparison (<, >, <=, >=) involving a new Date(nonTrivialArg) value unless that value was validated via Number.isNaN(x.getTime()) / isNaN(x.getTime()) somewhere in the file. Tracking is scope-aware (uses resolved Variable objects, so same-named locals in different function scopes are correctly kept separate — verified by the existing test "same variable name declared in a different function scope is not treated as validated").

The gap

The validated Set<Variable> is populated purely by whether a Number.isNaN(x.getTime()) call for that variable exists anywhere in the traversal, with no check that the guard is reachable and executes before the risky comparison. Concretely, in create():

  • CallExpression visitor adds a variable to validated unconditionally whenever isGetTimeNaNCheck matches, regardless of source position.
  • BinaryExpression visitor unconditionally records every relational comparison into comparisons.
  • Program:exit only checks validated.has(side.variable) — no ordering, dominance, or branch-reachability check against the comparison node.

So a guard that appears after the unguarded use, or only in a branch that doesn't actually protect the comparison, is treated as full validation and the diagnostic is suppressed:

const d = new Date(input);
if (d > threshold) { doIt(); }              // <-- unguarded here, should be flagged
if (Number.isNaN(d.getTime())) { return; }  // check comes too late to help the line above
const d = new Date(input);
if (someUnrelatedFlag) {
  if (Number.isNaN(d.getTime())) { return; } // only reachable when someUnrelatedFlag is true
}
if (d > threshold) { doIt(); }                // still unguarded on the other path, not flagged

Both are currently silently accepted by the rule (verified by reading the implementation; no live corpus occurrence was found in actions/setup/js/**/*.cjs today — the codebase's actual new Date(...) comparisons, e.g. actions/setup/js/check_rate_limit.cjs:161 and actions/setup/js/check_runs_helpers.cjs:43, have no Number.isNaN guard at all in those files, so the rule correctly flags them as-is). This is the same class of soundness gap previously found and fixed/tracked in sibling rules in this factory (no-unsafe-catch-error-property #42189, no-unsafe-promise-catch-error-property #42915: "whole-block/branch-order false negative" — presence-anywhere-in-scope instead of control-flow-ordered validation).

Suggested fix

At minimum, require the validating Number.isNaN(...) call's node range to start before the comparison node's range for that variable (cheap, catches the reordered case). Better: reuse or extend the ordering/ancestor-walk approach already used by try-catch-rule-utils.ts's isInsideTryBlock/crossedDeferredBoundary helpers elsewhere in this factory to check the guard's containing if actually dominates (encloses or precedes-and-returns-from) the comparison's statement, not just "appears somewhere in the Program".

Acceptance criteria

  • Add a failing-then-passing test: guard written after the comparison in the same block is still reported as invalid.
  • Add a test: guard only present in a sibling/unrelated conditional branch (not guaranteed to execute before the comparison) is still reported as invalid.
  • Keep the existing valid cases (guard-before-use in same or different function scopes) passing unchanged.
  • No new false positives on actions/setup/js/**/*.cjs.

Generated by 🤖 ESLint Refiner · agent · 224.5 AIC · ⌖ 40.7 AIC · ⊞ 4.9K ·

  • expires on Aug 15, 2026, 9:40 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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions