* fix(#551): lint hand-written bin/lib/*.cjs mislabeled as generated GENERATED_CJS_IGNORES excluded 12 hand-written runtime modules from the ADR-452 ESLint harness. None carry an @generated header and no generator emits them — they are hand-written, not generated. Remove the mislabeled list so they lint like the rest of get-shit-done/bin/**/*.cjs. The genuinely tsc-generated semver-compare.cjs keeps its own separate ADR-457 ignore. Also drop the stale "Type-aware via parserOptions.project=tsconfig.lint.json" comment on the .cjs block: that block sets no parser/project and enables no @typescript-eslint rules; type-aware linting lives in the src/**/*.cts block. Lint stays green (0 errors); 2 pre-existing warnings surface (already warn severity) per the file's warn-first convention, tracked as follow-up cleanup. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#551): guard ESLint coverage of hand-written bin/lib/*.cjs Asserts via ESLint's isPathIgnored API that every get-shit-done/bin/lib/*.cjs without an @generated header or a src/<name>.cts|.ts source is linted (not ignored), and that the genuinely tsc-generated semver-compare.cjs stays ignored (ADR-457). Fails 13/14 against the pre-fix config; passes on the fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -22,22 +22,6 @@ const localPlugin = {
|
||||
},
|
||||
};
|
||||
|
||||
// Generated bin/lib files — never lint
|
||||
const GENERATED_CJS_IGNORES = [
|
||||
'get-shit-done/bin/lib/command-aliases.cjs',
|
||||
'get-shit-done/bin/lib/configuration.cjs',
|
||||
'get-shit-done/bin/lib/decisions.cjs',
|
||||
'get-shit-done/bin/lib/phase-lifecycle.cjs',
|
||||
'get-shit-done/bin/lib/plan-scan.cjs',
|
||||
'get-shit-done/bin/lib/project-root.cjs',
|
||||
'get-shit-done/bin/lib/schema-detect.cjs',
|
||||
'get-shit-done/bin/lib/secrets.cjs',
|
||||
'get-shit-done/bin/lib/state-document.cjs',
|
||||
'get-shit-done/bin/lib/validate.cjs',
|
||||
'get-shit-done/bin/lib/workstream-inventory-builder.cjs',
|
||||
'get-shit-done/bin/lib/workstream-name-policy.cjs',
|
||||
];
|
||||
|
||||
const sdkSrcExists = existsSync(join(__dirname, 'sdk', 'src'));
|
||||
|
||||
export default tseslint.config(
|
||||
@@ -53,7 +37,6 @@ export default tseslint.config(
|
||||
'**/*.generated.cjs',
|
||||
// ADR-457: tsc-generated runtime artifact — lint the src/*.cts source, not the emitted .cjs.
|
||||
'get-shit-done/bin/lib/semver-compare.cjs',
|
||||
...GENERATED_CJS_IGNORES,
|
||||
],
|
||||
},
|
||||
|
||||
@@ -77,7 +60,6 @@ export default tseslint.config(
|
||||
|
||||
// ── get-shit-done/bin/**/*.cjs + scripts/**/*.cjs ───────────────────────────
|
||||
// CommonJS Node files: js.recommended + eslint-plugin-n + local plugin rules
|
||||
// Type-aware via parserOptions.project=tsconfig.lint.json where applicable
|
||||
{
|
||||
files: ['get-shit-done/bin/**/*.cjs', 'scripts/**/*.cjs'],
|
||||
plugins: {
|
||||
|
||||
94
tests/551-eslint-bin-lib-coverage.test.cjs
Normal file
94
tests/551-eslint-bin-lib-coverage.test.cjs
Normal file
@@ -0,0 +1,94 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Regression test for #551 — the ESLint harness (ADR-452) silently excluded 12
|
||||
* hand-written `get-shit-done/bin/lib/*.cjs` modules via a `GENERATED_CJS_IGNORES`
|
||||
* list mislabeled "Generated bin/lib files — never lint". The files are hand-written
|
||||
* (no `@generated` header, no generator emits them), so they belong in the harness.
|
||||
*
|
||||
* Invariant under test (not just today's file list): a `bin/lib/*.cjs` module must
|
||||
* be linted UNLESS it is genuinely generated — i.e. it carries an `@generated`
|
||||
* header or has a `src/<name>.cts|.ts` source it is compiled from (ADR-457). This
|
||||
* catches the next mislabeled file, not only the original 12.
|
||||
*
|
||||
* Behavior is checked through ESLint's own `isPathIgnored` API so the test reflects
|
||||
* real resolved flat-config precedence, not a textual scan of eslint.config.mjs.
|
||||
*/
|
||||
|
||||
const { describe, test, before } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { ESLint } = require('eslint');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const LIB_DIR = path.join(ROOT, 'get-shit-done', 'bin', 'lib');
|
||||
|
||||
// The 12 modules that #551 restored to lint coverage.
|
||||
const HAND_WRITTEN = [
|
||||
'command-aliases',
|
||||
'configuration',
|
||||
'decisions',
|
||||
'phase-lifecycle',
|
||||
'plan-scan',
|
||||
'project-root',
|
||||
'schema-detect',
|
||||
'secrets',
|
||||
'state-document',
|
||||
'validate',
|
||||
'workstream-inventory-builder',
|
||||
'workstream-name-policy',
|
||||
].map((name) => path.join(LIB_DIR, `${name}.cjs`));
|
||||
|
||||
function isGenerated(absPath) {
|
||||
if (!fs.existsSync(absPath)) return false;
|
||||
const head = fs.readFileSync(absPath, 'utf8').slice(0, 500);
|
||||
if (/@generated/.test(head)) return true;
|
||||
const base = path.basename(absPath, '.cjs');
|
||||
return (
|
||||
fs.existsSync(path.join(ROOT, 'src', `${base}.cts`)) ||
|
||||
fs.existsSync(path.join(ROOT, 'src', `${base}.ts`))
|
||||
);
|
||||
}
|
||||
|
||||
let eslint;
|
||||
before(() => {
|
||||
eslint = new ESLint({ cwd: ROOT });
|
||||
});
|
||||
|
||||
describe('#551: ESLint covers hand-written bin/lib/*.cjs', () => {
|
||||
for (const file of HAND_WRITTEN) {
|
||||
test(`lints ${path.basename(file)} (not ignored)`, async () => {
|
||||
assert.equal(
|
||||
await eslint.isPathIgnored(file),
|
||||
false,
|
||||
`${path.relative(ROOT, file)} is hand-written and must be linted, not ignored`,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
test('no hand-written bin/lib/*.cjs is silently ignored', async () => {
|
||||
const offenders = [];
|
||||
for (const entry of fs.readdirSync(LIB_DIR)) {
|
||||
if (!entry.endsWith('.cjs')) continue;
|
||||
const abs = path.join(LIB_DIR, entry);
|
||||
if (isGenerated(abs)) continue; // legitimately excluded from the harness
|
||||
if (await eslint.isPathIgnored(abs)) offenders.push(entry);
|
||||
}
|
||||
assert.deepEqual(
|
||||
offenders,
|
||||
[],
|
||||
`Hand-written modules silently excluded from ESLint: ${offenders.join(', ')}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('genuinely tsc-generated semver-compare.cjs stays ignored (ADR-457)', async () => {
|
||||
// Publish-time artifact compiled from src/semver-compare.cts; must not be linted.
|
||||
const f = path.join(LIB_DIR, 'semver-compare.cjs');
|
||||
assert.equal(
|
||||
await eslint.isPathIgnored(f),
|
||||
true,
|
||||
'semver-compare.cjs is a tsc-generated publish-time artifact and must stay ignored',
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user