UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

203 lines (202 loc) 9.54 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TypeSimilarityUtils = void 0; const typescript_1 = __importDefault(require("typescript")); /** * Utilities for comparing and measuring similarity between types */ class TypeSimilarityUtils { /** * Calculate similarity between two types (0-1) * Optimized to focus on property names which is most important */ static calculateTypeSimilarity(a, b) { // Handle different kinds of types differently if (a.signature.kind !== b.signature.kind) { return 0; // Different kinds of types are not similar } // For interfaces and type literals, compare properties and methods if (typescript_1.default.isInterfaceDeclaration(a.node) || typescript_1.default.isTypeAliasDeclaration(a.node)) { // Compare properties const aProps = Array.from(a.signature.properties.keys()); const bProps = Array.from(b.signature.properties.keys()); const commonProps = aProps.filter((prop) => bProps.includes(prop)); const allProps = [...new Set([...aProps, ...bProps])]; // Calculate Jaccard similarity for properties const propSimilarity = allProps.length > 0 ? commonProps.length / allProps.length : 0; // For speed, only consider property names as they're most important return propSimilarity; } // For enums, compare members if (typescript_1.default.isEnumDeclaration(a.node) && typescript_1.default.isEnumDeclaration(b.node)) { const aMembers = a.node.members.map((m) => m.name.getText()); const bMembers = b.node.members.map((m) => m.name.getText()); const commonMembers = aMembers.filter((mem) => bMembers.includes(mem)); const allMembers = [...new Set([...aMembers, ...bMembers])]; return allMembers.length > 0 ? commonMembers.length / allMembers.length : 0; } // For similar type names, give a small similarity score if (a.name.length > 3 && b.name.length > 3) { // Use substring matching for speed rather than Levenshtein if (a.name.includes(b.name) || b.name.includes(a.name)) { return 0.3; // Similar names could indicate related types } } return 0; } /** * Calculates detailed property similarity between two types */ static calculateDetailedSimilarity(a, b) { // Calculate property similarity const aProps = Array.from(a.signature.properties.keys()); const bProps = Array.from(b.signature.properties.keys()); const commonProps = aProps.filter((prop) => bProps.includes(prop)); const allProps = [...new Set([...aProps, ...bProps])]; const propSimilarity = allProps.length > 0 ? commonProps.length / allProps.length : 0; // Calculate method similarity const aMethods = Array.from(a.signature.methods.keys()); const bMethods = Array.from(b.signature.methods.keys()); const commonMethods = aMethods.filter((method) => bMethods.includes(method)); const allMethods = [...new Set([...aMethods, ...bMethods])]; const methodSimilarity = allMethods.length > 0 ? commonMethods.length / allMethods.length : 0; // Calculate extends similarity const aExtends = a.signature.extends; const bExtends = b.signature.extends; const commonExtends = aExtends.filter((ext) => bExtends.includes(ext)); const allExtends = [...new Set([...aExtends, ...bExtends])]; const extendsSimilarity = allExtends.length > 0 ? commonExtends.length / allExtends.length : 0; // Calculate overall similarity with weighted factors const overallSimilarity = propSimilarity * 0.6 + // Properties are most important methodSimilarity * 0.3 + // Methods somewhat important extendsSimilarity * 0.1; // Extends relationships least important return { propertySimilarity: propSimilarity, methodSimilarity, extendsSimilarity, overallSimilarity, commonProperties: commonProps, commonMethods, commonExtends, }; } /** * Find similar (but not identical) types */ static findSimilarTypes(candidates, similarityThreshold = 0.5) { const groups = []; const processedTypes = new Set(); // Create an index of types by property names for faster matching const typesByPropertyNames = new Map(); // Index types by their property names for (const typeDef of candidates) { if (processedTypes.has(`${typeDef.filePath}:${typeDef.name}`)) continue; const propNames = Array.from(typeDef.signature.properties.keys()) .sort() .join(","); if (propNames.length > 0) { if (!typesByPropertyNames.has(propNames)) { typesByPropertyNames.set(propNames, []); } typesByPropertyNames.get(propNames).push(typeDef); } } // Group types with exactly the same property names for (const [propNames, types] of typesByPropertyNames.entries()) { if (types.length >= 2) { groups.push(types); types.forEach((t) => processedTypes.add(`${t.filePath}:${t.name}`)); } } // For remaining types, use similarity-based grouping for (const typeDef of candidates) { const typeId = `${typeDef.filePath}:${typeDef.name}`; if (processedTypes.has(typeId)) continue; const similarTypes = [typeDef]; processedTypes.add(typeId); // Compare with a limited number of other types for efficiency const otherTypes = candidates .filter((t) => !processedTypes.has(`${t.filePath}:${t.name}`)) .slice(0, 50); // Limit comparisons to 50 other types for (const otherDef of otherTypes) { const otherId = `${otherDef.filePath}:${otherDef.name}`; if (typeId === otherId || processedTypes.has(otherId)) continue; // Skip if they're different kinds (interface vs type alias vs enum) if (typeDef.signature.kind !== otherDef.signature.kind) continue; // Calculate similarity score using property names as a shortcut const similarity = this.calculateTypeSimilarity(typeDef, otherDef); if (similarity > similarityThreshold) { // At least similar above threshold similarTypes.push(otherDef); processedTypes.add(otherId); } } if (similarTypes.length > 1) { groups.push(similarTypes); } } return groups; } /** * Find types that are structurally identical */ static findIdenticalTypes(types) { const signatureGroups = new Map(); // Group types by their structural signature for (const typeDef of types) { const signature = typeDef.signature.signature; if (!signatureGroups.has(signature)) { signatureGroups.set(signature, []); } signatureGroups.get(signature).push(typeDef); } // Return only groups with multiple types return Array.from(signatureGroups.values()).filter((group) => group.length > 1); } /** * Group similar types with detailed similarity information */ static groupSimilarTypes(types, similarityThreshold = 0.5) { const similarGroups = []; const typeGroups = this.findSimilarTypes(types, similarityThreshold); for (const group of typeGroups) { if (group.length < 2) continue; // Calculate combined similarity for the group let totalSimilarity = 0; let comparisons = 0; // Find properties common to all types in group const allProperties = group.map((t) => Array.from(t.signature.properties.keys())); const commonProperties = allProperties.length > 0 ? allProperties.reduce((common, props) => common.filter((prop) => props.includes(prop))) : []; // Calculate average similarity across all pairs in the group for (let i = 0; i < group.length; i++) { for (let j = i + 1; j < group.length; j++) { const similarity = this.calculateTypeSimilarity(group[i], group[j]); totalSimilarity += similarity; comparisons++; } } const avgSimilarity = comparisons > 0 ? totalSimilarity / comparisons : 0; similarGroups.push({ types: group, similarity: avgSimilarity, commonProperties, }); } // Sort by similarity (descending) return similarGroups.sort((a, b) => b.similarity - a.similarity); } } exports.TypeSimilarityUtils = TypeSimilarityUtils;