super-productivity/eslint-local-rules/run-specs.js
Johannes Millan 42e6626b76 docs(sync): consolidate sync docs + enforce the contributor model
Collapse the sprawling, partly-stale docs/sync-and-op-log/ tree into a
small authoritative set and make the sync-correctness invariant
partly lint-enforced instead of convention-only.

Docs:
- Delete superseded/duplicate/provably-stale design, plan, and
  background-research docs (quick-reference, the architecture-diagrams
  monolith, the "Hybrid Manifest" docs describing code that does not
  exist, completed long-term plans, LLM-synthesis analyses).
- Salvage load-bearing decision history into the surviving docs before
  deletion: rejected-alternatives rationale -> operation-log-architecture
  ("Why this architecture"); vector-clock pruning incident history ->
  vector-clocks.md; archive-payload optimization -> architecture E.7.
- Add contributor-sync-model.md as the single-invariant entry point
  (one user intent = one op; replayed/remote ops must not re-trigger
  effects), with a decision table mapping to the enforcing linters.
- Repoint external/internal cross-refs; add CONTRIBUTING.md + CLAUDE.md
  pointers; record the migration in a dated docs/plans/ design doc.

Enforcement (new eslint-local-rules):
- no-actions-in-effects (error): effects must inject LOCAL_ACTIONS /
  ALL_ACTIONS, never the raw @ngrx/effects Actions stream.
- no-multi-entity-effect (warn, heuristic): flags a literal returned
  array of >=2 action-creator calls; docstring + valid-case specs pin
  exactly which shapes are and are not detected.
- run-specs.js runner wired into `npm run lint` via test:lint-rules;
  refuses to run under test-framework globals and counts RuleTester.run
  invocations so a spec that asserts nothing fails instead of passing.
- Correct the ALL_ACTIONS JSDoc in local-actions.token.ts to match
  reality (archive-operation-handler uses LOCAL_ACTIONS).

Reviewed via parallel multi-agent review; findings W1/W2/W4 and a
dangling doc anchor addressed.
2026-05-15 16:51:50 +02:00

70 lines
2.6 KiB
JavaScript

/**
* Runs every `*.spec.js` in eslint-local-rules/rules via ESLint's RuleTester.
*
* RuleTester.run() executes synchronously and throws on the first failing
* case ONLY when no test-framework globals (describe/it) are present — with
* them, RuleTester defers cases to the framework and merely requiring the spec
* asserts nothing. This runner therefore:
* (a) refuses to run if such globals are present, and
* (b) counts RuleTester.run() invocations per spec and fails any spec that
* required cleanly but never called run() (e.g. run() commented out, or
* wrapped in a never-invoked function) — so a spec can no longer be a
* silent pass that asserts nothing.
*
* Wired as `npm run test:lint-rules` (the unit suite — Karma over *.spec.ts —
* does not run these .spec.js files). Run with bare `node`, never under
* jest/mocha.
*/
const fs = require('fs');
const path = require('path');
const { RuleTester } = require('eslint');
// (a) A test-framework global means RuleTester would NOT throw synchronously,
// so requiring a spec would assert nothing. Fail loudly instead of lying.
if (typeof globalThis.describe === 'function' || typeof globalThis.it === 'function') {
// eslint-disable-next-line no-console
console.error(
'lint-rule specs: REFUSING TO RUN — a test-framework global (describe/it) ' +
'is present, so RuleTester defers instead of throwing and nothing would ' +
'be asserted. Run with bare `node eslint-local-rules/run-specs.js`.',
);
process.exit(1);
}
// (b) Count run() calls so a spec that asserts nothing fails instead of passing.
let runCalls = 0;
const originalRun = RuleTester.prototype.run;
RuleTester.prototype.run = function countedRun(...args) {
runCalls += 1;
return originalRun.apply(this, args);
};
const rulesDir = path.join(__dirname, 'rules');
const specs = fs.readdirSync(rulesDir).filter((f) => f.endsWith('.spec.js'));
let failed = false;
for (const spec of specs) {
const runsBefore = runCalls;
try {
require(path.join(rulesDir, spec));
if (runCalls === runsBefore) {
failed = true;
// eslint-disable-next-line no-console
console.error(
`FAIL ${spec}\n required cleanly but never called RuleTester.run() — asserted nothing`,
);
}
} catch (err) {
failed = true;
// eslint-disable-next-line no-console
console.error(`FAIL ${spec}\n${(err && err.stack) || err}`);
}
}
// eslint-disable-next-line no-console
console.log(
failed
? '\nlint-rule specs: FAILED'
: `\nlint-rule specs: ${specs.length} file(s) passed (${runCalls} RuleTester.run call(s))`,
);
process.exit(failed ? 1 : 0);