Skip to content

Commit 40c8f67

Browse files
authored
Merge pull request #46 from ryanbas21/refactor/dead-export-finder-pure-functional
refactor(dead-export-finder): pure functional pipes, zero mutation
2 parents 8d88982 + 37753b6 commit 40c8f67

8 files changed

Lines changed: 1249 additions & 731 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Dead Export Finder — Pure Functional Refactor
2+
3+
## Goal
4+
5+
Eliminate all mutation from the `dead-export-finder` package internals. Use pipe syntax with small, well-named composed functions. Leverage Effect's `HashSet`/`HashMap` internally where structural equality matters, keep native `ReadonlyMap`/`ReadonlyArray` at service boundaries.
6+
7+
## Constraints
8+
9+
- Service topology unchanged (WorkspaceDetector, FileScanner, ExportParser, ImportParser, ExportGraph, Reporter)
10+
- All existing tests pass without modification (Map is assignable to ReadonlyMap)
11+
- Effect boundary stays at parse/IO level — pure synchronous functions inside
12+
13+
## Service Interface Changes
14+
15+
- `ExportGraph.analyze`: parameters become `ReadonlyMap` instead of `Map`
16+
- `Reporter.format`: `packageRoots` becomes `ReadonlyMap<string, string>`
17+
- No new services added, none removed
18+
19+
## File-by-File Plan
20+
21+
### export-parser.ts
22+
23+
- Replace `symbols.push()` in switch/case with `Array.flatMap(extractExportsFromNode)` over `program.body`
24+
- Small pure extractors: `extractNamedDeclaration`, `extractDefaultDeclaration`, `extractAllDeclaration`, `extractCjsExports` — each returns `ReadonlyArray<ExportedSymbol>`
25+
- `extractDeclarationNames` returns `ReadonlyArray<string>`
26+
- `lineFromOffset` stays as pure function (already is), replace mutable counter
27+
- `Effect.try` boundary stays for `oxc.parseSync`
28+
29+
### import-parser.ts
30+
31+
- `walkNode(node, filePath, symbols)``collectSymbols(node, filePath): ReadonlyArray<ImportedSymbol>` — pure recursive, returns results
32+
- Children via `Array.flatMap` recurse
33+
- Static imports: `Array.flatMap(extractStaticImports)` over ImportDeclaration nodes
34+
- Concatenate with `collectSymbols` results
35+
- Same `Effect.try` boundary
36+
37+
### export-graph.ts
38+
39+
- `resolveEntryPoints(packages, scannedFiles) → HashSet<string>`
40+
- `buildFileToPackageMap(packages, exportedFilePaths) → ReadonlyMap<string, PackageInfo>`
41+
- `buildConsumedSets(allImports, allExports) → { byRelative: HashSet, byPackage: HashSet, byNamespace: HashSet }`
42+
- `collectImportEdges` + `collectReExportEdges` merged with `HashSet.union`
43+
- `findDeadExports(...)` — pure filter with `Array.filter(isNotConsumed)`
44+
- `analyze` becomes ~15 lines piping through steps 1–4
45+
46+
### reporter.ts
47+
48+
- `Array.groupBy` for package/file grouping
49+
- `formatPackageSection` returns `ReadonlyArray<string>`
50+
- Top-level: header → sections → summary, `Array.join("\n")`
51+
52+
### file-scanner.ts
53+
54+
- `loadGitignorePatterns(root)``Effect<ReadonlyArray<string>>`
55+
- `buildIgnorePatterns(gitignore, custom)` — pure concatenation
56+
- Single `ignore()` construction from full pattern list
57+
58+
### workspace-detector.ts
59+
60+
- Nested if/else → `pipe(detectPnpm, Effect.orElse(detectNpm), Effect.orElse(detectNx), Effect.orElse(detectTurbo), Effect.orElse(detectSingle))`
61+
- Each detector is a small function returning `Effect<WorkspaceResult, WorkspaceNotFoundError>`
62+
63+
### index.ts (CLI)
64+
65+
- `scanWorkspace(workspace, ignoreGlobs, verbose)` → immutable file map + warnings
66+
- `parseAllFiles(filesByPackage, verbose)` → immutable exports/imports maps + warnings
67+
- `analyzeAndReport(...)` → calls graph + reporter
68+
- Command handler becomes ~20 lines
69+
- Warnings concatenated at end with `Array.appendAll`
70+
71+
## Testing
72+
73+
Zero test file changes. All `Map` constructions in tests are assignable to `ReadonlyMap`. All assertions remain valid.

0 commit comments

Comments
 (0)