sicua
Version:
A tool for analyzing project structure and dependencies
133 lines (132 loc) • 5.34 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeduplicationAnalyzer = void 0;
const deduplication_types_1 = require("./types/deduplication.types");
const componentFilter_1 = require("./utils/componentFilter");
const similarity_1 = require("./utils/similarity");
const componentInfoExtractor_1 = require("./extractors/componentInfoExtractor");
const grouping_1 = require("./utils/grouping");
/**
* Analyzer for detecting component duplication
*/
class DeduplicationAnalyzer {
constructor(components, scanResult) {
this.scanResult = scanResult;
this.sourceFiles = (0, componentInfoExtractor_1.createSourceFiles)(components, scanResult);
}
/**
* Analyzes components to find similarities and duplication
*/
async analyzeComponents(components, thresholds = deduplication_types_1.DEFAULT_SIMILARITY_THRESHOLDS) {
try {
const validComponents = components.filter((comp) => this.isValidForDeduplication(comp));
let processedCount = 0;
let comparisonCount = 0;
// Enhance components with detailed info
const enhancedComponents = await Promise.all(validComponents.map(async (component) => {
processedCount++;
await new Promise((resolve) => setTimeout(resolve, 0));
const sourceFile = this.sourceFiles.get(component.fullPath);
if (!sourceFile) {
return component;
}
return (0, componentInfoExtractor_1.enhanceComponentInfo)(component, sourceFile, this.scanResult);
}));
// Find similarities between components
const similarities = [];
const chunkSize = 100;
for (let i = 0; i < enhancedComponents.length; i++) {
for (let j = i + 1; j < enhancedComponents.length; j++) {
if (!(0, componentFilter_1.shouldCompareComponents)(enhancedComponents[i], enhancedComponents[j], thresholds.nameDistanceThreshold)) {
continue;
}
// Skip components from very different contexts
if (!shouldCompareByContext(enhancedComponents[i], enhancedComponents[j])) {
continue;
}
comparisonCount++;
if (comparisonCount % chunkSize === 0) {
await new Promise((resolve) => setTimeout(resolve, 0));
}
const similarity = (0, similarity_1.compareComponents)(enhancedComponents[i], enhancedComponents[j], thresholds);
similarities.push(similarity);
}
}
// Filter for significant matches
const significantMatches = (0, similarity_1.filterSignificantSimilarities)(similarities, thresholds.minSimilarityScore);
// Group similar components
const groupedSimilarities = (0, grouping_1.groupSimilarComponents)(significantMatches);
// Apply consolidation
const consolidatedResults = (0, grouping_1.consolidateSimilarities)(groupedSimilarities);
return consolidatedResults;
}
finally {
this.sourceFiles.clear();
}
}
/**
* Component validation using scan result metadata
*/
isValidForDeduplication(component) {
if (!(0, componentFilter_1.isValidComponentForComparison)(component)) {
return false;
}
const metadata = this.scanResult.fileMetadata.get(component.fullPath);
if (metadata) {
// Skip test files
if (metadata.isTest) {
return false;
}
// Require React patterns
if (!metadata.hasReactImport &&
!metadata.hasJSX &&
metadata.componentCount === 0) {
return false;
}
}
return true;
}
}
exports.DeduplicationAnalyzer = DeduplicationAnalyzer;
/**
* Check if components should be compared based on their context/purpose
*/
function shouldCompareByContext(comp1, comp2) {
// Extract context from file paths
const context1 = extractContext(comp1.fullPath);
const context2 = extractContext(comp2.fullPath);
// Don't compare components from very different contexts
const differentContexts = ["auth", "marketing", "admin", "dashboard"];
if (differentContexts.includes(context1) &&
differentContexts.includes(context2) &&
context1 !== context2) {
return false;
}
return true;
}
/**
* Extract context from file path
*/
function extractContext(filePath) {
const pathParts = filePath.split("/");
// Look for Next.js route groups like (auth), (marketing)
for (const part of pathParts) {
if (part.startsWith("(") && part.endsWith(")")) {
return part.slice(1, -1);
}
}
// Look for common directory patterns
const contextPatterns = [
"auth",
"marketing",
"admin",
"dashboard",
"components",
];
for (const part of pathParts) {
if (contextPatterns.includes(part.toLowerCase())) {
return part.toLowerCase();
}
}
return "general";
}