@@ -31,6 +31,35 @@ function isSetOrMapConstruction(sourceCode: TSESLint.SourceCode, expr: TSESTree.
3131 return name ;
3232}
3333
34+ function getConstSetOrMapBindingKind ( sourceCode : TSESLint . SourceCode , identifier : TSESTree . Identifier ) : "Set" | "Map" | null {
35+ let scope : SourceCodeScope | null = sourceCode . getScope ( identifier ) ;
36+
37+ while ( scope ) {
38+ const variable = scope . set . get ( identifier . name ) ;
39+ if ( variable && variable . defs . length > 0 ) {
40+ for ( const def of variable . defs ) {
41+ if ( def . type !== "Variable" ) continue ;
42+
43+ const declarator = def . node as TSESTree . VariableDeclarator ;
44+ if ( declarator . id . type !== AST_NODE_TYPES . Identifier || declarator . id . name !== identifier . name ) continue ;
45+ if ( ! declarator . init ) continue ;
46+
47+ const declaration = declarator . parent ;
48+ if ( declaration . type !== AST_NODE_TYPES . VariableDeclaration || declaration . kind !== "const" ) continue ;
49+
50+ const kind = isSetOrMapConstruction ( sourceCode , declarator . init ) ;
51+ if ( kind ) return kind ;
52+ }
53+
54+ return null ;
55+ }
56+
57+ scope = scope . upper ;
58+ }
59+
60+ return null ;
61+ }
62+
3463export const noJsonStringifySetOrMapRule = createRule ( {
3564 name : "no-json-stringify-set-or-map" ,
3665 meta : {
@@ -49,30 +78,11 @@ export const noJsonStringifySetOrMapRule = createRule({
4978 create ( context ) {
5079 const sourceCode = context . sourceCode ;
5180
52- // Track variable names in scope whose declared initializer is `new Set(...)` / `new Map(...)`,
53- // mapped to which kind they are. Reassignment to something else removes the binding from tracking
54- // (handled conservatively: only VariableDeclarator initializers are tracked, not `let` reassignment).
55- const trackedVars = new Map < string , "Set" | "Map" > ( ) ;
56-
5781 function suggestionFor ( kind : "Set" | "Map" , varName : string ) : string {
5882 return kind === "Set" ? `Array.from(${ varName } )` : `Object.fromEntries(${ varName } )` ;
5983 }
6084
6185 return {
62- VariableDeclarator ( node : TSESTree . VariableDeclarator ) {
63- if ( node . id . type !== AST_NODE_TYPES . Identifier ) return ;
64- if ( ! node . init ) return ;
65- const kind = isSetOrMapConstruction ( sourceCode , node . init ) ;
66- if ( ! kind ) return ;
67-
68- // Only track when declared with `const` — `let`/`var` bindings could be reassigned
69- // to a non-Set/Map value later, which this rule cannot statically follow.
70- const declaration = node . parent ;
71- if ( declaration . type !== AST_NODE_TYPES . VariableDeclaration || declaration . kind !== "const" ) return ;
72-
73- trackedVars . set ( node . id . name , kind ) ;
74- } ,
75-
7686 CallExpression ( node : TSESTree . CallExpression ) {
7787 const callee = node . callee ;
7888 if ( callee . type !== AST_NODE_TYPES . MemberExpression || callee . computed ) return ;
@@ -97,9 +107,9 @@ export const noJsonStringifySetOrMapRule = createRule({
97107 return ;
98108 }
99109
100- // Reference to a tracked `const x = new Set(...)/new Map(...)` binding.
110+ // Reference to a `const x = new Set(...)/new Map(...)` binding.
101111 if ( firstArg . type !== AST_NODE_TYPES . Identifier ) return ;
102- const kind = trackedVars . get ( firstArg . name ) ;
112+ const kind = getConstSetOrMapBindingKind ( sourceCode , firstArg ) ;
103113 if ( ! kind ) return ;
104114
105115 context . report ( {
0 commit comments