Files
msd-core/tests/eslint-rules.test.cjs
Tom Boucher bcf7b04864 chore(#2896): convert CONTEXT.md prose defect registry into enforced gates (#3325)
* chore(#2896): convert CONTEXT.md prose defect registry into enforced gates

Squashes the prior 4-commit sequence and fixes defects found while
resuming this branch: 5 orphaned/corrupted DEFECT fragment lines left
by an earlier botched edit, 17 "Source of truth: Memtrace `find_symbol`"
placeholders that had destroyed real file-path citations, and 3
DEFECT.GENERATIVE-* entries merged into one RULESET.GENERATIVE-FIX
predicate (policy, not an unenforced defect) to satisfy the zero
DEFECT.<NAME>.<field>= acceptance criterion.

Six mechanizable defects get real gates: DEFECT.UNBOUNDED-SUBPROCESS
(eslint-rules/require-subprocess-timeout.cjs), DEFECT.CANARY-VERSION-LEAK
(scripts/lint-canary-version-leak.cjs + version-gate.yml),
DEFECT.CHANGESET-PR-FIELD-DRIFT (findPrFieldDrift in changeset/lint.cjs),
DEFECT.FRONTMATTER-SCALAR-BROAD-GREP, DEFECT.REMOVED-BUT-NEEDED, and
DEFECT.DEFAULT-FLIP-DOCUMENTATION (new lint scripts, wired into lint:ci).
Already-enforced and unenforceable prose entries are deleted; the gate
is the record.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore(#2896): route the new lint tests' subprocess calls through the bounded process-seam helper

The 4 new test files for this PR's lint checks called cp.spawnSync/
execFileSync directly with no timeout, tripping this repo's own
existing local/no-unbounded-spawn ESLint rule. Route every one through
runNode/gitOrThrow (tests/helpers/process-seam.cjs,
tests/helpers/git-fixture.cjs) instead, matching the pattern already
used elsewhere in the suite (e.g. tests/changeset-lint.test.cjs).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: register claude-orchestration.cjs and regenerate stale generated indexes

Pre-existing drift on next, unrelated to this PR's own change, surfaced
by running lint:ci as part of verifying #2896: two cli_modules
(claude-orchestration.cjs, write-set.cjs) landed without a manifest
regen, and CONTEXT.md's own edits in this PR staled its two generated
indexes. Adds the missing docs/INVENTORY.md row for
claude-orchestration.cjs (write-set.cjs already had one — only its
manifest entry was stale) and regenerates
docs/INVENTORY-MANIFEST.json, docs/CONTEXT-INDEX.json, and
examples/dynamic-context-management/CONTEXT-INDEX.json.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#2896): default-flip-documentation lint's local fallback base was main, not next

Found in review: every other base-ref fallback in this repo (see
scripts/changeset/lint.cjs's DEFAULT_BASE, #2988) defaults to `next`,
the integration branch every PR actually targets — `main` is the
release branch. This script's local fallback (used only when
GITHUB_BASE_REF is unset, i.e. never in CI, but potentially on a local
or direct invocation) diffed against the wrong ref. No test exercised
the unset-env-var path, so it shipped unnoticed; every e2e test sets
GITHUB_BASE_REF explicitly and is unaffected by this fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#2896): stale eslint comment, overclaiming CONTEXT.md wording, and an incompletely-regenerated manifest

Found by the isolated Standards code-review pass:
- eslint.config.mjs's require-subprocess-timeout comment said "'warn'
  for now... flip to 'error' once migrated" while the rule already
  shipped as 'error' with all 8 sites migrated in the same commit —
  described a state that never existed.
- The CONTEXT.md pointer block claimed the rule's bounded call sites
  "never throw", but roadmap-upgrade.cts's pre-mutation clean-tree
  check correctly still throws on failure (it gates a destructive
  real-run migration; degrading to "assume clean" would risk clobbering
  uncommitted work) — softened the claim to describe both shapes
  accurately instead of overclaiming one.
- docs/INVENTORY-MANIFEST.json's claude-orchestration.cjs/write-set.cjs
  entries from the prior "fix: register claude-orchestration.cjs..."
  commit didn't actually land — re-running the generator now includes
  them; lint:generated-sync is green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* chore(#2896): backfill changeset pr field with the real PR number

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#2896): normalize buildCorpus file paths to POSIX in lint-removed-but-needed

Windows CI caught it: path.relative(root, abs) returns backslash-
separated paths on Windows, but findSurvivingReferences's package-lock
special case does file.startsWith('.github/workflows') — a forward-
slash literal. On Windows the check silently never matched, so
tests/removed-but-needed-lint.test.cjs's real-defect-shape fixture got
exit 0 instead of the expected exit 1. Normalize at the production
source (RULESET.CONTENT-PATH-NORMALIZATION) rather than the test side.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 12:55:52 -04:00

2109 lines
72 KiB
JavaScript

'use strict';
/**
* 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
*/
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 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: file with allow-test-rule annotation is exempt', () => {
ruleTester.run('no-source-grep', noSourceGrep, {
valid: [
{
// The allow annotation exempts the whole file
code: `
// allow-test-rule: pending migration
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',
},
],
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-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' }],
},
],
});
});
});
// ─── 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: <source-grep reason> 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');
});
// ── 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: [],
});
});
test('valid: rule is inert outside src/*.cts files', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
// Same fence-block-strip regex in a test file → rule does not apply
code: String.raw`const stripFences = /~~~[\s\S]*?~~~/;`,
filename: 'tests/some.test.cjs',
},
{
// Same regex in a scripts file → rule does not apply
code: String.raw`const p = /(##\s*X\n)([\s\S]*?)(?=\n##|$)/;`,
filename: 'scripts/helper.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: [],
});
});
test('valid: table-regex in a non-src/*.cts file is not flagged (rule is inert there)', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: String.raw`const rowRe = /\|[^|]*\|/;`,
filename: 'scripts/helper.cjs',
},
],
invalid: [],
});
});
// ── TABLE-REGEX via new RegExp(<Literal-string | TemplateLiteral>) (#2143 Phase 4) ─
test('invalid: new RegExp(<string literal>) 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(<template literal>) whose STATIC quasis match the table fingerprint (dynamic segment ignored)', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [],
invalid: [
{
// new RegExp(`^(\|\s*${phase}\.?\s[^|]*(?:\|[^\n]*))$`) — the exact
// roadmap.cts/phase.cts tableRowPattern shape, dynamic ${phase} in the middle.
code: 'const tableRowPattern = new RegExp(`^(\\\\|\\\\s*${phase}\\\\.?\\\\s[^|]*(?:\\\\|[^\\\\n]*))$`, \'im\');',
filename: 'src/some-module.cts',
errors: [{ messageId: 'tableRegex' }],
},
],
});
});
test('valid: new RegExp(`## Phase ${x}`) — dynamic heading pattern, static quasis are not table/section (non-table)', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: 'const headingRe = new RegExp(`## Phase ${x}`, \'i\');',
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('valid: new RegExp(someIdentifier) — pattern built elsewhere and referenced by variable is out of scope for this check', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: `
const pattern = buildRowPattern();
const rowRe = new RegExp(pattern, 'im');
`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
// ── new RegExp(identifier) resolved via resolveVariableInit scope walk (#2245 recall-hole fix) ─
test('invalid: new RegExp(identifier) resolves a const-declared identifier whose pattern matches the table fingerprint', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [],
invalid: [
{
// const tablePattern = '\|[^|]*\|' (doubled backslashes cook to a literal \|),
// then new RegExp(tablePattern) — the pattern is built one hop away via a
// const-declared identifier instead of inline, which used to escape the
// fingerprint check entirely (the recall hole this fix closes).
code: String.raw`
const tablePattern = '\\|[^|]*\\|';
const rowRe = new RegExp(tablePattern);
`,
filename: 'src/some-module.cts',
errors: [{ messageId: 'tableRegex' }],
},
],
});
});
test('valid: new RegExp(identifier) resolves a const-declared identifier whose pattern is NOT table-shaped', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: `
const headingPattern = '## Phase';
const headingRe = new RegExp(headingPattern);
`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('valid: new RegExp(identifier) does NOT resolve a function-parameter identifier (documented boundary)', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: String.raw`
function buildRowRegex(tablePattern) {
return new RegExp(tablePattern);
}
`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('valid: annotated new RegExp(...) table-regex with allow-adhoc-markdown is NOT flagged', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: String.raw`const rowRe = new RegExp('\\|[^|]*\\|'); // allow-adhoc-markdown: protocol-marker probe, not a table scan`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('valid: new RegExp(...) table-regex in a non-src/*.cts file is not flagged', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: String.raw`const rowRe = new RegExp('\\|[^|]*\\|');`,
filename: 'scripts/helper.cjs',
},
],
invalid: [],
});
});
// ── ADHOC-REPLACE-MUTATION: .replace() on a roadmap/state receiver (#2143 Phase 4) ─
test('invalid: roadmapContent.replace(<inline table-fingerprint literal>, ...) trips both the CallExpression and Literal detectors', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [],
invalid: [
{
code: String.raw`roadmapContent.replace(/\|[^|]*\|/, 'x');`,
filename: 'src/some-module.cts',
// CallExpression is the outer/enter-first node; its Literal argument
// (visited next, on descent) is a second, independent finding.
errors: [{ messageId: 'adhocReplaceMutation' }, { messageId: 'tableRegex' }],
},
],
});
});
test('invalid: stateContent.replace(<variable holding a table-fingerprint regex>, ...) resolves the variable via scope', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [],
invalid: [
{
code: String.raw`
const rowRe = /\|[^|]*\|/;
stateContent.replace(rowRe, 'x');
`,
filename: 'src/some-module.cts',
errors: [{ messageId: 'tableRegex' }, { messageId: 'adhocReplaceMutation' }],
},
],
});
});
test('valid: withSection(...) call is NOT a .replace() and is never flagged', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: `
const { withSection } = require('./markdown-sectionizer');
const result = withSection(content, 'x', (body) => body);
`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('valid: foo.replace(/x/, "y") — neither the receiver name nor the pattern match, not flagged', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: `foo.replace(/x/, 'y');`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('valid: state.replace(/x/, "y") — matching receiver name but non-matching pattern, not flagged', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: `state.replace(/x/, 'y');`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('valid: allow-adhoc-markdown suppresses an inline ad-hoc .replace() mutation', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: String.raw`roadmapContent.replace(/\|[^|]*\|/, 'x'); // allow-adhoc-markdown: pre-seam write path`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('valid: .replace() ad-hoc mutation in a non-src/*.cts file is not flagged', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: String.raw`roadmapContent.replace(/\|[^|]*\|/, 'x');`,
filename: 'scripts/helper.cjs',
},
],
invalid: [],
});
});
// ── TABLE-REGEX widening: [^|\n] and escaped-pipe-plus-others classes (#2880) ──
test('invalid: content.replace(<inline [^|\\n] cell-class regex>) — the exact shape that evaded the rule before #2880', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [],
invalid: [
{
// /\|[^|\n]*\|/ — pipe-excluding cell class ALSO excludes newline; this
// is the src/state-document.cts shape that the sole-member-class check
// missed prior to the #2880 widening.
code: String.raw`content.replace(/\|[^|\n]*\|/, 'x');`,
filename: 'src/state-document.cts',
// CallExpression is the outer/enter-first node (adhocReplaceMutation);
// its Literal argument (visited next, on descent) is the second,
// independent tableRegex finding — same ordering as the established
// roadmapContent.replace(...) case above.
errors: [{ messageId: 'adhocReplaceMutation' }, { messageId: 'tableRegex' }],
},
],
});
});
test('invalid: factory function returning new RegExp(<template literal with [^|\\n] cell class>)', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [],
invalid: [
{
code: 'function buildRowPattern() {\n return new RegExp(`\\\\|[^|\\\\n]*\\\\|`, \'im\');\n}',
filename: 'src/state-document.cts',
errors: [{ messageId: 'tableRegex' }],
},
],
});
});
test('invalid: cell class with the pipe escaped alongside another excluded member [^\\|\\n]', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [],
invalid: [
{
code: String.raw`const cellRe = /\|[^\|\n]*\|/;`,
filename: 'src/state-document.cts',
errors: [{ messageId: 'tableRegex' }],
},
],
});
});
test('valid: negated class with NO pipe at all is not a cell scan (e.g. /^[^\\n]*$/)', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: String.raw`content.replace(/^[^\n]*$/, 'x');`,
filename: 'src/state-document.cts',
},
],
invalid: [],
});
});
test('invalid: body.replace(...) — non-matching receiver name (bounded withSection callback) suppresses ONLY adhocReplaceMutation; the regex literal itself is still an independent tableRegex finding', () => {
// The ADHOC-REPLACE-MUTATION check is scoped to receivers matching
// /roadmap|state|reqContent|content/i — "body" (the withSection callback
// parameter name) does not match, so no adhocReplaceMutation fires here.
// But the standalone Literal visitor inspects EVERY regex literal in the
// file regardless of call-site context, so the pipe-excluding-class regex
// is still caught as a bare tableRegex finding either way.
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [],
invalid: [
{
code: String.raw`body.replace(/\|[^|\n]*\|/, 'x');`,
filename: 'src/state-document.cts',
errors: [{ messageId: 'tableRegex' }],
},
],
});
});
test('valid: allow-adhoc-markdown suppresses the widened [^|\\n] shape', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: String.raw`content.replace(/\|[^|\n]*\|/, 'x'); // allow-adhoc-markdown: reason`,
filename: 'src/state-document.cts',
},
],
invalid: [],
});
});
// ── TABLE-REGEX narrowing: negated class excluding pipe + something ELSE
// is a different (non-table) idiom, not flagged (#2880 FIX 4) ───────────
test('valid: [^\\s|] (pipe excluded alongside \\s, not a pure line-terminator class) is NOT flagged', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: String.raw`const re = /[^\s|]+\|cmd/;`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('valid: [^"|] (pipe excluded alongside a quote) is NOT flagged', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code: String.raw`const re = /\|[^"|]*\|/;`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('invalid: [^|\\r\\n] (pipe plus only line-terminator escapes) IS flagged', () => {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [],
invalid: [
{
code: String.raw`const rowRe = /\|[^|\r\n]*\|/;`,
filename: 'src/some-module.cts',
errors: [{ messageId: 'tableRegex' }],
},
],
});
});
test('performance: a 256000-char adversarial regex-literal source does not hang the rule (ReDoS regression)', () => {
// The previous regex-based fingerprint, /\[\^[^\]]*\\?\|[^\]]*\]/, was
// quadratic on failure — an unclosed negated class of this size took
// ~23s. The single-pass scanner must stay linear. The adversarial text is
// embedded directly inside a single string-literal argument to
// `new RegExp(...)` (not built via `+` at the source-code level under
// test) so `getNewRegExpSource` actually resolves it and the scanner
// walks the full 256000-char unclosed negated class.
const bigPipeRun = '|'.repeat(256000);
const code = `const re = new RegExp('\\\\|[^${bigPipeRun}');`;
// The 256000-char input is the regression guard for the O(n^2) scan fixed
// in #2880: the pre-fix regex took ~23s on this input. There is deliberately
// no elapsed-time assertion (banned by local/no-elapsed-assertion and flaky
// by nature) — if the quadratic path is ever reintroduced this test stops
// completing, which surfaces as a suite timeout rather than a silent pass.
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [
{
code,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
// ── property test: negated-pipe-class scanner (hasQualifyingNegatedPipeClass) ──
test('property: single-pass negated-class scanner verdict matches an independent reference implementation', () => {
// hasQualifyingNegatedPipeClass is a closure private to the rule's
// `create(context)` — it cannot be called directly, so it is exercised
// through the public surface: `new RegExp(<string literal>)` feeds
// `arg.value` through UNCHANGED as the "effective regex source" (see
// getNewRegExpSource), so any generated string, however malformed as a
// real regex, reaches the scanner byte-for-byte via JSON.stringify(...).
// A guaranteed literal `\|` is prepended so isTableRegexSource's OTHER
// gate (`src.includes('\\|')`) is always satisfied — the property is then
// solely a probe of the negated-class scanner's own verdict, matching the
// instruction to test the scanner in isolation.
//
// Reference implementation (independent tokenizer, NOT a copy of the
// scanner under test): tokenize `src` once into {esc, text} units,
// tracking escapes; then walk the tokens with a MONOTONIC cursor: on
// finding a `[` char-token immediately followed by a `^` char-token,
// consume forward to the first unescaped `]` (or to the end if there is
// none) as a single committed unit, decide qualification for that unit,
// and resume scanning strictly AFTER whatever was consumed — a class
// candidate found INSIDE an already-consumed (opened) class body is
// never separately reconsidered. Qualifies iff the collected body
// contains a pipe (bare `|` or escaped `\|`) AND every other member is
// one of the escapes `\n`, `\r`, `\t`.
function referenceHasQualifyingNegatedPipeClass(src) {
const tokens = [];
let i = 0;
while (i < src.length) {
if (src[i] === '\\') {
const next = i + 1 < src.length ? src[i + 1] : '';
tokens.push({ esc: true, text: next });
i += 2;
}
else {
tokens.push({ esc: false, text: src[i] });
i += 1;
}
}
let t = 0;
while (t < tokens.length) {
const opensClass = !tokens[t].esc && tokens[t].text === '['
&& t + 1 < tokens.length && !tokens[t + 1].esc && tokens[t + 1].text === '^';
if (!opensClass) {
t += 1;
continue;
}
let u = t + 2;
const members = [];
let closed = false;
while (u < tokens.length) {
const tok = tokens[u];
if (!tok.esc && tok.text === ']') {
closed = true;
u += 1;
break;
}
members.push(tok);
u += 1;
}
if (closed) {
let hasPipe = false;
let isPure = true;
for (const m of members) {
if (!m.esc && m.text === '|') {
hasPipe = true;
continue;
}
if (m.esc && m.text === '|') {
hasPipe = true;
continue;
}
if (m.esc && (m.text === 'n' || m.text === 'r' || m.text === 't')) continue;
isPure = false;
}
if (hasPipe && isPure) return true;
}
// Monotonic advance: whether this candidate qualified, failed, or
// ran off the end unclosed, never re-enter the bytes just consumed.
t = closed ? u : tokens.length;
}
return false;
}
// Composed of random characters PLUS randomly inserted `[^...]` classes
// with and without pipes (some closed, some not; some qualifying, some
// not) so both the "flagged" and "not flagged" verdicts are well
// exercised — a purely uniform character soup almost never assembles a
// well-formed `[^...|...]` class by chance.
const pipeMemberArb = fc.constantFrom('|', '\\|');
const pureFillerArb = fc.constantFrom('\\n', '\\r', '\\t');
const impureFillerArb = fc.constantFrom('a', 'Z', '1', '\\s', '\\d', '\\w', '\\\\', '-', ' ', '\\]', '\\[');
const classMemberArb = fc.oneof(pipeMemberArb, pureFillerArb, impureFillerArb);
const classBodyArb = fc.array(classMemberArb, { minLength: 1, maxLength: 3 }).map((members) => members.join(''));
const classChunkArb = fc
.record({ body: classBodyArb, closed: fc.boolean() })
.map(({ body, closed }) => '[^' + body + (closed ? ']' : ''));
const noiseCharArb = fc.constantFrom('[', ']', '^', 'x', 'y', '0', '9', ' ', '.', '-', '(', ')');
const escapeChunkArb = fc
.tuple(fc.constant('\\'), fc.constantFrom('n', 'r', 't', '|', 's', 'd', '\\', '[', ']', '^', 'a'))
.map(([bs, c]) => bs + c);
const chunkArb = fc.oneof(
{ weight: 5, arbitrary: classChunkArb },
{ weight: 2, arbitrary: escapeChunkArb },
{ weight: 2, arbitrary: noiseCharArb },
);
const srcArb = fc.array(chunkArb, { minLength: 0, maxLength: 5 }).map((chunks) => chunks.join(''));
fc.assert(
fc.property(srcArb, (fuzzed) => {
const src = '\\|' + fuzzed;
const expected = referenceHasQualifyingNegatedPipeClass(src);
const code = `const re = new RegExp(${JSON.stringify(src)});`;
if (expected) {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [],
invalid: [
{
code,
filename: 'src/some-module.cts',
errors: [{ messageId: 'tableRegex' }],
},
],
});
}
else {
ruleTester.run('no-adhoc-markdown-parsing', noAdhocMarkdownParsing, {
valid: [{ code, filename: 'src/some-module.cts' }],
invalid: [],
});
}
}),
{ numRuns: 200, seed: 2880 },
);
});
});
// ─── no-duplicate-fold-marker ────────────────────────────────────────
describe('no-duplicate-fold-marker rule', () => {
const REPO_ROOT = path.join(__dirname, '..');
/** Build a source string whose line numbers are the array indices + 1. */
const src = (...lines) => lines.join('\n');
const FOLD_A_B1 = '__foldDescribe("folded:a (consolidation epic #1969 B1 #1970)", () => {});';
const FOLD_A_B5 = '__foldDescribe("folded:a (consolidation epic #1969 B5 #1975)", () => {});';
const FOLD_B_B1 = '__foldDescribe("folded:b (consolidation epic #1969 B1 #1970)", () => {});';
test('rule module exports a create function', () => {
assert.strictEqual(typeof noDuplicateFoldMarker.create, 'function');
});
// ── Row 1: the #3271 regression, asserted against the real tree ────────────
//
// The unit cases below prove the rule can fire. THIS proves the tree it
// guards is actually clean — it is the assertion that was red before the 25
// duplicated regions were deleted (18 in install.test.cjs, 5 in
// install-minimal-hooks.test.cjs, 2 in install-write-confinement.test.cjs).
//
// Driven through the real ESLint API over the production glob rather than a
// hand-rolled scan of file text: a readFileSync + .match() scan of a .cjs
// path is exactly the shape `local/no-source-grep` bans in tests/**.
test('regression #3271: the real tests/ tree has no duplicate fold markers', async () => {
const eslint = new ESLint({
cwd: REPO_ROOT,
overrideConfigFile: true,
overrideConfig: {
files: ['tests/**/*.cjs'],
plugins: { local: { rules: { 'no-duplicate-fold-marker': noDuplicateFoldMarker } } },
languageOptions: { ecmaVersion: 2022, sourceType: 'commonjs' },
rules: { 'local/no-duplicate-fold-marker': 'error' },
},
});
const results = await eslint.lintFiles(['tests/**/*.cjs']);
// Filter to THIS rule: an ad-hoc config also surfaces "rule not found" for
// inline eslint-disable directives naming rules it does not register.
const violations = results.flatMap((r) =>
r.messages
.filter((m) => m.ruleId === 'local/no-duplicate-fold-marker')
.map((m) => `${path.relative(REPO_ROOT, r.filePath)}:${m.line} ${m.message}`),
);
// Non-vacuous: if the glob silently matched nothing, the empty result below
// would be meaningless.
assert.ok(results.length > 100, `expected the tests/ glob to match many files, got ${results.length}`);
assert.deepStrictEqual(violations, [], `duplicate folded suites found:\n${violations.join('\n')}`);
});
test('the rule is registered at error for tests/**/*.cjs in the real config', async () => {
const eslint = new ESLint({ cwd: REPO_ROOT });
const config = await eslint.calculateConfigForFile(
path.join(REPO_ROOT, 'tests', 'install.test.cjs'),
);
assert.deepStrictEqual(config.rules['local/no-duplicate-fold-marker'], [2]);
});
// ── Occurrence-count boundary: 1 (clean) / 2 (one report) / 3 (two) ────────
test('valid: a single folded marker in a file', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [{ code: src(FOLD_A_B1), filename: 'tests/host.test.cjs' }],
invalid: [],
});
});
test('invalid: the same folded marker twice in one file', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [],
invalid: [
{
code: src(FOLD_A_B1, FOLD_A_B1),
filename: 'tests/host.test.cjs',
errors: [
{ messageId: 'duplicateFoldMarker', data: { marker: 'a', firstLine: '1' }, line: 2 },
],
},
],
});
});
test('invalid: three occurrences report the 2nd and 3rd', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [],
invalid: [
{
code: src(FOLD_A_B1, FOLD_A_B1, FOLD_A_B1),
filename: 'tests/host.test.cjs',
errors: [
{ messageId: 'duplicateFoldMarker', data: { marker: 'a', firstLine: '1' }, line: 2 },
{ messageId: 'duplicateFoldMarker', data: { marker: 'a', firstLine: '1' }, line: 3 },
],
},
],
});
});
test('valid: two distinct folded markers', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [{ code: src(FOLD_A_B1, FOLD_B_B1), filename: 'tests/host.test.cjs' }],
invalid: [],
});
});
// ── Negative space (10-diagnosis.md) ──────────────────────────────────────
// #3271's own reproduction regex (`folded:[a-z0-9-]*`) stops at `.` and
// collides these two genuinely distinct suites, which coexist in
// tests/model-resolver.test.cjs. A guard written to that key would red the
// build on `next` forever.
test('valid: dot-suffixed marker is distinct from its prefix (model-resolver #3271 false positive)', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [
{
code: src(
'__foldDescribe("folded:feat-443-effort-fast-mode.integration (consolidation epic #1969 B8 #1977)", () => {});',
'__foldDescribe("folded:feat-443-effort-fast-mode (consolidation epic #1969 B8 #1977)", () => {});',
),
filename: 'tests/model-resolver.test.cjs',
},
],
invalid: [],
});
});
// tests/review-default-reviewers-workflow.test.cjs reuses the fold alias for
// an ordinary describe block. Those carry no uniqueness obligation.
test('valid: __foldDescribe titles without a folded: prefix are ignored', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [
{
code: src(
"__foldDescribe('#1936: OpenCode reviewer empty-output hardening', () => {});",
"__foldDescribe('#1936: OpenCode reviewer empty-output hardening', () => {});",
),
filename: 'tests/host.test.cjs',
},
],
invalid: [],
});
});
test('valid: a file with no fold markers', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [{ code: src('describe("ordinary", () => {});'), filename: 'tests/host.test.cjs' }],
invalid: [],
});
});
test('valid: plain describe with a folded: title is not the fold convention', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [
{
code: src(
'describe("folded:a (consolidation epic #1969 B1 #1970)", () => {});',
'describe("folded:a (consolidation epic #1969 B1 #1970)", () => {});',
),
filename: 'tests/host.test.cjs',
},
],
invalid: [],
});
});
// Documented non-goal, pinned so the behavior is deliberate rather than
// accidental: the rule keys on the callee identifier being literally
// __foldDescribe. Every one of the 365 fold sites calls it directly.
test('valid: a call through a further alias of the fold alias is not tracked', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [
{
code: src(
'const d = __foldDescribe;',
'd("folded:a (consolidation epic #1969 B1 #1970)", () => {});',
'd("folded:a (consolidation epic #1969 B1 #1970)", () => {});',
),
filename: 'tests/host.test.cjs',
},
],
invalid: [],
});
});
test('valid: a member-expression call named __foldDescribe is not the fold alias', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [
{
code: src(
'helpers.__foldDescribe("folded:a (consolidation epic #1969 B1 #1970)", () => {});',
'helpers.__foldDescribe("folded:a (consolidation epic #1969 B1 #1970)", () => {});',
),
filename: 'tests/host.test.cjs',
},
],
invalid: [],
});
});
// The same marker in two different HOST files is not intra-file duplication.
// RuleTester lints each entry as its own file, so this also proves the
// per-file state is rebuilt rather than shared across files.
test('valid: the same marker in two different files is not an intra-file duplicate', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [
{ code: src(FOLD_A_B1), filename: 'tests/host-one.test.cjs' },
{ code: src(FOLD_A_B1), filename: 'tests/host-two.test.cjs' },
],
invalid: [],
});
});
// ── Ordering / identity ───────────────────────────────────────────────────
test('invalid: interleaved duplicates each report against their own first occurrence', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [],
invalid: [
{
code: src(FOLD_A_B1, FOLD_B_B1, FOLD_A_B1, FOLD_B_B1),
filename: 'tests/host.test.cjs',
errors: [
{ messageId: 'duplicateFoldMarker', data: { marker: 'a', firstLine: '1' }, line: 3 },
{ messageId: 'duplicateFoldMarker', data: { marker: 'b', firstLine: '2' }, line: 4 },
],
},
],
});
});
// The batch label is provenance, not identity — a re-fold under a different
// batch must not evade the guard. This is the exact shape of #3271: #1975
// re-applied #1970's blocks.
test('invalid: a duplicate marker is reported even when the batch label differs', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [],
invalid: [
{
code: src(FOLD_A_B1, FOLD_A_B5),
filename: 'tests/host.test.cjs',
errors: [
{ messageId: 'duplicateFoldMarker', data: { marker: 'a', firstLine: '1' }, line: 2 },
],
},
],
});
});
// ── Title shapes that cannot be resolved statically ───────────────────────
test('invalid: substitution-free template-literal fold titles are resolved', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [],
invalid: [
{
code: src(
'__foldDescribe(`folded:a (consolidation epic #1969 B1 #1970)`, () => {});',
'__foldDescribe(`folded:a (consolidation epic #1969 B1 #1970)`, () => {});',
),
filename: 'tests/host.test.cjs',
errors: [
{ messageId: 'duplicateFoldMarker', data: { marker: 'a', firstLine: '1' }, line: 2 },
],
},
],
});
});
test('valid: non-literal fold titles are skipped without throwing', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [
{
code: src(
'const name = "folded:a";',
'const x = "a";',
'__foldDescribe(name, () => {});',
'__foldDescribe(name, () => {});',
'__foldDescribe(`folded:${x} (epic)`, () => {});',
'__foldDescribe(`folded:${x} (epic)`, () => {});',
'__foldDescribe(42, () => {});',
'__foldDescribe(42, () => {});',
),
filename: 'tests/host.test.cjs',
},
],
invalid: [],
});
});
test('valid: __foldDescribe with no arguments does not throw', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [
{ code: src('__foldDescribe();', '__foldDescribe();'), filename: 'tests/host.test.cjs' },
],
invalid: [],
});
});
test('valid: an empty marker after the folded: prefix is not tracked', () => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [
{
code: src(
'__foldDescribe("folded: (consolidation epic #1969 B1 #1970)", () => {});',
'__foldDescribe("folded: (consolidation epic #1969 B1 #1970)", () => {});',
),
filename: 'tests/host.test.cjs',
},
],
invalid: [],
});
});
// Property: marker identity is the whole whitespace-delimited token.
//
// This is the generative form of the #3271 correctness question. An
// implementation that keyed on the issue's `[a-z0-9-]*` slice would truncate
// at `.` and pass arm 1 while failing arm 2 on any pair like
// (`a.integration`, `a`) — which is exactly the tests/model-resolver.test.cjs
// false positive. The alphabet deliberately includes `.` and `_` so those
// pairs are generated, not hoped for.
//
// `fc` is already imported at the top of this file and used by the
// no-adhoc-markdown-parsing suite; this follows the same
// fc.property-driving-ruleTester shape.
test('property: a marker is identified by its whole token, so distinct markers never collide', () => {
const markerArb = fc
.array(fc.constantFrom('a', 'z', 'q', '0', '9', '-', '.', '_'), { minLength: 1, maxLength: 12 })
.map((chars) => chars.join(''));
const fold = (marker) =>
`__foldDescribe("folded:${marker} (consolidation epic #1969 B1 #1970)", () => {});`;
// Arm 1: the SAME marker twice is always reported exactly once, against
// the first occurrence.
fc.assert(
fc.property(markerArb, (marker) => {
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [],
invalid: [
{
code: src(fold(marker), fold(marker)),
filename: 'tests/host.test.cjs',
errors: [
{ messageId: 'duplicateFoldMarker', data: { marker, firstLine: '1' }, line: 2 },
],
},
],
});
}),
{ numRuns: 150, seed: 3271 },
);
// Arm 2: two DISTINCT markers never collide, however they differ.
fc.assert(
fc.property(markerArb, markerArb, (a, b) => {
fc.pre(a !== b);
ruleTester.run('no-duplicate-fold-marker', noDuplicateFoldMarker, {
valid: [{ code: src(fold(a), fold(b)), filename: 'tests/host.test.cjs' }],
invalid: [],
});
}),
{ numRuns: 150, seed: 3271 },
);
});
});
// ─── require-subprocess-timeout ────────────────────────────────────
describe('require-subprocess-timeout rule', () => {
test('rule module exports a create function', () => {
assert.strictEqual(typeof requireSubprocessTimeout.create, 'function');
});
// ── INVALID cases (must error) ────────────────────────────────────────────
test('invalid: execFileSync("git", args, { cwd }) — object-literal options with no timeout key', () => {
ruleTester.run('require-subprocess-timeout', requireSubprocessTimeout, {
valid: [],
invalid: [
{
code: `
const { execFileSync } = require('node:child_process');
const args = ['status'];
const cwd = '/repo';
execFileSync('git', args, { cwd });
`,
filename: 'src/some-module.cts',
errors: [{ messageId: 'requireSubprocessTimeout' }],
},
],
});
});
test('invalid: execSync("npm ci", { encoding: "utf8" }) — object-literal options with no timeout key', () => {
ruleTester.run('require-subprocess-timeout', requireSubprocessTimeout, {
valid: [],
invalid: [
{
code: `
const { execSync } = require('node:child_process');
execSync('npm ci', { encoding: 'utf8' });
`,
filename: 'src/some-module.cts',
errors: [{ messageId: 'requireSubprocessTimeout' }],
},
],
});
});
test('invalid: spawnSync with a dotted childProcess.spawnSync callee and no timeout', () => {
ruleTester.run('require-subprocess-timeout', requireSubprocessTimeout, {
valid: [],
invalid: [
{
code: `
const childProcess = require('node:child_process');
childProcess.spawnSync('git', ['log'], { cwd: '/repo', encoding: 'utf-8' });
`,
filename: 'src/some-module.cts',
errors: [{ messageId: 'requireSubprocessTimeout' }],
},
],
});
});
test('invalid: execFileSync with NO options argument at all — categorically no timeout', () => {
ruleTester.run('require-subprocess-timeout', requireSubprocessTimeout, {
valid: [],
invalid: [
{
code: `
const { execFileSync } = require('node:child_process');
execFileSync('git', ['status']);
`,
filename: 'src/some-module.cts',
errors: [{ messageId: 'requireSubprocessTimeout' }],
},
],
});
});
// ── VALID cases (must NOT error) ──────────────────────────────────────────
test('valid: execFileSync("git", args, { cwd, timeout: 30000 }) — timeout key present', () => {
ruleTester.run('require-subprocess-timeout', requireSubprocessTimeout, {
valid: [
{
code: `
const { execFileSync } = require('node:child_process');
const args = ['status'];
const cwd = '/repo';
execFileSync('git', args, { cwd, timeout: 30000 });
`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('valid: options as a pre-built identifier — execFileSync("git", args, opts) is not traced', () => {
ruleTester.run('require-subprocess-timeout', requireSubprocessTimeout, {
valid: [
{
code: `
const { execFileSync } = require('node:child_process');
const args = ['status'];
const opts = { cwd: '/repo', timeout: 30000 };
execFileSync('git', args, opts);
`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
test('valid: same unbounded call under a tests/** filename — rule is inert outside src/*.cts', () => {
ruleTester.run('require-subprocess-timeout', requireSubprocessTimeout, {
valid: [
{
code: `
const { execFileSync } = require('node:child_process');
execFileSync('git', ['status'], { cwd: '/repo' });
`,
filename: 'tests/foo.test.cjs',
},
],
invalid: [],
});
});
test('valid: allow-unbounded-subprocess suppression comment on the call line', () => {
ruleTester.run('require-subprocess-timeout', requireSubprocessTimeout, {
valid: [
{
code: `
const { execFileSync } = require('node:child_process');
execFileSync('git', ['status'], { cwd: '/repo' }); // allow-unbounded-subprocess: bounded by caller's own watchdog
`,
filename: 'src/some-module.cts',
},
],
invalid: [],
});
});
});