sicua
Version:
A tool for analyzing project structure and dependencies
136 lines (135 loc) • 5.03 kB
JavaScript
;
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("-"));
}