UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

221 lines (220 loc) 9.15 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.compareComponents = compareComponents; exports.generateDeduplicationData = generateDeduplicationData; exports.filterSignificantSimilarities = filterSignificantSimilarities; const analysisUtils_1 = require("../../../utils/common/analysisUtils"); const deduplication_types_1 = require("../types/deduplication.types"); const propComparison_1 = require("./propComparison"); const structureComparison_1 = require("./structureComparison"); const short_unique_id_1 = __importDefault(require("short-unique-id")); const { randomUUID } = new short_unique_id_1.default(); /** * Compares two components for similarity with balanced filtering */ function compareComponents(comp1, comp2, thresholds = deduplication_types_1.DEFAULT_SIMILARITY_THRESHOLDS) { // Find common props and structures const commonProps = findCommonProps(comp1.props, comp2.props); const commonStructure = (0, structureComparison_1.findCommonStructure)(comp1.jsxStructure, comp2.jsxStructure); // Calculate structure complexity const complexity1 = (0, structureComparison_1.calculateStructureComplexity)(comp1.jsxStructure); const complexity2 = (0, structureComparison_1.calculateStructureComplexity)(comp2.jsxStructure); const complexityRatio = Math.min(complexity1.complexity, complexity2.complexity) / Math.max(complexity1.complexity, complexity2.complexity); // Check minimum complexity requirements if (complexityRatio < thresholds.minComplexityRatio || complexity1.complexity < thresholds.minStructureComplexity || complexity2.complexity < thresholds.minStructureComplexity) { return createLowSimilarityResult(comp1, comp2, commonProps, commonStructure); } // Calculate base similarity scores const propsScore = (0, propComparison_1.calculatePropsSimilarity)(commonProps, [comp1, comp2]); const childComponentScore = (0, structureComparison_1.calculateChildComponentSimilarity)(comp1.jsxStructure, comp2.jsxStructure); const styleScore = (0, structureComparison_1.calculateStyleSimilarity)(comp1.jsxStructure, comp2.jsxStructure); const baseStructureScore = (0, structureComparison_1.calculateStructureSimilarity)(commonStructure, [ comp1.jsxStructure, comp2.jsxStructure, ]); // Apply common pattern penalty const commonPatternPenalty = calculateCommonPatternPenalty(comp1.jsxStructure, comp2.jsxStructure); // Calculate weighted similarity score const structureScore = (baseStructureScore + childComponentScore + styleScore) / 3; const rawSimilarityScore = propsScore * 0.4 + structureScore * 0.6; // Apply penalty for too many common UI patterns const similarityScore = Math.max(0, Math.round((rawSimilarityScore - commonPatternPenalty) * 100) / 100); const deduplicationData = generateDeduplicationData([comp1, comp2], commonProps, commonStructure); return { groupId: randomUUID(), components: [(0, analysisUtils_1.generateComponentId)(comp1), (0, analysisUtils_1.generateComponentId)(comp2)], commonProps, commonJSXStructure: commonStructure, similarityScore, deduplicationData, }; } /** * Calculates penalty for components that share mostly common UI patterns */ function calculateCommonPatternPenalty(struct1, struct2) { if (!struct1 || !struct2) return 0; const elements1 = getAllElementNames(struct1); const elements2 = getAllElementNames(struct2); // Count common UI patterns const commonPatterns1 = elements1.filter((el) => deduplication_types_1.COMMON_UI_PATTERNS.includes(el)).length; const commonPatterns2 = elements2.filter((el) => deduplication_types_1.COMMON_UI_PATTERNS.includes(el)).length; // Calculate ratio of common patterns const ratio1 = elements1.length > 0 ? commonPatterns1 / elements1.length : 0; const ratio2 = elements2.length > 0 ? commonPatterns2 / elements2.length : 0; const avgRatio = (ratio1 + ratio2) / 2; // Apply penalty if more than 70% of elements are common UI patterns if (avgRatio > 0.7) { return (avgRatio - 0.7) * 0.5; // Up to 15% penalty } return 0; } /** * Gets all element names from JSX structure */ function getAllElementNames(structure) { const names = [structure.tagName]; structure.children.forEach((child) => { names.push(...getAllElementNames(child)); }); return names; } /** * Creates a low similarity result */ function createLowSimilarityResult(comp1, comp2, commonProps, commonStructure) { return { groupId: randomUUID(), components: [(0, analysisUtils_1.generateComponentId)(comp1), (0, analysisUtils_1.generateComponentId)(comp2)], commonProps, commonJSXStructure: commonStructure, similarityScore: 0.1, deduplicationData: generateDeduplicationData([comp1, comp2], commonProps, commonStructure), }; } /** * Finds common props between two sets of props */ function findCommonProps(props1, props2) { if (!props1 || !props2) return []; return props1.filter((prop1) => props2.some((prop2) => prop1.name === prop2.name && prop1.type === prop2.type)); } /** * Generates detailed deduplication data for components */ function generateDeduplicationData(components, commonProps, commonStructure) { const componentData = components.map((comp) => ({ name: comp.name, path: comp.fullPath, content: comp.content || "", componentId: (0, analysisUtils_1.generateComponentId)(comp), })); const propSimilarities = commonProps.map((prop) => ({ name: prop.name, type: prop.type, isRequired: prop.required, usedInComponents: components.map((c) => (0, analysisUtils_1.generateComponentId)(c)), })); const propDifferences = components.map((comp) => ({ componentName: comp.name, componentId: (0, analysisUtils_1.generateComponentId)(comp), uniqueProps: (comp.props || []) .filter((prop) => !commonProps.some((cp) => cp.name === prop.name)) .map((prop) => ({ name: prop.name, type: prop.type, isRequired: prop.required, })), })); const jsxSimilarity = { sharedRootElement: commonStructure[0]?.tagName || "", sharedStructure: extractSharedStructure(commonStructure), sharedClassNames: extractSharedClassNames(commonStructure), }; const jsxDifferences = components.map((comp) => ({ componentName: comp.name, componentId: (0, analysisUtils_1.generateComponentId)(comp), uniqueElements: findUniqueElements(comp.jsxStructure, commonStructure), })); return { components: componentData, commonalities: { props: propSimilarities, structure: jsxSimilarity, }, differences: { props: propDifferences, structure: jsxDifferences, }, }; } /** * Extracts the structure of shared elements */ function extractSharedStructure(structure) { const result = []; const process = (node) => { result.push(node.tagName); node.children.forEach(process); }; structure.forEach(process); return result; } /** * Extracts shared class names from JSX structure */ function extractSharedClassNames(structure) { const classNames = []; const process = (node) => { const className = node.props.find((p) => p.name === "className"); if (className) { const classes = className.type .replace(/['"]/g, "") .split(" ") .filter(Boolean); classNames.push(...classes); } node.children.forEach(process); }; structure.forEach(process); return [...new Set(classNames)]; } /** * Finds elements unique to a component's structure */ function findUniqueElements(componentStructure, commonStructure) { const unique = []; const process = (node, commonNode, path = "") => { if (!node) return; if (!commonNode || node.tagName !== commonNode.tagName) { unique.push({ element: node.tagName, location: path, props: node.props.reduce((acc, prop) => ({ ...acc, [prop.name]: prop.type, }), {}), }); } node.children.forEach((child, index) => { process(child, commonNode?.children[index], `${path}${path ? "." : ""}children[${index}]`); }); }; process(componentStructure, commonStructure?.[0]); return unique; } /** * Filters similarities based on a minimum threshold */ function filterSignificantSimilarities(similarities, threshold = deduplication_types_1.DEFAULT_SIMILARITY_THRESHOLDS.minSimilarityScore) { return similarities.filter((s) => s.similarityScore >= threshold); }