arela
Version:
AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.
176 lines • 6.09 kB
JavaScript
/**
* Coupling calculator - measures cross-layer/cross-module dependencies
* Lower coupling is better (less tightly coupled)
*/
/**
* Calculate coupling score from imports
*
* Algorithm:
* 1. Count all imports in the codebase
* 2. Identify cross-directory imports (dependencies between different dirs)
* 3. For horizontal architectures, check if imports cross layers
* 4. Score = (crossLayerImports / totalImports) * 100
*
* Score scale:
* 0-20: Excellent (very loosely coupled)
* 21-40: Good (loosely coupled)
* 41-60: Fair (moderately coupled)
* 61-80: Poor (tightly coupled)
* 81-100: Critical (very tightly coupled)
*/
export function calculateCoupling(imports, directories) {
const details = [];
const couplingMap = new Map();
let totalImports = 0;
let crossDirectoryImports = 0;
let crossLayerImports = 0;
// Analyze each import
for (const imp of imports) {
if (!imp.to)
continue;
totalImports++;
// Check if this is a cross-directory import
if (imp.fromDir && imp.toDir && imp.fromDir !== imp.toDir) {
crossDirectoryImports++;
// Check if this crosses a layer boundary
const fromDir = directories.get(imp.fromDir);
const toDir = directories.get(imp.toDir);
// Cross-layer violations happen when:
// 1. Both directories are layer-type directories
// 2. They're at the same level in the hierarchy
// 3. But the import violates expected dependency direction
if (fromDir?.type === "layer" && toDir?.type === "layer") {
const layerOrder = getLayerOrder(fromDir.path, toDir.path);
if (layerOrder && layerOrder.violatesHierarchy) {
crossLayerImports++;
}
}
// Track coupling between directories
const key = `${imp.fromDir} -> ${imp.toDir}`;
couplingMap.set(key, (couplingMap.get(key) || 0) + 1);
}
}
// Build details array
for (const [key, count] of couplingMap) {
const [from, to] = key.split(" -> ");
const fromDir = directories.get(from);
const toDir = directories.get(to);
const isLayerViolation = fromDir?.type === "layer" &&
toDir?.type === "layer" &&
getLayerOrder(from, to)?.violatesHierarchy;
details.push({
from,
to,
count,
isLayerViolation: isLayerViolation || false,
});
}
// Calculate coupling score
// More weight on cross-layer violations
let couplingScore = 0;
if (totalImports > 0) {
const crossDirRatio = crossDirectoryImports / totalImports;
const crossLayerRatio = crossLayerImports / totalImports;
// Weighting: cross-directory imports count for 50% of score
// cross-layer violations count for additional 50%
couplingScore = crossDirRatio * 50 + crossLayerRatio * 50;
couplingScore = Math.round(couplingScore * 100);
}
return {
totalImports,
crossDirectoryImports,
crossLayerImports,
coupling: Math.min(100, couplingScore),
details,
};
}
const LAYER_HIERARCHY = {
pages: 0,
containers: 0,
components: 1,
views: 0,
routes: 0,
controllers: 0,
services: 2,
hooks: 1,
middleware: 2,
models: 2,
schemas: 3,
utils: 3,
helpers: 3,
lib: 3,
constants: 4,
types: 4,
interfaces: 4,
config: 4,
};
function getLayerOrder(fromDir, toDir) {
const fromLayer = Object.entries(LAYER_HIERARCHY).find(([name]) => fromDir.includes(name));
const toLayer = Object.entries(LAYER_HIERARCHY).find(([name]) => toDir.includes(name));
if (!fromLayer || !toLayer) {
return null;
}
const fromOrder = fromLayer[1];
const toOrder = toLayer[1];
// Violation: lower layer importing from higher layer (e.g., services importing from components)
return {
order: Math.abs(fromOrder - toOrder),
violatesHierarchy: fromOrder > toOrder,
};
}
/**
* Extract directory from file path
*/
function extractDirectory(filePath) {
const parts = filePath.split("/").filter(p => p && !p.startsWith("."));
// Return first meaningful directory (skip src, lib, app roots)
if (parts[0] === "src" || parts[0] === "lib" || parts[0] === "app") {
return parts[1] || parts[0];
}
return parts[0] || "root";
}
/**
* Analyze import patterns and detect problematic dependencies
*/
export function detectCouplingIssues(analysis, directories) {
const issues = [];
// High cross-directory imports indicate tight coupling
if (analysis.crossDirectoryImports > analysis.totalImports * 0.7) {
issues.push({
severity: "critical",
message: "Very high cross-directory dependencies detected",
from: "overall",
to: "overall",
});
}
else if (analysis.crossDirectoryImports > analysis.totalImports * 0.5) {
issues.push({
severity: "warning",
message: "High cross-directory dependencies detected",
from: "overall",
to: "overall",
});
}
// Detect circular dependencies and violations
for (const detail of analysis.details) {
if (detail.isLayerViolation) {
issues.push({
severity: "warning",
message: `Layer violation: ${detail.from} depends on ${detail.to} (${detail.count} times)`,
from: detail.from,
to: detail.to,
});
}
// Check for excessive coupling between specific directories
if (detail.count > 10) {
issues.push({
severity: "warning",
message: `Excessive coupling: ${detail.from} -> ${detail.to} (${detail.count} imports)`,
from: detail.from,
to: detail.to,
});
}
}
return issues;
}
//# sourceMappingURL=coupling.js.map