UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

136 lines (135 loc) 5.03 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.memoizedLevenshteinDistance = void 0; exports.isValidComponentForComparison = isValidComponentForComparison; exports.shouldCompareComponents = shouldCompareComponents; exports.getComponentType = getComponentType; const deduplication_types_1 = require("../types/deduplication.types"); const lodash_1 = require("lodash"); const fast_levenshtein_1 = require("fast-levenshtein"); /** * Names of components that should be excluded from deduplication analysis */ const EXCLUDED_COMPONENTS = new Set(["App", "Root", "Main", "Layout", "index"]); /** * Memoized Levenshtein distance calculation for performance */ exports.memoizedLevenshteinDistance = (0, lodash_1.memoize)((str1, str2) => (0, fast_levenshtein_1.get)(str1, str2), (str1, str2) => `${str1}:${str2}`); /** * Determines if a component is valid for deduplication analysis * @param component The component to check * @returns boolean indicating if the component is valid for comparison */ function isValidComponentForComparison(component) { const { name, fullPath } = component; const filePath = fullPath.toLowerCase(); const checks = [ () => !filePath.endsWith(".d.ts"), () => !filePath.includes(".test.") && !filePath.includes(".spec."), () => !name.startsWith("use"), () => !EXCLUDED_COMPONENTS.has(name), () => !isTypeFile(name), () => !isUtilityFile(name), () => !isContextFile(name), () => !isHOC(name), () => isPascalCase(name), ]; return checks.every((check) => check()); } /** * Checks if two components should be compared for similarity * @param comp1 First component * @param comp2 Second component * @param nameDistanceThreshold Threshold for name similarity (0.0-1.0) * @returns boolean indicating if the components should be compared */ function shouldCompareComponents(comp1, comp2, nameDistanceThreshold = 0.7) { // Don't compare if either component is invalid if (!isValidComponentForComparison(comp1) || !isValidComponentForComparison(comp2)) { return false; } // Don't compare components from the same file (they're likely different by design) if (comp1.fullPath === comp2.fullPath) { return false; } // Don't compare components from very different directories const type1 = getComponentType(comp1.fullPath); const type2 = getComponentType(comp2.fullPath); if (type1 !== type2) return false; // Don't compare components with very different names const nameDistance = (0, exports.memoizedLevenshteinDistance)(comp1.name, comp2.name); const maxLength = Math.max(comp1.name.length, comp2.name.length); // If names are too different, skip comparison if (nameDistance > maxLength * nameDistanceThreshold) return false; return true; } /** * Gets the type of a component based on its file path * @param path The component file path * @returns The component type */ function getComponentType(path) { const lowerPath = path.toLowerCase(); if (lowerPath.includes("/pages/")) return deduplication_types_1.ComponentType.Page; if (lowerPath.includes("/components/")) return deduplication_types_1.ComponentType.Component; if (lowerPath.includes("/layouts/")) return deduplication_types_1.ComponentType.Layout; if (lowerPath.includes("/features/")) return deduplication_types_1.ComponentType.Feature; return deduplication_types_1.ComponentType.Other; } /** * Checks if a component is likely a TypeScript type definition * @param name Component name * @returns boolean indicating if it's a type file */ function isTypeFile(name) { return (name.endsWith("Type") || name.endsWith("Types") || name.includes("Interface") || name.includes("Enum")); } /** * Checks if a component is likely a utility file * @param name Component name * @returns boolean indicating if it's a utility file */ function isUtilityFile(name) { return (name.endsWith("Utils") || name.endsWith("Helper") || name.endsWith("Helpers") || name.endsWith("Util")); } /** * Checks if a component is likely a React context or provider * @param name Component name * @returns boolean indicating if it's a context file */ function isContextFile(name) { return name.endsWith("Context") || name.endsWith("Provider"); } /** * Checks if a component is likely a higher-order component * @param name Component name * @returns boolean indicating if it's a HOC */ function isHOC(name) { return (name.startsWith("with") && name.length > 4 && name[4] === name[4].toUpperCase()); } /** * Checks if a name follows PascalCase convention * @param name The name to check * @returns boolean indicating if it follows PascalCase */ function isPascalCase(name) { return (name[0] === name[0].toUpperCase() && !name.includes("_") && !name.includes("-")); }