sicua
Version:
A tool for analyzing project structure and dependencies
154 lines (153 loc) • 7.79 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateCouplingDegree = calculateCouplingDegree;
const analysisUtils_1 = require("../../../utils/common/analysisUtils");
function calculateCouplingDegree(components) {
const coupling = {};
const totalComponents = components.length;
// Build dependency maps for more accurate analysis using unique IDs
const componentMap = new Map();
const componentsByName = new Map();
const internalDependencies = new Map();
const externalDependencies = new Map();
// Index components for quick lookup
components.forEach((component) => {
const componentId = (0, analysisUtils_1.generateComponentId)(component);
componentMap.set(componentId, component);
// Also index by name for import resolution
if (!componentsByName.has(component.name)) {
componentsByName.set(component.name, []);
}
componentsByName.get(component.name).push(component);
internalDependencies.set(componentId, new Set());
externalDependencies.set(componentId, new Set());
});
// Categorize dependencies
components.forEach((component) => {
const componentId = (0, analysisUtils_1.generateComponentId)(component);
const internalDeps = internalDependencies.get(componentId);
const externalDeps = externalDependencies.get(componentId);
component.imports.forEach((importPath) => {
// Check if import is an internal component
const matchingComponents = findMatchingComponents(importPath, components, componentsByName);
if (matchingComponents.length > 0) {
// It's an internal dependency
matchingComponents.forEach((matchingComp) => {
const targetId = (0, analysisUtils_1.generateComponentId)(matchingComp);
if (targetId !== componentId) {
// Avoid self-references
internalDeps.add(targetId);
}
});
}
else {
// It's an external dependency
externalDeps.add(importPath);
}
});
});
components.forEach((component) => {
const componentId = (0, analysisUtils_1.generateComponentId)(component);
let couplingScore = 0;
// Base coupling from imports and usage (normalized)
const directConnections = component.imports.length + component.usedBy.length;
const baseCoupling = directConnections / Math.max(totalComponents - 1, 1);
// Internal coupling (more significant than external)
const internalDeps = internalDependencies.get(componentId);
const internalCoupling = internalDeps.size / Math.max(totalComponents - 1, 1);
// External coupling (less critical for internal coupling degree)
const externalDeps = externalDependencies.get(componentId);
const externalCoupling = Math.min(externalDeps.size / 20, 1); // Cap external influence
// Bidirectional coupling (components that both import and are used by this component)
const bidirectionalConnections = component.imports.filter((imp) => component.usedBy.some((user) => imp.includes(user) || user.includes(imp))).length;
const bidirectionalCoupling = bidirectionalConnections / Math.max(totalComponents - 1, 1);
// Transitive coupling (components connected through intermediaries)
const transitiveCoupling = calculateTransitiveCoupling(component, components, componentMap, componentsByName);
// Function call coupling
let functionCallCoupling = 0;
if (component.functionCalls) {
const totalFunctionCalls = Object.values(component.functionCalls).reduce((sum, calls) => sum + calls.length, 0);
functionCallCoupling = Math.min(totalFunctionCalls / (totalComponents * 5), 1);
}
// Hub component penalty (components with very high usage)
const hubPenalty = component.usedBy.length > totalComponents * 0.3
? (component.usedBy.length / totalComponents) * 0.2
: 0;
// Shared directory coupling boost
const sharedDirectoryBoost = component.directory.includes("shared") ||
component.directory.includes("common") ||
component.directory.includes("utils")
? 0.1
: 0;
// Calculate weighted coupling score
couplingScore =
baseCoupling * 0.3 + // Base connections
internalCoupling * 0.4 + // Internal dependencies (most important)
externalCoupling * 0.1 + // External dependencies
bidirectionalCoupling * 0.15 + // Bidirectional coupling
transitiveCoupling * 0.05 + // Transitive connections
functionCallCoupling * 0.1 + // Function-level coupling
hubPenalty + // Hub component penalty
sharedDirectoryBoost; // Shared component boost
// Apply coupling intensity multiplier for highly connected components
if (directConnections > totalComponents * 0.2) {
couplingScore *= 1 + (directConnections / totalComponents) * 0.5;
}
// Normalize to reasonable range and round, using unique component ID as key
coupling[componentId] = Math.round(Math.min(couplingScore, 2) * 100) / 100;
});
return coupling;
}
/**
* Find matching components for an import path
*/
function findMatchingComponents(importPath, allComponents, componentsByName) {
const results = [];
// Strategy 1: Direct name match
const directMatches = componentsByName.get(importPath) || [];
results.push(...directMatches);
if (results.length > 0) {
return results;
}
// Strategy 2: Check by file path patterns
const matchingComponents = allComponents.filter((c) => c.fullPath.includes(importPath) ||
importPath.includes(c.name) ||
c.exports.some((exp) => exp === importPath));
results.push(...matchingComponents);
return results;
}
function calculateTransitiveCoupling(component, allComponents, componentMap, componentsByName) {
const visited = new Set();
const transitiveDeps = new Set();
const componentId = (0, analysisUtils_1.generateComponentId)(component);
function findTransitiveDependencies(compId, depth) {
if (depth > 2 || visited.has(compId))
return; // Limit depth to avoid cycles
visited.add(compId);
const comp = componentMap.get(compId);
if (!comp)
return;
comp.imports.forEach((imp) => {
// Find matching components for this import
const matchingComponents = findMatchingComponents(imp, allComponents, componentsByName);
matchingComponents.forEach((matchingComp) => {
const matchingCompId = (0, analysisUtils_1.generateComponentId)(matchingComp);
if (matchingCompId !== componentId) {
transitiveDeps.add(matchingCompId);
findTransitiveDependencies(matchingCompId, depth + 1);
}
});
});
}
// Start from component's direct imports
component.imports.forEach((imp) => {
const matchingComponents = findMatchingComponents(imp, allComponents, componentsByName);
matchingComponents.forEach((matchingComp) => {
const matchingCompId = (0, analysisUtils_1.generateComponentId)(matchingComp);
if (matchingCompId !== componentId) {
findTransitiveDependencies(matchingCompId, 0);
}
});
});
return Math.min(transitiveDeps.size / Math.max(allComponents.length - 1, 1), 0.5);
}