Files
callstack__agent-device/scripts/__tests__/fallow-fixture-policy.test.ts
Michał Pierzchała 7f5dbd2e50 chore: drive unused production exports to zero (#1743)
* chore: drive unused production exports to zero

`pnpm check:production-exports` has been failing on main with 21 findings.
Each was investigated rather than blanket-suppressed; they split three ways.

Genuinely dead, deleted:
- `androidDeviceForSerial` (android/adb.ts) had zero references anywhere,
  tests included.
- `streamAndroidLogcatWithAdb` (android/logcat.ts) had no production consumer
  and only a guard-clause test; its `captureAndroidLogcatWithAdb` sibling is
  the published SDK surface. Removed with its options type and test.

Test-only aliases over live siblings, collapsed:
- app-log-resource-store re-exposed four bound store methods; production used
  only `resolvePath`, tests used the other three. The sibling
  screen-recording-resource-store exports just the store, so this now matches:
  one export, all consumers call `appLogResourceStore.x`.
- device-claims re-exported `canonicalLocalDeviceKey` for a single test, while
  production imports it from device-claim-paths directly. Dropped the
  re-export and pointed the test at the canonical module.

Real consumers the analysis cannot see, exempted with the reason:
- The nine remaining `src/cli/commands/*Command` handlers are reached only
  through `dedicatedCliCommandHandlerLoaders`, the dynamic import() table in
  router.ts. `deviceCommand` already carried an inline suppression for exactly
  this; replaced it with one config entry naming the table that enumerates all
  ten, matching the existing daemon route-handler entry.
- `resolveVitestMaxWorkers` (vitest.config.ts), `DEVICE_CLAIM_IN_USE_SAMPLE`
  (bench sample producers) and the capture-kit `createAppLogLiveHandle` facade
  export joined the existing entries that already record their exact shape.
- `**/*.fixtures.ts` is now an ignorePattern: all 15 build doubles for
  co-located tests, several import `vi`, and none is imported by production
  source. Pattern-matching them as test infrastructure also keeps this class
  of finding from recurring.

Gate now reports zero. Unit suite, layering, fallow audit, MCP metadata,
build, bundle-owner and package checks all pass.

* chore: scope the fixture exemption to unused exports

Review feedback on #1743: `ignorePatterns` removes a file from every Fallow
mode and rule, but the false positive here is only production-unused-exports.
Moved *.fixtures.ts to an ignoreExports entry so fixtures stay inside health,
dead-code and cycle analysis. Kept `exports: ["*"]` rather than today's three
symbols because the property is per-file — no fixture module has a production
consumer — so a new fixture symbol should not reopen the finding.

check:production-exports still reports zero, and a full `fallow --summary`
returns identical totals (2 dead-code / 4 dupes / 123 health) with and without
the change, so nothing was newly surfaced or newly hidden.

* test(fallow): prove fixture policy scope
2026-08-12 11:17:33 +02:00

114 lines
3.8 KiB
TypeScript

import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { test } from 'vitest';
import { mkdtempForTest } from '../../src/__tests__/test-utils/index.ts';
import { runCmd } from '../../src/utils/exec.ts';
const REPOSITORY_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const FALLOW_CLI = path.join(REPOSITORY_ROOT, 'node_modules', 'fallow', 'bin', 'fallow');
const FIXTURE_GLOB = '**/*.fixtures.ts';
type IgnoreExportPolicy = {
file: string;
exports: string[];
};
type ResolvedFallowConfig = {
ignorePatterns: string[];
ignoreExports: IgnoreExportPolicy[];
};
type DeadCodeReport = {
summary: { unused_exports: number };
unused_exports: Array<{ path: string; export_name: string }>;
};
type HealthReport = {
findings: Array<{ path: string; name: string }>;
};
function parseJsonEnvelope<T>(stdout: string): T {
const jsonStart = stdout.indexOf('{');
assert.notEqual(jsonStart, -1, `expected JSON output, received: ${stdout}`);
return JSON.parse(stdout.slice(jsonStart)) as T;
}
async function runFallow(root: string, args: string[]) {
return await runCmd(process.execPath, [FALLOW_CLI, ...args], {
cwd: root,
allowFailure: true,
timeoutMs: 30_000,
});
}
test('fixture exports stay exempt while fixture health remains audited', async () => {
const configResult = await runFallow(REPOSITORY_ROOT, ['config', '--format', 'json', '--quiet']);
assert.equal(configResult.exitCode, 0, configResult.stderr);
const resolved = parseJsonEnvelope<ResolvedFallowConfig>(configResult.stdout);
const fixturePolicy = resolved.ignoreExports.find(({ file }) => file === FIXTURE_GLOB);
const root = await mkdtempForTest('fallow-fixture-policy-');
await fs.mkdir(path.join(root, 'src'));
await Promise.all([
fs.writeFile(path.join(root, 'package.json'), '{"type":"module"}\n'),
fs.writeFile(
path.join(root, 'tsconfig.json'),
'{"compilerOptions":{"target":"ES2022","module":"NodeNext","moduleResolution":"NodeNext"},"include":["src/**/*.ts"]}\n',
),
fs.writeFile(
path.join(root, 'src', 'index.ts'),
"import './sample.fixtures.ts';\nexport const entry = true;\n",
),
fs.writeFile(
path.join(root, 'src', 'sample.fixtures.ts'),
'export function unusedFixture(value: boolean): number {\n if (value) return 1;\n return 0;\n}\n',
),
fs.writeFile(
path.join(root, '.fallowrc.json'),
`${JSON.stringify({
entry: ['src/index.ts'],
ignorePatterns: resolved.ignorePatterns,
ignoreExports: fixturePolicy ? [fixturePolicy] : [],
health: { maxCyclomatic: 1, maxCognitive: 1 },
})}\n`,
),
]);
const deadCodeResult = await runFallow(root, [
'dead-code',
'--unused-exports',
'--format',
'json',
'--quiet',
'--no-cache',
]);
assert.equal(deadCodeResult.exitCode, 0, deadCodeResult.stderr);
const deadCode = parseJsonEnvelope<DeadCodeReport>(deadCodeResult.stdout);
assert.equal(deadCode.summary.unused_exports, 0);
assert.deepEqual(deadCode.unused_exports, []);
const healthResult = await runFallow(root, [
'health',
'--complexity',
'--format',
'json',
'--quiet',
'--no-cache',
]);
assert.equal(healthResult.exitCode, 1, 'the planted fixture complexity must fail health');
const health = parseJsonEnvelope<HealthReport>(healthResult.stdout);
assert.ok(
health.findings.some(
({ path: findingPath, name }) =>
findingPath === 'src/sample.fixtures.ts' && name === 'unusedFixture',
),
'Fallow health did not analyze the planted fixture',
);
assert.equal(resolved.ignorePatterns.includes(FIXTURE_GLOB), false);
assert.ok(fixturePolicy, `missing ${FIXTURE_GLOB} unused-export policy`);
assert.deepEqual(fixturePolicy.exports, ['*']);
});