UNPKG

archunit

Version:

ArchUnit TypeScript is an architecture testing library, to specify and assert architecture rules in your TypeScript app

335 lines 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LCOMStar = exports.LCOM5 = exports.LCOM4 = exports.LCOM3 = exports.LCOM2 = exports.LCOM1 = exports.LCOM96b = exports.LCOM96a = void 0; /** * LCOM96a (Lack of Cohesion of Methods, Handerson et al., 1996) * * Formula: LCOM96a = (1/(1-m)) * ((1/a) * Σ(μ(A)) - m) for all attributes A * * Where: * - a is the number of attributes (fields) in the class * - m is the number of attributes (fields) in the class * - μ(A) is the number of methods that access an attribute (field) A * - The formula measures how methods are connected through attributes * * Returns a value >= 0: * - 0: perfect cohesion * - Higher values: increasing lack of cohesion */ class LCOM96a { constructor() { this.name = 'LCOM96a'; this.description = 'Lack of Cohesion of Methods (Handerson et al., 1996a) - Measures attribute sharing among methods'; } calculate(classInfo) { const m = classInfo.methods.length; const a = classInfo.fields.length; // If there are no attributes, cohesion is undefined/perfect if (a === 0) { return 0; } const summands = []; classInfo.fields.forEach((field) => { const mu = field.accessedBy.length; summands.push(mu / a); }); const sum = summands.reduce((partialSum, summand) => partialSum + summand, 0); return (1 / (1 - m)) * (sum - m); } } exports.LCOM96a = LCOM96a; /** * LCOM96b (Lack of Cohesion of Methods, Handerson et al., 1996) * * Formula: LCOM96b = (1/a) * Σ((1/m) * (m - μ(A))) for all attributes A * * Where: * - m is the number of methods in the class * - a is the number of attributes (fields) in the class * - μ(A) is the number of methods that access an attribute (field) A * - The formula measures how methods are connected through attributes * * Returns a value between 0 and 1: * - 0: perfect cohesion (all methods access all attributes) * - 1: complete lack of cohesion (each method accesses its own attribute) */ class LCOM96b { constructor() { this.name = 'LCOM96b'; this.description = 'Lack of Cohesion of Methods (Handerson et al., 1996) - Ranges from 0 (high cohesion) to 1 (low cohesion)'; } calculate(classInfo) { const m = classInfo.methods.length; const a = classInfo.fields.length; // If there are no methods or only one method, cohesion is perfect if (m <= 1 || a === 0) { return 0; } const summands = []; classInfo.fields.forEach((field) => { const mu = field.accessedBy.length; const s = (1 / m) * (m - mu); summands.push(s); }); const sum = summands.reduce((partialSum, summand) => partialSum + summand, 0); return (1 / a) * sum; } } exports.LCOM96b = LCOM96b; /** * LCOM1 (Chidamber & Kemerer, 1991) * * Formula: LCOM1 = |P| - |Q|, where P > Q, otherwise 0 * * Where: * - P is the set of method pairs that do not share instance variables * - Q is the set of method pairs that share at least one instance variable * - If P <= Q, then LCOM1 = 0 * * Returns a value >= 0: * - 0: good cohesion * - Higher values: lack of cohesion */ class LCOM1 { constructor() { this.name = 'LCOM1'; this.description = 'Lack of Cohesion of Methods (Chidamber & Kemerer, 1991) - Difference between non-sharing and sharing method pairs'; } calculate(classInfo) { const methods = classInfo.methods; const m = methods.length; if (m <= 1) { return 0; } let P = 0; // pairs that don't share variables let Q = 0; // pairs that share at least one variable // Compare each pair of methods for (let i = 0; i < m; i++) { for (let j = i + 1; j < m; j++) { const method1Fields = new Set(methods[i].accessedFields); const method2Fields = new Set(methods[j].accessedFields); // Check if methods share any fields const hasSharedField = [...method1Fields].some((field) => method2Fields.has(field)); if (hasSharedField) { Q++; } else { P++; } } } return Math.max(0, P - Q); } } exports.LCOM1 = LCOM1; /** * LCOM2 (Chidamber & Kemerer, 1994 - revised) * * Formula: LCOM2 = 1 - (Σ(MF) / (M * F)) * * Where: * - M is the number of methods in the class * - F is the number of fields in the class * - MF is the number of methods that access each field * - Σ(MF) is the sum of MF over all fields * * Returns a value between 0 and 1: * - 0: perfect cohesion (all methods access all fields) * - 1: no cohesion (no method accesses any field) */ class LCOM2 { constructor() { this.name = 'LCOM2'; this.description = 'Lack of Cohesion of Methods (Chidamber & Kemerer, 1994) - Normalized measure of method-field relationships'; } calculate(classInfo) { const M = classInfo.methods.length; const F = classInfo.fields.length; if (M === 0 || F === 0) { return 0; } // Sum of methods accessing each field const sumMF = classInfo.fields.reduce((sum, field) => sum + field.accessedBy.length, 0); return 1 - sumMF / (M * F); } } exports.LCOM2 = LCOM2; /** * LCOM3 (Li & Henry, 1993) * * Formula: LCOM3 = (M - Σ(MF)/F) / (M - 1) * * Where: * - M is the number of methods in the class * - F is the number of fields in the class * - MF is the number of methods that access each field * - Σ(MF) is the sum of MF over all fields * * Returns a value between 0 and 1: * - 0: perfect cohesion * - 1: lack of cohesion */ class LCOM3 { constructor() { this.name = 'LCOM3'; this.description = 'Lack of Cohesion of Methods (Li & Henry, 1993) - Normalized cohesion measure'; } calculate(classInfo) { const M = classInfo.methods.length; const F = classInfo.fields.length; if (M <= 1 || F === 0) { return 0; } // Sum of methods accessing each field const sumMF = classInfo.fields.reduce((sum, field) => sum + field.accessedBy.length, 0); return (M - sumMF / F) / (M - 1); } } exports.LCOM3 = LCOM3; /** * LCOM4 (Hitz & Montazeri, 1995) * * Formula: LCOM4 = number of connected components in the method-field graph * * Where: * - A connected component is a maximal set of methods connected through shared fields * - Methods are connected if they access the same field or are transitively connected * * Returns a value >= 1: * - 1: perfect cohesion (all methods are connected) * - Higher values: increasing lack of cohesion */ class LCOM4 { constructor() { this.name = 'LCOM4'; this.description = 'Lack of Cohesion of Methods (Hitz & Montazeri, 1995) - Number of connected components'; } calculate(classInfo) { const methods = classInfo.methods; const m = methods.length; if (m <= 1) { return 1; } // Create adjacency list for method connectivity const methodConnections = new Map(); for (let i = 0; i < m; i++) { methodConnections.set(i, new Set()); } // Connect methods that share at least one field for (let i = 0; i < m; i++) { for (let j = i + 1; j < m; j++) { const method1Fields = new Set(methods[i].accessedFields); const method2Fields = new Set(methods[j].accessedFields); // Check if methods share any fields const hasSharedField = [...method1Fields].some((field) => method2Fields.has(field)); if (hasSharedField) { methodConnections.get(i).add(j); methodConnections.get(j).add(i); } } } // Count connected components using DFS const visited = new Set(); let components = 0; for (let i = 0; i < m; i++) { if (!visited.has(i)) { components++; // DFS to mark all connected methods const stack = [i]; while (stack.length > 0) { const current = stack.pop(); if (!visited.has(current)) { visited.add(current); for (const neighbor of methodConnections.get(current)) { if (!visited.has(neighbor)) { stack.push(neighbor); } } } } } } return components; } } exports.LCOM4 = LCOM4; /** * LCOM5 (Henderson-Sellers, 1996) * * Formula: LCOM5 = (a - μΣ(mA)) / (a - μ) * * Where: * - a is the number of attributes (fields) * - μ is the number of methods * - mA is the number of methods accessing attribute A * - Σ(mA) is the sum over all attributes * * Returns a value between 0 and 1: * - 0: perfect cohesion * - 1: lack of cohesion */ class LCOM5 { constructor() { this.name = 'LCOM5'; this.description = 'Lack of Cohesion of Methods (Henderson-Sellers, 1996) - Normalized attribute access measure'; } calculate(classInfo) { const mu = classInfo.methods.length; const a = classInfo.fields.length; if (a === 0 || mu <= 1) { return 0; } // Sum of methods accessing each attribute const sumMA = classInfo.fields.reduce((sum, field) => sum + field.accessedBy.length, 0); const denominator = a - mu; if (denominator === 0) { return 0; } return (a - sumMA / mu) / denominator; } } exports.LCOM5 = LCOM5; /** * LCOM* (Fernandez & Pena, 2006) - also known as LCOM-star * * Formula: LCOM* = (number of method pairs without shared attributes) / (total method pairs) * * Where: * - Method pairs are counted once (not twice) * - Shared attributes means both methods access at least one common field * * Returns a value between 0 and 1: * - 0: perfect cohesion (all method pairs share attributes) * - 1: no cohesion (no method pairs share attributes) */ class LCOMStar { constructor() { this.name = 'LCOM*'; this.description = 'Lack of Cohesion of Methods Star (Fernandez & Pena, 2006) - Ratio of non-sharing method pairs'; } calculate(classInfo) { const methods = classInfo.methods; const m = methods.length; if (m <= 1) { return 0; } let nonSharingPairs = 0; let totalPairs = 0; // Compare each pair of methods for (let i = 0; i < m; i++) { for (let j = i + 1; j < m; j++) { totalPairs++; const method1Fields = new Set(methods[i].accessedFields); const method2Fields = new Set(methods[j].accessedFields); // Check if methods share any fields const hasSharedField = [...method1Fields].some((field) => method2Fields.has(field)); if (!hasSharedField) { nonSharingPairs++; } } } return totalPairs === 0 ? 0 : nonSharingPairs / totalPairs; } } exports.LCOMStar = LCOMStar; //# sourceMappingURL=lcom.js.map