@rayburst/sharity
Version:
Analyze shared package usage across monorepos - calculate symbol sharing percentages, track exclusive imports, and identify unused exports
143 lines • 5.07 kB
JavaScript
;
/**
* Symbol tracker - tracks exports and their usage across consumers
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SymbolTracker = void 0;
const typescript_parser_1 = require("../utils/typescript-parser");
const workspace_scanner_1 = require("../utils/workspace-scanner");
/**
* Symbol registry to track all exports and imports
*/
class SymbolTracker {
constructor() {
this.exportedSymbols = new Map();
this.symbolUsages = new Map();
}
/**
* Scan target package and build export registry
*/
async scanPackageExports(packagePath, packageName, includeTests = false) {
const files = await (0, workspace_scanner_1.findSourceFiles)(packagePath, [], includeTests);
for (const file of files) {
try {
const analysis = (0, typescript_parser_1.parseFile)(file);
for (const exportedSymbol of analysis.exports) {
this.exportedSymbols.set(exportedSymbol.name, exportedSymbol);
// Initialize usage tracking
if (!this.symbolUsages.has(exportedSymbol.name)) {
this.symbolUsages.set(exportedSymbol.name, []);
}
}
}
catch (error) {
console.warn(`Warning: Failed to parse ${file}:`, error);
}
}
}
/**
* Scan consumer package for imports from target
*/
async scanConsumerImports(consumerPath, consumerName, targetPackageName, includeTests = false) {
const files = await (0, workspace_scanner_1.findSourceFiles)(consumerPath, [], includeTests);
const importCounts = new Map();
for (const file of files) {
try {
const analysis = (0, typescript_parser_1.parseFile)(file);
// Filter imports from target package
const targetImports = analysis.imports.filter((imp) => this.isImportFromTarget(imp.source, targetPackageName));
for (const imp of targetImports) {
for (const symbolName of imp.specifiers) {
// Track this usage
if (this.exportedSymbols.has(symbolName)) {
importCounts.set(symbolName, (importCounts.get(symbolName) || 0) + 1);
}
}
}
}
catch (error) {
console.warn(`Warning: Failed to parse ${file}:`, error);
}
}
// Add usages to registry
for (const [symbolName, fileCount] of importCounts) {
const usages = this.symbolUsages.get(symbolName) || [];
usages.push({
symbolName,
consumerName,
consumerPath,
importPath: targetPackageName,
fileCount,
});
this.symbolUsages.set(symbolName, usages);
}
}
/**
* Check if import is from the target package
*/
isImportFromTarget(importSource, targetPackageName) {
// Direct import: import from "@exaring/ui"
if (importSource === targetPackageName) {
return true;
}
// Subpath import: import from "@exaring/ui/components/Button"
if (importSource.startsWith(`${targetPackageName}/`)) {
return true;
}
return false;
}
/**
* Analyze all symbols and generate analysis results
*/
analyzeSymbols() {
const analyses = [];
for (const [symbolName, symbol] of this.exportedSymbols) {
const usages = this.symbolUsages.get(symbolName) || [];
// Get unique consumers
const uniqueConsumers = new Set(usages.map((u) => u.consumerName));
const consumerCount = uniqueConsumers.size;
analyses.push({
symbol,
usages,
consumerCount,
isShared: consumerCount >= 2,
isExclusive: consumerCount === 1,
isUnused: consumerCount === 0,
});
}
return analyses;
}
/**
* Get all exported symbols
*/
getExportedSymbols() {
return Array.from(this.exportedSymbols.values());
}
/**
* Get usage for a specific symbol
*/
getSymbolUsage(symbolName) {
return this.symbolUsages.get(symbolName) || [];
}
/**
* Get all consumers that import from the target package
*/
getAllConsumers() {
const consumers = new Set();
for (const usages of this.symbolUsages.values()) {
for (const usage of usages) {
consumers.add(usage.consumerName);
}
}
return Array.from(consumers);
}
/**
* Clear all tracked data
*/
clear() {
this.exportedSymbols.clear();
this.symbolUsages.clear();
}
}
exports.SymbolTracker = SymbolTracker;
//# sourceMappingURL=symbol-tracker.js.map