Skip to content

eslint-factory: no-json-stringify-set-or-map tracks Set/Map variables by bare name, not by scope #54163

Description

@github-actions

Summary

no-json-stringify-set-or-map flags JSON.stringify(x) when x was previously seen as const x = new Set(...)/new Map(...). The tracking is a single Map<string, "Set"|"Map"> created once per create(context) call (i.e. once per linted file) and keyed purely by variable name string, with no scope resolution at all.

Where

eslint-factory/src/rules/no-json-stringify-set-or-map.ts:

  • trackedVars declared once inside create(context) (file-wide, not per-block/per-function).
  • VariableDeclarator visitor: trackedVars.set(node.id.name, kind) — name-only key, no scope/binding captured.
  • CallExpression visitor for JSON.stringify(identifier): trackedVars.get(firstArg.name) — a pure name lookup, no check that the specific binding at the call site is the same binding that was tracked.

Impact

If a single file declares two different const-bound variables with the same name in different lexical scopes (e.g. two different functions each with their own const seen = new Set() / const cache = new Map()), the flat map only ever holds whichever same-named entry was most recently visited in traversal order. Depending on traversal/declaration order this produces either:

  • a false positive: an unrelated non-Set/Map variable gets flagged because a same-named Set/Map from a different scope overwrote the tracking entry, or
  • a false negative: a real JSON.stringify(realSetVar) call is missed because a different, same-named, non-Set/Map variable overwrote the entry first.

Grounding

This is a soundness gap in the tracking design, not (yet) confirmed against a live same-file/same-name/different-scope collision — a targeted grep across actions/setup/js/**/*.cjs for repeated Set/Map variable names (seen, cache, map, ids, groups, etc.) combined with JSON.stringify(<same name>) calls found no live occurrence today. The codebase's current Set/Map variable names happen not to collide within any single file. The bug is real in the rule's logic regardless — it is a latent regression risk, and the fix is cheap (track bindings/scope, e.g. via context.sourceCode.getScope(node) + variable resolution, matching the scope-aware binding-resolution pattern already used elsewhere in this rule family, e.g. try-catch-rule-utils.ts's scope-walking resolvers) rather than name-string matching.

Ask

  1. Replace the name-keyed trackedVars map with scope-aware binding resolution (resolve the Identifier at the JSON.stringify(x) call site to its actual variable/definition via ESLint scope analysis, then check whether that specific binding's initializer was new Set(...)/new Map(...)), instead of a flat per-file name map.
  2. Add a regression test: two functions in the same file, each declaring const seen = new Set() / const seen = { other: true } (or similar) with the same name in different scopes, asserting only the actual Set/Map reference is flagged.

Acceptance criteria

  • Tracking is scope/binding-aware, not name-string-only.

  • New test covers a same-name, different-scope, mixed Set/Map-and-non-Set/Map case in one file.

  • Existing tests for the single-scope tracked-variable case still pass unchanged.

  • This issue will auto-expire if not addressed within 90 days.

Generated by 🤖 ESLint Refiner · agent · 280.9 AIC · ⌖ 9.73 AIC · ⊞ 5.3K ·

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