* Find exports that are never imported anywhere in the codebase. * These may indicate dead code or forgotten APIs. * * @param scope Optional path prefix to limit analysis
(scope?: string)
| 808 | * @param scope Optional path prefix to limit analysis |
| 809 | */ |
| 810 | findUnusedExports(scope?: string): UnusedExport[] { |
| 811 | const result: UnusedExport[] = []; |
| 812 | |
| 813 | for (const [file, fileExports] of this.exports.entries()) { |
| 814 | // Apply scope filter |
| 815 | if (scope && !file.startsWith(scope)) continue; |
| 816 | |
| 817 | // Skip index/barrel files - they often re-export things |
| 818 | if (file.endsWith('/index.ts') || file.endsWith('/index.js')) continue; |
| 819 | |
| 820 | // Skip test files |
| 821 | if (file.includes('.spec.') || file.includes('.test.')) continue; |
| 822 | |
| 823 | const importedFromThisFile = this.importedSymbols.get(file) || new Set(); |
| 824 | |
| 825 | const unusedExports: string[] = []; |
| 826 | for (const exp of fileExports) { |
| 827 | // Skip default exports (commonly used implicitly) |
| 828 | if (exp.type === 'default') continue; |
| 829 | |
| 830 | // Check if this export is imported anywhere |
| 831 | if (!importedFromThisFile.has(exp.name)) { |
| 832 | unusedExports.push(exp.name); |
| 833 | } |
| 834 | } |
| 835 | |
| 836 | if (unusedExports.length > 0) { |
| 837 | result.push({ file, exports: unusedExports }); |
| 838 | } |
| 839 | } |
| 840 | |
| 841 | // Sort by file path for consistent output |
| 842 | return result.sort((a, b) => a.file.localeCompare(b.file)); |
| 843 | } |
| 844 | |
| 845 | /** |
| 846 | * Get statistics about the internal dependency graph |
no test coverage detected