'use strict'; // docs-guard-exempt: 'docs/readme.md' appears only inside literal RuleTester // fixture `code` strings (sample source text fed to no-source-grep for AST // linting) — this file never itself reads a real docs/ file off disk. /** * eslint-rules.test.cjs * * RuleTester unit tests for the local ESLint rules: * - local/no-source-grep * - local/no-magic-sleep-in-tests * - local/no-elapsed-assertion * - local/no-raw-rmsync-in-tests * - local/no-adhoc-markdown-parsing * - local/require-subprocess-timeout * - local/require-registered-exit */ const { test, describe } = require('node:test'); const assert = require('node:assert/strict'); const { RuleTester, ESLint } = require('eslint'); const path = require('node:path'); const fc = require('fast-check'); const noSourceGrep = require('../eslint-rules/no-source-grep.cjs'); const noMagicSleepInTests = require('../eslint-rules/no-magic-sleep-in-tests.cjs'); const noElapsedAssertion = require('../eslint-rules/no-elapsed-assertion.cjs'); const noRawRmsyncInTests = require('../eslint-rules/no-raw-rmsync-in-tests.cjs'); const noTautologicalAssert = require('../eslint-rules/no-tautological-assert.cjs'); const noAdhocMarkdownParsing = require('../eslint-rules/no-adhoc-markdown-parsing.cjs'); const noDuplicateFoldMarker = require('../eslint-rules/no-duplicate-fold-marker.cjs'); const requireSubprocessTimeout = require('../eslint-rules/require-subprocess-timeout.cjs'); const requireRegisteredExit = require('../eslint-rules/require-registered-exit.cjs'); const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2022, sourceType: 'commonjs', }, }); // ─── no-source-grep ────────────────────────────────────────────────────────── describe('no-source-grep rule', () => { test('rule module exports a create function', () => { assert.strictEqual(typeof noSourceGrep.create, 'function'); }); test('valid: readFileSync on .md file is allowed', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const fs = require('fs'); const path = require('path'); const content = fs.readFileSync(path.join(__dirname, '..', 'docs', 'readme.md'), 'utf-8'); content.includes('hello'); `, filename: 'tests/foo.test.cjs', }, { code: ` const fs = require('fs'); const path = require('path'); const content = fs.readFileSync(path.join(__dirname, '..', 'gsd-core', 'workflows', 'config.json'), 'utf-8'); content.includes('key'); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('invalid: readFileSync on .cjs source file followed by .includes()', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const src = fs.readFileSync(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'io.cjs'), 'utf-8'); src.includes('someFunction'); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('invalid: readFileSync on .cjs source file followed by .match()', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'foo.cjs'), 'utf-8'); src.match(/pattern/); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('valid: allow-test-rule annotation adjacent to the read exempts that site (#3508: site-scoped, not file-wide)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { // The marker sits directly above the read+search it suppresses. code: ` const fs = require('fs'); const path = require('path'); // allow-test-rule: pending migration const src = fs.readFileSync(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'io.cjs'), 'utf-8'); src.includes('someFunction'); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('valid: require() of a .cjs file is allowed (not readFileSync)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const mod = require('../gsd-core/bin/lib/io.cjs'); mod.someMethod(); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); }); // ─── no-source-grep widening (#3502 / Phase 3 of #3464) ───────────────────── // // One RuleTester case per row of .gsd/phase/chore-3464-widen-source-grep/ // 50-test-matrix.md. Row numbers in test names refer to that matrix. describe('no-source-grep rule — widening (#3502)', () => { test('row 1: baseline literal .cjs read + .includes() (happy regression)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); src.includes('x'); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 2: .cts source read + .match() (gap B)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const ROOT = '/repo'; const src = fs.readFileSync(path.join(ROOT, 'src', 'verification.cts'), 'utf-8'); src.match(/x/); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 3: .mts source read + .match() (gap B)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const ROOT = '/repo'; const src = fs.readFileSync(path.join(ROOT, 'src', 'x.mts'), 'utf-8'); src.match(/x/); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 4: .mjs source read + .match() (gap B)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const ROOT = '/repo'; const src = fs.readFileSync(path.join(ROOT, 'src', 'x.mjs'), 'utf-8'); src.match(/x/); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 5: .matchAll() on a tracked read (gap A)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); src.matchAll(/x/g); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 6: regex.test(tracked) (gap A, argument-side detection)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); const re = /x/; re.test(src); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 7: /lit/.test(tracked) (gap A, argument-side detection)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); /x/.test(src); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 8: .split() / .replace() probes (gap A)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); src.split('\\n'); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, { code: ` const fs = require('fs'); const path = require('path'); const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); src.replace(/x/, ''); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 9: two-hop derived variable (gap C)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); function strip(x) { return x; } const a = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); const b = strip(a); b.match(/x/); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 10: three-hop derived variable — at the depth bound (gap C, boundary)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const a = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); const b = a; const c = b; c.includes('x'); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 11: hop chain beyond the configured depth is a documented limit (gap C, boundary)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const fs = require('fs'); const path = require('path'); const a = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); const b = a; const c = b; const d = c; d.includes('x'); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('row 12: shadowed same-name param — false-positive guard (gap D)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const fs = require('fs'); const path = require('path'); const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); const fn = (src) => src.replace(/x/, 'y'); fn('unrelated'); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('row 13: same name, sibling block scopes — false-positive guard (gap D)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const fs = require('fs'); const path = require('path'); { const c = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); } { const c = 'x'; c.includes('y'); } `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('row 14: .md literal read + .includes() (negative space, unchanged)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const fs = require('fs'); const path = require('path'); const content = fs.readFileSync(path.join(__dirname, '..', 'workflows', 'a.md'), 'utf-8'); content.includes('x'); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('row 15: .json literal read + .match() (negative space, unchanged)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const fs = require('fs'); const path = require('path'); const content = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.json'), 'utf-8'); content.match(/x/); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('row 16: dynamic path variable → .includes() — deliberately not flagged (rejected widening)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const fs = require('fs'); function readIt(p) { const content = fs.readFileSync(p, 'utf-8'); content.includes('x'); } `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('row 17: tracked read, no text search (negative space, unchanged)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const fs = require('fs'); const path = require('path'); const data = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8')); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('row 18: require() of a .cjs (negative space, unchanged)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const mod = require('../lib/a.cjs'); mod.someMethod(); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('row 19: a marker adjacent to the read+search suppresses it (#3508: site-scoped, not file-wide)', () => { // The raw marker text is assembled via string concatenation so this // FILE's own bytes never contain a contiguous "allow" + "-test-rule:" // token (scripts/lint-allow-test-rule-refs.cjs does a raw whole-file // substring scan). At RuleTester-run time the concatenation resolves to // a real single-line comment, which the rule under test honors normally. // The marker sits directly above the read+search (site-scoped, #3508), // not merely somewhere earlier in the file (the pre-#3508 file-wide form // this row originally exercised). const marker = '// ' + 'allow' + '-test-rule: split marker for row 19, see #3502'; const code = [ "const fs = require('fs');", "const path = require('path');", marker, "const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); src.includes('x');", ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('row 20: marker text inside a string literal (not a comment) does not suppress', () => { // Same split-marker technique as row 19, applied to a STRING literal // (not a comment) — this row exists to prove the rule's suppression // check only honors an actual comment, per the #3465 discriminator. const stringMarkerLine = "const note = '" + 'allow' + "-test-rule: this is just data, not a directive';"; const code = [ "const fs = require('fs');", "const path = require('path');", stringMarkerLine, "const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8');", 'src.includes(note);', ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); }); // ─── no-source-grep site-scoped suppression (#3508 / Phase 4 of #3464) ────── // // One RuleTester case per row of // .gsd/phase/chore-3464-site-scoped-suppression/50-test-matrix.md, rows 1-12. // Row 4 is the one that actually proves the defect is closed: file-wide // amnesty is gone, so a marker adjacent to one violation must NOT reach an // unrelated violation later in the same file. Rows 3 and 6 are the // compatibility guards (prose between marker and read; marker + zero // violations) that must keep working or this would break the 277 // marker-bearing files that rely on file-level markers being a documented // no-op when there's nothing to suppress. // // Marker text is always assembled via string concatenation (`AT` below) so // THIS file's raw bytes never contain a contiguous "allow" + "-test-rule:" // token — same fixture-host discipline as the row 19/20 cases above // (scripts/lint-allow-test-rule-refs.cjs does a raw whole-file substring // scan and must not newly count this file). // // NOTE: row 9's fixture length is tied to MAX_MARKER_LOOKAHEAD_LINES (8) in // eslint-rules/no-source-grep.cjs — if that constant changes, this fixture's // filler-line count must change with it. // Row 12 ("marker with no #NNN") is explicitly a script-level check, not a // RuleTester case (test-matrix.md marks it "(script, not RuleTester)") — // it's covered by `node scripts/lint-allow-test-rule-refs.cjs` instead. describe('no-source-grep rule — site-scoped suppression (#3508)', () => { const AT = 'allow' + '-test-rule:'; test('row 1: marker directly above the read+search is suppressed (site-scoped)', () => { const code = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason (#1)`, "const s = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); s.includes('x');", ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [{ code, filename: 'tests/foo.test.cjs' }], invalid: [], }); }); test('row 2: marker trailing on the same line as the search is suppressed', () => { const code = [ "const fs = require('fs');", "const path = require('path');", `const s = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); s.includes('x'); // ${AT} reason (#1)`, ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [{ code, filename: 'tests/foo.test.cjs' }], invalid: [], }); }); test('row 3: marker with prose lines between it and the read is still suppressed (repo real-style guard)', () => { const code = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason (#1)`, '// continuation prose line one explaining the reason', '// continuation prose line two continuing the explanation', "const s = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); s.includes('x');", ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [{ code, filename: 'tests/foo.test.cjs' }], invalid: [], }); }); test('row 4: marker adjacent to V1 does NOT reach an unrelated V2 later in the file (the defect this phase closes)', () => { const filler = Array.from({ length: 40 }, (_, i) => `// unrelated filler line ${i + 1}, pushing V2 well past the lookahead bound`); const lines = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason for V1 (#1)`, "const s1 = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); s1.includes('x');", ...filler, "const s2 = fs.readFileSync(path.join(__dirname, '..', 'lib', 'b.cjs'), 'utf-8'); s2.includes('y');", ]; const code = lines.join('\n'); const v2Line = lines.length; // s2's line is the last line of the fixture ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code, filename: 'tests/foo.test.cjs', // Exactly ONE error, reported at V2 -- V1 stays suppressed, and the // marker's reach does NOT extend to the unrelated V2 40 lines later. errors: [{ messageId: 'noSourceGrep', line: v2Line }], }, ], }); }); test('row 5: marker far above a violation with no marker text of its own is not suppressed', () => { const filler = Array.from({ length: 100 }, (_, i) => `// unrelated filler line ${i + 1}`); const lines = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason (#1)`, ...filler, "const s = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); s.includes('x');", ]; const code = lines.join('\n'); const violationLine = lines.length; ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep', line: violationLine }], }, ], }); }); test('row 6: file with a marker and zero violations stays green (the 277 inert-marker files compatibility guard)', () => { const code = [ `// ${AT} reason (#1)`, "const fs = require('fs');", "const path = require('path');", "const content = fs.readFileSync(path.join(__dirname, '..', 'docs', 'readme.md'), 'utf-8');", "content.includes('hello');", ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [{ code, filename: 'tests/foo.test.cjs' }], invalid: [], }); }); test('row 7: no marker, one violation is flagged (baseline unchanged)', () => { const code = [ "const fs = require('fs');", "const path = require('path');", "const s = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); s.includes('x');", ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 8: two violations, two adjacent markers -- per-site marking works', () => { const code = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason for V1 (#1)`, "const s1 = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); s1.includes('x');", `// ${AT} reason for V2 (#1)`, "const s2 = fs.readFileSync(path.join(__dirname, '..', 'lib', 'b.cjs'), 'utf-8'); s2.includes('y');", ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [{ code, filename: 'tests/foo.test.cjs' }], invalid: [], }); }); test('row 9: marker beyond the lookahead bound does not suppress (the bound is where it claims)', () => { // MAX_MARKER_LOOKAHEAD_LINES is 8 in eslint-rules/no-source-grep.cjs. // 9 filler comment lines between the marker and the read pushes the gap // to 10 lines (> 8), just past the bound. const filler = Array.from({ length: 9 }, (_, i) => `// filler comment line ${i + 1}`); const lines = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason (#1)`, ...filler, "const s = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); s.includes('x');", ]; const code = lines.join('\n'); const violationLine = lines.length; ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep', line: violationLine }], }, ], }); }); test('row 10: marker text inside a fixture string (not a real comment) is not a directive', () => { // The marker-looking text lives inside a STRING LITERAL in the linted // fixture, never as a `//` comment -- ESLint's comment AST (what the // rule inspects) never sees string-literal contents, so this must not // suppress the real, unmarked violation below it (the #3465 lesson). const code = [ "const fs = require('fs');", "const path = require('path');", `const note = 'not a directive: ${AT} fake reason';`, "const s = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); s.includes(note);", ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('row 11: marker citing #NNN on the same line still suppresses (citation contract unaffected)', () => { const code = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason for this read (#3508)`, "const s = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); s.includes('x');", ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [{ code, filename: 'tests/foo.test.cjs' }], invalid: [], }); }); // ─── read-site suppression (adversarial-review fix, ITEM 1) ──────────── // // A violation is fundamentally about a read+search PAIR. Before this fix, // a marker adjacent to the readFileSync() call (the intuitive annotation // spot) failed to suppress once the search happened on a later line, // because the readFileSync assignment line itself is "real code" and // broke comment-purity on the marker->search lookahead path. The rule now // also checks a marker's site-scoping against the ORIGINATING read call's // own line, independent of the marker->search path. test('valid: marker directly above the read, search on the very next (non-comment) line', () => { const code = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason (#1)`, "const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf8');", "src.includes('x');", ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [{ code, filename: 'tests/foo.test.cjs' }], invalid: [], }); }); test('valid: marker directly above the read, search several comment-pure lines later (read-line real code no longer breaks the marker->search path)', () => { const code = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason (#1)`, "const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf8');", '// comment-pure line one', '// comment-pure line two', '// comment-pure line three', "src.includes('x');", ].join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [{ code, filename: 'tests/foo.test.cjs' }], invalid: [], }); }); test('invalid: marker above the read suppresses that pair, but an unrelated tracked variable searched further down is still flagged', () => { const lines = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason for V1 (#1)`, "const s1 = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf8');", "s1.includes('x');", "const s2 = fs.readFileSync(path.join(__dirname, '..', 'lib', 'b.cjs'), 'utf8');", "s2.includes('y');", ]; const code = lines.join('\n'); const v2Line = lines.length; // s2.includes(...) is the last line ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep', line: v2Line }], }, ], }); }); test('invalid: marker far from both the read and the search is still flagged', () => { const filler = Array.from({ length: 20 }, (_, i) => `// unrelated filler line ${i + 1}`); const lines = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason (#1)`, ...filler, "const s = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf8');", "s.includes('x');", ]; const code = lines.join('\n'); const violationLine = lines.length; // s.includes(...) is the last line ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep', line: violationLine }], }, ], }); }); test('boundary: marker exactly MAX_MARKER_LOOKAHEAD_LINES (8) above the read is suppressed via the read-site path', () => { // 7 comment-pure filler lines between the marker and the read puts the // read exactly 8 lines below the marker -- the inclusive boundary. const filler = Array.from({ length: 7 }, (_, i) => `// filler comment line ${i + 1}`); const lines = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason (#1)`, ...filler, "const s = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf8');", "s.includes('x');", ]; const code = lines.join('\n'); ruleTester.run('no-source-grep', noSourceGrep, { valid: [{ code, filename: 'tests/foo.test.cjs' }], invalid: [], }); }); test('boundary: marker one line beyond MAX_MARKER_LOOKAHEAD_LINES (9) above the read is not suppressed', () => { // 8 comment-pure filler lines between the marker and the read puts the // read 9 lines below the marker -- one past the inclusive boundary. const filler = Array.from({ length: 8 }, (_, i) => `// filler comment line ${i + 1}`); const lines = [ "const fs = require('fs');", "const path = require('path');", `// ${AT} reason (#1)`, ...filler, "const s = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf8');", "s.includes('x');", ]; const code = lines.join('\n'); const violationLine = lines.length; // s.includes(...) is the last line ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep', line: violationLine }], }, ], }); }); }); // ─── no-source-grep hop-propagation value-shape (adversarial-review fix) ──── // // minTrackedHop() used to walk EVERY Identifier under a derivation's RHS // and treat any bare reference to a tracked variable as propagating, // regardless of whether the derived VALUE could still carry text (e.g. // `.length`). These rows cover the value-shape gate that replaced that // blind walk: propagate only through derivations that plausibly still // carry the source file's text; do not propagate through scalar-producing // shapes (member access, numeric/boolean methods, comparisons, Number() // et al). describe('no-source-grep rule — hop-propagation value-shape (adversarial-review fix)', () => { test('valid: .length derivation does not propagate (reported false-positive repro)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const fs = require('fs'); const path = require('path'); const raw = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); const len = raw.length; if (/^\\d+$/.test(len)) {} `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('invalid: numeric-returning method derivation does not cascade to a second error', () => { // raw.indexOf('x') is itself already flagged directly (indexOf is one // of the TEXT_METHODS this rule flags on a tracked receiver, unrelated // to hop propagation). The important assertion here is that there is // exactly ONE error, not two: the numeric result of .indexOf() must // NOT stay tracked, so String(n).includes('1') is not a second finding. ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const raw = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); const n = raw.indexOf('x'); String(n).includes('1'); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('invalid: boolean-returning method derivation does not cascade to a second error', () => { // Same shape as above with a boolean-returning method: raw.includes('x') // is itself already flagged directly. The boolean result must NOT stay // tracked, so String(ok).includes('true') is not a second finding. ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const raw = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); const ok = raw.includes('x'); String(ok).includes('true'); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('valid: comparison of a tracked derivation does not propagate', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [ { code: ` const fs = require('fs'); const path = require('path'); const raw = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); const same = raw.length === 0; `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('invalid: string-returning method derivation still propagates and is caught', () => { // raw.replace(...) is flagged directly (replace is a TEXT_METHOD, same // as the indexOf/includes rows above) AND the string-returning result // (b) correctly stays tracked, so b.includes('y') is a second, distinct // finding. Two errors total, both real. ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const raw = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); const b = raw.replace(/x/, ''); b.includes('y'); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }, { messageId: 'noSourceGrep' }], }, ], }); }); test('invalid: template-literal derivation still propagates and is caught', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const raw = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); const b = \`\${raw}\`; b.match(/y/); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); test('invalid: direct .includes() on the tracked source read is unchanged (no regression)', () => { ruleTester.run('no-source-grep', noSourceGrep, { valid: [], invalid: [ { code: ` const fs = require('fs'); const path = require('path'); const raw = fs.readFileSync(path.join(__dirname, '..', 'lib', 'a.cjs'), 'utf-8'); raw.includes('x'); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noSourceGrep' }], }, ], }); }); }); // ─── no-magic-sleep-in-tests ───────────────────────────────────────────────── describe('no-magic-sleep-in-tests rule', () => { test('rule module exports a create function', () => { assert.strictEqual(typeof noMagicSleepInTests.create, 'function'); }); test('valid: setTimeout used outside tests (no-op since rule only applies to *.test.cjs)', () => { // Rule only applies to *.test.cjs files; a non-test filename is always valid ruleTester.run('no-magic-sleep-in-tests', noMagicSleepInTests, { valid: [ { code: ` const delay = new Promise(resolve => setTimeout(resolve, 100)); `, filename: 'scripts/some-script.cjs', }, ], invalid: [], }); }); test('invalid: Atomics.wait() in test file', () => { ruleTester.run('no-magic-sleep-in-tests', noMagicSleepInTests, { valid: [], invalid: [ { code: ` const shared = new SharedArrayBuffer(4); const arr = new Int32Array(shared); Atomics.wait(arr, 0, 0, 100); `, filename: 'tests/some.test.cjs', errors: [{ messageId: 'atomicsWaitSleep' }], }, ], }); }); test('invalid: setTimeout used for synchronization in Promise in test file', () => { ruleTester.run('no-magic-sleep-in-tests', noMagicSleepInTests, { valid: [], invalid: [ { code: ` async function waitABit() { await new Promise(resolve => setTimeout(resolve, 50)); } `, filename: 'tests/some.test.cjs', errors: [{ messageId: 'setTimeoutSync' }], }, ], }); }); test('valid: setTimeout with callback (not synchronization pattern) in test file', () => { // A setTimeout with no second arg or with a callback that does real work // is allowed. The rule only flags the await-new-Promise(setTimeout) pattern. ruleTester.run('no-magic-sleep-in-tests', noMagicSleepInTests, { valid: [ { code: ` function doSomethingLater(cb) { setTimeout(cb, 100); } `, filename: 'tests/some.test.cjs', }, ], invalid: [], }); }); }); // ─── no-elapsed-assertion ───────────────────────────────────────────────────── describe('no-elapsed-assertion rule', () => { test('rule module exports a create function', () => { assert.strictEqual(typeof noElapsedAssertion.create, 'function'); }); test('valid: assert on non-timing property', () => { ruleTester.run('no-elapsed-assertion', noElapsedAssertion, { valid: [ { code: ` const assert = require('node:assert/strict'); const result = { count: 5 }; assert.equal(result.count, 5); `, filename: 'tests/foo.test.cjs', }, { code: ` const assert = require('node:assert/strict'); assert.ok(result.success); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('invalid: assert on .elapsed property', () => { ruleTester.run('no-elapsed-assertion', noElapsedAssertion, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); const result = { elapsed: 150 }; assert.ok(result.elapsed < 200); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noElapsedAssertion' }], }, ], }); }); test('invalid: assert on .duration property', () => { ruleTester.run('no-elapsed-assertion', noElapsedAssertion, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.equal(stats.duration, 100); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noElapsedAssertion' }], }, ], }); }); test('invalid: assert on .took property', () => { ruleTester.run('no-elapsed-assertion', noElapsedAssertion, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.ok(result.took < 500); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noElapsedAssertion' }], }, ], }); }); test('invalid: assert on .ms property', () => { ruleTester.run('no-elapsed-assertion', noElapsedAssertion, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.ok(result.ms > 0); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noElapsedAssertion' }], }, ], }); }); test('invalid: assert.equal with timing comparison', () => { ruleTester.run('no-elapsed-assertion', noElapsedAssertion, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.equal(result.elapsed > 0, true); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noElapsedAssertion' }], }, ], }); }); // ─── #3987: camelCase/suffixed evasion (elapsedMs escaped the exact-name // regex; CI caught the resulting flake instead of lint catching the // anti-pattern) ─────────────────────────────────────────────────────── test('invalid: assert on .elapsedMs property (the exact identifier that evaded the pre-widening exact-name regex)', () => { ruleTester.run('no-elapsed-assertion', noElapsedAssertion, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); const result = { elapsedMs: 150 }; assert.ok(result.elapsedMs < 200); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noElapsedAssertion' }], }, ], }); }); test('invalid: assert on tookMs/durationMs/msElapsed/elapsedTime/startMs/endMs — camelCase family the widened rule must catch', () => { ruleTester.run('no-elapsed-assertion', noElapsedAssertion, { valid: [], invalid: [ { code: `assert.ok(x.tookMs < 500);`, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noElapsedAssertion' }], }, { code: `assert.ok(x.durationMs > 0);`, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noElapsedAssertion' }], }, { code: `assert.ok(x.msElapsed > 0);`, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noElapsedAssertion' }], }, { code: `assert.ok(x.elapsedTime < 1000);`, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noElapsedAssertion' }], }, { code: `assert.ok(x.endMs - x.startMs < 100);`, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noElapsedAssertion' }], }, ], }); }); test('valid: non-timing camelCase identifiers containing "ms" as a plain substring do not flag (params/items/forms/terms/dirnames — and a configured-bound timeoutMs)', () => { ruleTester.run('no-elapsed-assertion', noElapsedAssertion, { valid: [ { code: `assert.equal(params.length, 2);`, filename: 'tests/foo.test.cjs' }, { code: `assert.equal(items.length, 0);`, filename: 'tests/foo.test.cjs' }, { code: `assert.ok(forms.valid);`, filename: 'tests/foo.test.cjs' }, { code: `assert.equal(terms.length, 3);`, filename: 'tests/foo.test.cjs' }, { code: `assert.equal(dirnames.length, 1);`, filename: 'tests/foo.test.cjs' }, { // A configured bound (deterministic pass-through), not a measured // wall-clock elapsed value — must not be caught by the widening. code: `assert.equal(seen[0].timeoutMs, HOOK_FANOUT_TIMEOUT_MS);`, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); }); // ─── no-raw-rmsync-in-tests ────────────────────────────────────────────────── describe('no-raw-rmsync-in-tests rule', () => { test('rule module exports a create function', () => { assert.strictEqual(typeof noRawRmsyncInTests.create, 'function'); }); // ── INVALID cases (must error) ──────────────────────────────────────────── test('invalid: fs.rmSync() in a test file', () => { ruleTester.run('no-raw-rmsync-in-tests', noRawRmsyncInTests, { valid: [], invalid: [ { code: ` const fs = require('fs'); fs.rmSync(tmpDir, { recursive: true, force: true }); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noRawRmSync' }], }, ], }); }); test('invalid: computed member fs["rmSync"]() in a test file', () => { ruleTester.run('no-raw-rmsync-in-tests', noRawRmsyncInTests, { valid: [], invalid: [ { code: ` const fs = require('fs'); fs['rmSync'](d, { recursive: true, force: true }); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noRawRmSync' }], }, ], }); }); test('invalid: destructured rmSync from require("fs") in a test file', () => { ruleTester.run('no-raw-rmsync-in-tests', noRawRmsyncInTests, { valid: [], invalid: [ { code: ` const { rmSync } = require('fs'); rmSync(d, { recursive: true, force: true }); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noRawRmSync' }], }, ], }); }); test('invalid: aliased const del = fs.rmSync; del() in a test file', () => { ruleTester.run('no-raw-rmsync-in-tests', noRawRmsyncInTests, { valid: [], invalid: [ { code: ` const fs = require('fs'); const del = fs.rmSync; del(d, { recursive: true, force: true }); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noRawRmSync' }], }, ], }); }); test('invalid: allow-test-rule annotation no longer suppresses this rule (Defect 1 fixed)', () => { // A file with // allow-test-rule: must still error // on raw rmSync calls. The file-level annotation is for no-source-grep only. ruleTester.run('no-raw-rmsync-in-tests', noRawRmsyncInTests, { valid: [], invalid: [ { code: ` // allow-test-rule: source-text-is-the-product const fs = require('fs'); fs.rmSync(d, { recursive: true, force: true }); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'noRawRmSync' }], }, ], }); }); // ── VALID cases (must NOT error) ────────────────────────────────────────── test('valid: helpers.cleanup() in a test file (no error)', () => { ruleTester.run('no-raw-rmsync-in-tests', noRawRmsyncInTests, { valid: [ { code: ` const { cleanup } = require('../helpers.cjs'); cleanup(tmpDir); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('valid: bare rmSync() that is NOT fs-derived (local function) is not flagged', () => { // A locally defined function named rmSync must not be flagged — the rule // only tracks names that were bound from require("fs"). ruleTester.run('no-raw-rmsync-in-tests', noRawRmsyncInTests, { valid: [ { code: ` const rmSync = () => {}; rmSync(d); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); // NOTE: The inline `// eslint-disable-next-line local/no-raw-rmsync-in-tests -- reason` // escape hatch is handled entirely by ESLint's own disable-comment mechanism and // cannot be unit-tested here via RuleTester (RuleTester runs the rule under a // different internal namespace so the comment's rule-id doesn't match). The escape // hatch works correctly when ESLint processes real files via `npx eslint`. test('valid: fs.rmSync() in a non-test file (rule is inert outside *.test.cjs)', () => { ruleTester.run('no-raw-rmsync-in-tests', noRawRmsyncInTests, { valid: [ { code: ` const fs = require('fs'); fs.rmSync(tmpDir, { recursive: true, force: true }); `, filename: 'scripts/foo.cjs', }, ], invalid: [], }); }); test('valid: member access / assignment without calling (not a CallExpression)', () => { ruleTester.run('no-raw-rmsync-in-tests', noRawRmsyncInTests, { valid: [ { code: ` const fs = require('fs'); const orig = fs.rmSync; fs.rmSync = orig; `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); }); // ─── no-tautological-assert ────────────────────────────────────────────────── describe('no-tautological-assert rule', () => { test('rule module exports a create function', () => { assert.strictEqual(typeof noTautologicalAssert.create, 'function'); }); // ── VALID cases (must NOT error) ────────────────────────────────────────── test('valid: assert.ok with a non-literal identifier argument', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [ { code: ` const assert = require('node:assert/strict'); assert.ok(result); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('valid: assert.strictEqual with mixed literal/identifier arguments', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [ { code: ` const assert = require('node:assert/strict'); assert.strictEqual(actual, true); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('valid: assert.strictEqual with identifier and numeric literal', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [ { code: ` const assert = require('node:assert/strict'); assert.strictEqual(x, 5); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('valid: assert.ok with a CallExpression argument', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [ { code: ` const assert = require('node:assert/strict'); assert.ok(fn()); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('valid: assert.deepStrictEqual with two identifier arguments', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [ { code: ` const assert = require('node:assert/strict'); assert.deepStrictEqual(got, expected); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('valid: assert.strictEqual with two different identifier arguments', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [ { code: ` const assert = require('node:assert/strict'); assert.strictEqual(a, b); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); // ── INVALID cases (must error) ──────────────────────────────────────────── test('invalid: assert.ok(true) — always-truthy boolean literal', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.ok(true); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalTruthiness' }], }, ], }); }); test('invalid: assert(true) — bare assert with always-truthy boolean literal', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert'); assert(true); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalTruthiness' }], }, ], }); }); test('invalid: assert.ok(1) — always-truthy non-zero numeric literal', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.ok(1); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalTruthiness' }], }, ], }); }); test('invalid: assert.ok("always") — always-truthy non-empty string literal', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.ok('always'); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalTruthiness' }], }, ], }); }); test('invalid: assert.ok([]) — always-truthy array literal', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.ok([]); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalTruthiness' }], }, ], }); }); test('invalid: assert.ok(cond || true) — logical OR whose right side is true', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.ok(cond || true); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalTruthiness' }], }, ], }); }); test('invalid: assert.strictEqual(true, true) — identical boolean literals', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.strictEqual(true, true); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalEquality' }], }, ], }); }); test('invalid: assert.equal(1, 1) — identical numeric literals', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.equal(1, 1); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalEquality' }], }, ], }); }); // ── Fix #3: true || cond (left-side true) ──────────────────────────────── test('invalid: assert.ok(true || x) — left side is literal true (always short-circuits)', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.ok(true || x); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalTruthiness' }], }, ], }); }); test('invalid: assert(true || y) — bare assert, left side is literal true', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert'); assert(true || y); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalTruthiness' }], }, ], }); }); // ── Fix #4: empty [] / {} deep-equality ────────────────────────────────── test('invalid: assert.deepStrictEqual([], []) — two empty arrays are always deep-equal', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.deepStrictEqual([], []); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalEquality' }], }, ], }); }); test('invalid: assert.deepStrictEqual({}, {}) — two empty objects are always deep-equal', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [], invalid: [ { code: ` const assert = require('node:assert/strict'); assert.deepStrictEqual({}, {}); `, filename: 'tests/foo.test.cjs', errors: [{ messageId: 'tautologicalEquality' }], }, ], }); }); // ── Conservative: non-empty arrays/objects must NOT be flagged ──────────── test('valid: assert.deepStrictEqual([1], [2]) — non-empty arrays with different content are not flagged', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [ { code: ` const assert = require('node:assert/strict'); assert.deepStrictEqual([1], [2]); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); test('valid: assert.deepStrictEqual(got, expected) — identifier arguments are not flagged', () => { ruleTester.run('no-tautological-assert', noTautologicalAssert, { valid: [ { code: ` const assert = require('node:assert/strict'); assert.deepStrictEqual(got, expected); `, filename: 'tests/foo.test.cjs', }, ], invalid: [], }); }); }); // ─── no-adhoc-markdown-parsing ─────────────────────────────────────────────── describe('no-adhoc-markdown-parsing rule', () => { test('rule module exports a create function', () => { assert.strictEqual(typeof noAdhocMarkdownParsing.create, 'function'); }); // ── #3951 B6(b): filename-gate reach — src/**/*.cts, subdirectories included ── // The gate used to be `/(?:^|\/)src\/[^/]+\.cts$/` (flat-only), which // silently exempted 28 files in src/ subdirectories // (health-diagnostic-rules/, installer-migrations/, observability/, // host-integration-adapters/, vendor/) even though the eslint.config.mjs // registration (src/**/*.cts) already covers them. These three rows pin // that the gate and the registration agree — a subdirectory path is // linted, a flat src/ path keeps working, and a path outside src/ stays // exempt. test('invalid: a table-regex fingerprint under a src/ SUBDIRECTORY is linted (gate reach)', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { code: String.raw`const cellPattern = /\|[^|]*\|/;`, filename: 'src/health-diagnostic-rules/some-check.cts', errors: [{ messageId: 'tableRegex' }], }, ], }); }); test('valid: the same fingerprint under a FLAT src/*.cts path still is linted (regression, not exempt)', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { code: String.raw`const cellPattern = /\|[^|]*\|/;`, filename: 'src/some-module.cts', errors: [{ messageId: 'tableRegex' }], }, ], }); }); test('valid: the same fingerprint OUTSIDE src/+tests/+scripts/ is NOT linted (gate and registration must agree)', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: String.raw`const cellPattern = /\|[^|]*\|/;`, filename: 'gsd-core/bin/lib/foo.cjs', }, ], invalid: [], }); }); // ── #3951 Rung B: filename-gate reach — tests/**/*.cjs and scripts/**/*.cjs ── // The gate self-restricted to src/**/*.cts only. eslint.config.mjs also // registers the rule on tests/**/*.cjs and scripts/**/*.cjs (Rung B); these // rows pin that the gate and the registration agree for BOTH new globs — // a path each registration covers must not be silently skipped by the // gate, and a path outside all three globs stays exempt (mirrors the // src/ subdirectory rows above, which pinned the same contract for #3951 // B6(b)). test('invalid: a table-regex fingerprint under tests/**/*.cjs is linted (gate/registration parity)', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { code: String.raw`const cellPattern = /\|[^|]*\|/;`, filename: 'tests/some.test.cjs', errors: [{ messageId: 'tableRegex' }], }, ], }); }); test('invalid: a table-regex fingerprint under a tests/ SUBDIRECTORY is linted (gate reach)', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { code: String.raw`const cellPattern = /\|[^|]*\|/;`, filename: 'tests/fixtures/some.test.cjs', errors: [{ messageId: 'tableRegex' }], }, ], }); }); test('invalid: a table-regex fingerprint under scripts/**/*.cjs is linted (gate/registration parity)', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { code: String.raw`const cellPattern = /\|[^|]*\|/;`, filename: 'scripts/some-tool.cjs', errors: [{ messageId: 'tableRegex' }], }, ], }); }); // ── POSITIVE cases: flag fence-block-strip and section-collect ──────────── test('invalid: fence-block-strip regex with triple-backtick and multiline body', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { // /```[\s\S]*?```/ — triple-backtick + [\s\S] body → flagged as fenceRegex code: String.raw`const stripFences = /` + '```' + String.raw`[\s\S]*?` + '```' + '/;', filename: 'src/some-module.cts', errors: [{ messageId: 'fenceRegex' }], }, ], }); }); test('invalid: fence-block-strip regex with triple-tilde and multiline body', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { // /~~~[\s\S]*?~~~/ — triple-tilde + [\s\S] body → flagged as fenceRegex code: String.raw`const stripTildes = /~~~[\s\S]*?~~~/;`, filename: 'src/some-module.cts', errors: [{ messageId: 'fenceRegex' }], }, ], }); }); test('invalid: section-collect regex with heading capture, multiline body, heading lookahead', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { // /(##\s*X\n)([\s\S]*?)(?=\n##|$)/ — the classic section-collect fingerprint code: String.raw`const pat = /(##\s*X\n)([\s\S]*?)(?=\n##|$)/;`, filename: 'src/some-module.cts', errors: [{ messageId: 'sectionCollect' }], }, ], }); }); // ── NEGATIVE cases: single-line fence tests and heading matches NOT flagged ─ test('valid: bare single-line fence-opener /^```/ is NOT flagged', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: 'const fenceRegex = /^' + '```' + '/;', filename: 'src/some-module.cts', }, ], invalid: [], }); }); test('valid: /^\\s*(?:```|~~~)/ fence-line test is NOT flagged', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: String.raw`const isFenceLine = /^\s*(?:` + '```' + String.raw`|~~~)/;`, filename: 'src/some-module.cts', }, ], invalid: [], }); }); test('valid: /^#\\s+/ single-line title-find is NOT flagged', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: String.raw`const titleRe = /^#\s+/;`, filename: 'src/some-module.cts', }, ], invalid: [], }); }); test('valid: /^###\\s+(.+?)\\s*$/ single-line heading-category match is NOT flagged', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: String.raw`const headingRe = /^###\s+(.+?)\s*$/;`, filename: 'src/some-module.cts', }, ], invalid: [], }); }); test('valid: /^(#{1,6})\\s+(.*)/ single-line heading match is NOT flagged', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: String.raw`const headingM = line.match(/^(#{1,6})\s+(.*)/);`, filename: 'src/some-module.cts', }, ], invalid: [], }); }); test('valid: seam usage (no regex, just an import reference) is NOT flagged', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: ` const { collectSection } = require('./markdown-sectionizer'); const result = collectSection(content, 'Introduction'); `, filename: 'src/some-module.cts', }, ], invalid: [], }); }); test('valid: annotated fence-block-strip with allow-adhoc-markdown is NOT flagged', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { // Trailing annotation on the same line suppresses the finding code: 'const stripFences = /```' + String.raw`[\s\S]*?` + '`' + '``/; // allow-adhoc-markdown: pre-seam write path; pending migration #1372', filename: 'src/some-module.cts', }, ], invalid: [], }); }); // #3951 Rung B: the gate's reach is src/**/*.cts, tests/**/*.cjs and // scripts/**/*.cjs — the same fingerprints under those three roots are now // linted, and the negative space (a path outside all three) stays exempt. test('invalid: fence-block-strip and section-collect fingerprints under tests/ and scripts/ are now flagged (gate/registration parity)', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { // Same fence-block-strip regex under tests/**/*.cjs → now linted code: String.raw`const stripFences = /~~~[\s\S]*?~~~/;`, filename: 'tests/some.test.cjs', errors: [{ messageId: 'fenceRegex' }], }, { // Same section-collect regex under scripts/**/*.cjs → now linted code: String.raw`const p = /(##\s*X\n)([\s\S]*?)(?=\n##|$)/;`, filename: 'scripts/helper.cjs', errors: [{ messageId: 'sectionCollect' }], }, ], }); }); test('valid: the same fence-block-strip fingerprint OUTSIDE src/+tests/+scripts/ stays NOT flagged (negative space preserved)', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: String.raw`const stripFences = /~~~[\s\S]*?~~~/;`, filename: 'gsd-core/bin/lib/foo.cjs', }, ], invalid: [], }); }); // ── TABLE-REGEX (ADR-2143 §7) ────────────────────────────────────────────── test('invalid: table-row/cell regex with escaped pipe and negated-pipe cell class', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { // /\|[^|]*\|/ — the classic hand-rolled table-row/cell scan fingerprint code: String.raw`const rowRe = /\|[^|]*\|/;`, filename: 'src/some-module.cts', errors: [{ messageId: 'tableRegex' }], }, ], }); }); test('invalid: table-cell regex with escaped-pipe class variant [^\\|]', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { code: String.raw`const cellRe = /\|\s*([^\|]+)\s*\|/;`, filename: 'src/some-module.cts', errors: [{ messageId: 'tableRegex' }], }, ], }); }); test('valid: parseMarkdownTable() seam call is NOT flagged (no regex literal)', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: ` const { parseMarkdownTable } = require('./markdown-table'); const result = parseMarkdownTable(sectionText); `, filename: 'src/some-module.cts', }, ], invalid: [], }); }); test('valid: escaped pipe alone (no negated-pipe cell class) is NOT flagged', () => { // A bare delimiter probe like /^\|/ or /\|\|/ has an escaped pipe but no // [^|] cell-capture class — not a table-row/cell scan, so it must stay // conservative and not fire. ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: String.raw`const isPipeDelim = /^\|/;`, filename: 'src/some-module.cts', }, { code: String.raw`const orDelim = /a\|b/;`, filename: 'src/some-module.cts', }, ], invalid: [], }); }); test('valid: annotated table-regex with allow-adhoc-markdown is NOT flagged', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: String.raw`const rowRe = /\|[^|]*\|/; // allow-adhoc-markdown: not a table scan, protocol-marker probe`, filename: 'src/some-module.cts', }, ], invalid: [], }); }); // #3951 Rung B: table-regex under scripts/**/*.cjs is now linted (gate/ // registration parity); the same fingerprint outside src/+tests/+scripts/ // stays exempt (negative space preserved). test('invalid: table-regex under scripts/**/*.cjs is now flagged (gate/registration parity)', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { code: String.raw`const rowRe = /\|[^|]*\|/;`, filename: 'scripts/helper.cjs', errors: [{ messageId: 'tableRegex' }], }, ], }); }); test('valid: the same table-regex fingerprint OUTSIDE src/+tests/+scripts/ stays NOT flagged', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [ { code: String.raw`const rowRe = /\|[^|]*\|/;`, filename: 'gsd-core/bin/lib/foo.cjs', }, ], invalid: [], }); }); // ── TABLE-REGEX via new RegExp() (#2143 Phase 4) ─ test('invalid: new RegExp() matching the table fingerprint', () => { ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, { valid: [], invalid: [ { // new RegExp('\|[^|]*\|') — doubled backslashes cook to a literal \| code: String.raw`const rowRe = new RegExp('\\|[^|]*\\|');`, filename: 'src/some-module.cts', errors: [{ messageId: 'tableRegex' }], }, ], }); }); test('invalid: new RegExp(