Skip to content

Commit 957282d

Browse files
ryanbas21claude
andcommitted
fix(dead-export-finder): harden error handling and add ParseError tests
- workspace-detector: catch only GlobError (not all errors) in readPkgDirs - workspace-detector: fail with WorkspaceNotFoundError on malformed root package.json instead of silently returning empty object - workspace-detector: use Effect.try instead of try/catch in Effect.gen - CLI: catch GlobError from scanner.scan, accumulate as warning - tests: add ParseError tests for both export-parser and import-parser 41 tests passing across 7 test files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f710585 commit 957282d

4 files changed

Lines changed: 37 additions & 9 deletions

File tree

packages/dead-export-finder/src/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,18 @@ const command = Command.make(
116116
const parseWarnings: string[] = [];
117117

118118
for (const pkg of allPackages) {
119-
const files = yield* scanner.scan(pkg.root, ignoreGlobs);
119+
const files = yield* scanner.scan(pkg.root, ignoreGlobs).pipe(
120+
Effect.catchTag('GlobError', (e) =>
121+
Effect.gen(function* () {
122+
const msg = `failed to scan files in ${pkg.root}: ${String(e.cause)}`;
123+
parseWarnings.push(msg);
124+
if (verbose) {
125+
yield* Console.log(`Warning: ${msg}`);
126+
}
127+
return [] as readonly string[];
128+
}),
129+
),
130+
);
120131

121132
for (const filePath of files) {
122133
const sourceResult = yield* fs.readFileString(filePath, 'utf-8').pipe(Effect.either);

packages/dead-export-finder/src/lib/export-parser.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { it } from '@effect/vitest';
22
import { expect } from 'vitest';
33
import { Effect } from 'effect';
44
import { ExportParser, ExportParserLive } from './export-parser.js';
5+
import { ParseError } from './errors.js';
56

67
// ─── helpers ──────────────────────────────────────────────────────────────────
78

@@ -112,3 +113,12 @@ it.effect('parses type exports', () =>
112113
expect(names).toContain('Bar');
113114
}),
114115
);
116+
117+
it.effect('returns ParseError for syntactically invalid source', () =>
118+
Effect.gen(function* () {
119+
const error = yield* parse('bad.ts', '<<<invalid>>>').pipe(Effect.flip);
120+
expect(error).toBeInstanceOf(ParseError);
121+
expect(error.filePath).toBe('bad.ts');
122+
expect(error.message).toBeTruthy();
123+
}),
124+
);

packages/dead-export-finder/src/lib/import-parser.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { it } from '@effect/vitest';
22
import { expect } from 'vitest';
33
import { Effect } from 'effect';
44
import { ImportParser, ImportParserLive } from './import-parser.js';
5+
import { ParseError } from './errors.js';
56

67
// ─── helpers ──────────────────────────────────────────────────────────────────
78

@@ -96,3 +97,12 @@ it.effect('parses type-only imports', () =>
9697
expect(symbols[0]?.isDynamic).toBe(false);
9798
}),
9899
);
100+
101+
it.effect('returns ParseError for syntactically invalid source', () =>
102+
Effect.gen(function* () {
103+
const error = yield* parse('bad.ts', '<<<invalid>>>').pipe(Effect.flip);
104+
expect(error).toBeInstanceOf(ParseError);
105+
expect(error.filePath).toBe('bad.ts');
106+
expect(error.message).toBeTruthy();
107+
}),
108+
);

packages/dead-export-finder/src/lib/workspace-detector.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export const WorkspaceDetectorLive = Layer.effect(
128128
resolveWorkspaceGlobs(path, root, globs).pipe(
129129
Effect.flatMap((dirs) => Effect.all(dirs.map((d) => readPackageInfo(fs, path, d)))),
130130
Effect.map((infos) => infos.filter((p): p is PackageInfo => p !== null)),
131-
Effect.catchAll(() => Effect.succeed([] as PackageInfo[])),
131+
Effect.catchTag('GlobError', () => Effect.succeed([] as PackageInfo[])),
132132
);
133133

134134
const detect = (cwd: string): Effect.Effect<WorkspaceResult, WorkspaceNotFoundError> =>
@@ -151,13 +151,10 @@ export const WorkspaceDetectorLive = Layer.effect(
151151

152152
if (hasRootPkg) {
153153
const raw = yield* fs.readFileString(rootPkgPath, 'utf-8').pipe(Effect.orDie);
154-
const rootPkg: Record<string, unknown> = (() => {
155-
try {
156-
return JSON.parse(raw) as Record<string, unknown>;
157-
} catch {
158-
return {};
159-
}
160-
})();
154+
const rootPkg = yield* Effect.try({
155+
try: () => JSON.parse(raw) as Record<string, unknown>,
156+
catch: () => new WorkspaceNotFoundError({ cwd }),
157+
});
161158

162159
// npm / yarn workspaces
163160
const workspaces = rootPkg['workspaces'];

0 commit comments

Comments
 (0)