@@ -1380,6 +1380,129 @@ function findTsJsxExpr(sf: ts.SourceFile, attrName?: string): ts.JsxExpression |
13801380 check ( 'saw JSXSpreadAttribute (sanity)' , seenTypes . has ( 'JSXSpreadAttribute' ) ) ;
13811381}
13821382
1383+ // --- Debug-estree counter (env-gated globalThis instrumentation) -------
1384+ //
1385+ // Counter is gated by env TSSLINT_DEBUG_ESTREE=1 (read at module load).
1386+ // To exercise both the off and on paths cleanly, spawn a child Node
1387+ // process with the desired env, run a small inline script that builds a
1388+ // LazyNode, and print the resulting counter via getNodeTypeCounts().
1389+
1390+ const { spawnSync } = require ( 'child_process' ) as typeof import ( 'child_process' ) ;
1391+ const repoCompatEslint = require . resolve ( '../index.js' ) ;
1392+
1393+ function runChild ( env : NodeJS . ProcessEnv , code : string ) : { stdout : string ; stderr : string ; status : number } {
1394+ const r = spawnSync ( process . execPath , [ '-e' , code ] , {
1395+ env : { ...process . env , ...env } ,
1396+ encoding : 'utf8' ,
1397+ } ) ;
1398+ return { stdout : r . stdout || '' , stderr : r . stderr || '' , status : r . status ?? - 1 } ;
1399+ }
1400+
1401+ // Inline driver: load lazy, build the source file, force materialisation
1402+ // of the JSX subtree by reading getters (otherwise the counter only sees
1403+ // the eager Program node), then read getNodeTypeCounts on the next
1404+ // microtask so the deferred queueMicrotask bumps have landed.
1405+ const childCommon = `
1406+ const ts = require('typescript');
1407+ const lazy = require(${ JSON . stringify ( repoCompatEslint . replace ( / i n d e x \. j s $ / , 'lib/lazy-estree.js' ) ) } );
1408+ const visitorKeys = require(${ JSON . stringify ( repoCompatEslint . replace ( / i n d e x \. j s $ / , 'lib/visitor-keys.js' ) ) } ).visitorKeys;
1409+ const sf = ts.createSourceFile('/t.tsx', 'let _ = <Foo a={x} b={y} />;', ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
1410+ const { estree } = lazy.convertLazy(sf);
1411+ const seen = new WeakSet();
1412+ function walk(n) {
1413+ if (n == null || typeof n !== 'object' || seen.has(n)) return;
1414+ seen.add(n);
1415+ const keys = visitorKeys[n.type];
1416+ if (!keys) return;
1417+ for (const k of keys) {
1418+ const v = n[k];
1419+ if (Array.isArray(v)) for (const x of v) walk(x);
1420+ else walk(v);
1421+ }
1422+ }
1423+ walk(estree);
1424+ Promise.resolve().then(() => {
1425+ const counts = lazy.getNodeTypeCounts();
1426+ process.stdout.write(JSON.stringify([...counts.entries()].sort()) + '\\n');
1427+ });
1428+ ` ;
1429+
1430+ // ββ 8: env unset β counter empty βββββββββββββββββββββββββββββββββββββββ
1431+ {
1432+ const r = runChild ( { TSSLINT_DEBUG_ESTREE : '' } , childCommon ) ;
1433+ check ( 'off-path: child exited 0' , r . status === 0 , r . stderr ) ;
1434+ check ( 'off-path: counter empty' , r . stdout . trim ( ) === '[]' , `got: ${ r . stdout . trim ( ) } ` ) ;
1435+ }
1436+
1437+ // ββ 9: env set β counter populated βββββββββββββββββββββββββββββββββββββ
1438+ {
1439+ const r = runChild ( { TSSLINT_DEBUG_ESTREE : '1' } , childCommon ) ;
1440+ check ( 'on-path: child exited 0' , r . status === 0 , r . stderr ) ;
1441+ const parsed = JSON . parse ( r . stdout . trim ( ) ) as Array < [ string , number ] > ;
1442+ const map = new Map ( parsed ) ;
1443+ check ( 'on-path: counter has Program' , ( map . get ( 'Program' ) ?? 0 ) >= 1 ) ;
1444+ check ( 'on-path: counter has JSXOpeningElement' , ( map . get ( 'JSXOpeningElement' ) ?? 0 ) >= 1 ) ;
1445+ check ( 'on-path: counter has JSXAttribute' , ( map . get ( 'JSXAttribute' ) ?? 0 ) >= 1 ) ;
1446+ check (
1447+ 'on-path: counter has NO TSJsxAttributes (skip works)' ,
1448+ ! map . has ( 'TSJsxAttributes' ) ,
1449+ `got TS-prefixed: ${ parsed . filter ( ( [ t ] ) => t . startsWith ( 'TS' ) ) . map ( ( [ t , n ] ) => `${ t } =${ n } ` ) . join ( ', ' ) } ` ,
1450+ ) ;
1451+ }
1452+
1453+ // ββ 10: globalThis-shared counter β second require() instance sees it ββ
1454+ {
1455+ const childTwoInstances = `
1456+ const ts = require('typescript');
1457+ // Load lazy-estree TWICE via different require paths to mimic the
1458+ // CLI-vs-project module-resolution split. delete from cache between
1459+ // to force a fresh module instance, then check that counts populated by
1460+ // the first instance are visible from the second's getNodeTypeCounts().
1461+ const path1 = ${ JSON . stringify ( repoCompatEslint . replace ( / i n d e x \. j s $ / , 'lib/lazy-estree.js' ) ) } ;
1462+ const lazy1 = require(path1);
1463+ const sf = ts.createSourceFile('/t.tsx', 'let _ = <Foo a={x} />;', ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
1464+ lazy1.convertLazy(sf);
1465+ delete require.cache[require.resolve(path1)];
1466+ const lazy2 = require(path1);
1467+ Promise.resolve().then(() => {
1468+ const c1 = lazy1.getNodeTypeCounts();
1469+ const c2 = lazy2.getNodeTypeCounts();
1470+ // Same Map instance backing both β both should report identical entries.
1471+ process.stdout.write(JSON.stringify({
1472+ sameSize: c1.size === c2.size,
1473+ sameProgramCount: c1.get('Program') === c2.get('Program'),
1474+ nonEmpty: c2.size > 0,
1475+ }) + '\\n');
1476+ });
1477+ ` ;
1478+ const r = runChild ( { TSSLINT_DEBUG_ESTREE : '1' } , childTwoInstances ) ;
1479+ check ( 'shared-counter: child exited 0' , r . status === 0 , r . stderr ) ;
1480+ const parsed = JSON . parse ( r . stdout . trim ( ) ) as { sameSize : boolean ; sameProgramCount : boolean ; nonEmpty : boolean } ;
1481+ check ( 'shared-counter: second instance sees populated counter' , parsed . nonEmpty ) ;
1482+ check ( 'shared-counter: both instances see same map size' , parsed . sameSize ) ;
1483+ check ( 'shared-counter: both instances see same Program count' , parsed . sameProgramCount ) ;
1484+ }
1485+
1486+ // ββ 11: resetNodeTypeCounts() clears the counter βββββββββββββββββββββββ
1487+ {
1488+ const code = `
1489+ const ts = require('typescript');
1490+ const lazy = require(${ JSON . stringify ( repoCompatEslint . replace ( / i n d e x \. j s $ / , 'lib/lazy-estree.js' ) ) } );
1491+ lazy.convertLazy(ts.createSourceFile('/t.tsx', 'let _ = <Foo a={x} />;', ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
1492+ Promise.resolve().then(() => {
1493+ const before = lazy.getNodeTypeCounts().size;
1494+ lazy.resetNodeTypeCounts();
1495+ const after = lazy.getNodeTypeCounts().size;
1496+ process.stdout.write(JSON.stringify({ before, after }) + '\\n');
1497+ });
1498+ ` ;
1499+ const r = runChild ( { TSSLINT_DEBUG_ESTREE : '1' } , code ) ;
1500+ check ( 'reset: child exited 0' , r . status === 0 , r . stderr ) ;
1501+ const { before, after } = JSON . parse ( r . stdout . trim ( ) ) ;
1502+ check ( 'reset: before non-empty' , before > 0 ) ;
1503+ check ( 'reset: after empty' , after === 0 ) ;
1504+ }
1505+
13831506// --- Done ---------------------------------------------------------------
13841507
13851508console . log ( `\n${ failures . length === 0 ? 'all pass' : `${ failures . length } FAILED` } ` ) ;
0 commit comments