escomplex-plugin-metrics-module
Advanced tools
Comparing version 0.0.10 to 0.0.11
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
@@ -11,14 +11,10 @@ | ||
var _HalsteadArray = require('typhonjs-escomplex-commons/dist/module/traits/HalsteadArray'); | ||
var _ModuleMetricCalculate = require('./ModuleMetricCalculate'); | ||
var _HalsteadArray2 = _interopRequireDefault(_HalsteadArray); | ||
var _ModuleMetricCalculate2 = _interopRequireDefault(_ModuleMetricCalculate); | ||
var _TraitUtil = require('typhonjs-escomplex-commons/dist/module/traits/TraitUtil'); | ||
var _ModuleMetricControl = require('./ModuleMetricControl'); | ||
var _TraitUtil2 = _interopRequireDefault(_TraitUtil); | ||
var _ModuleMetricControl2 = _interopRequireDefault(_ModuleMetricControl); | ||
var _ObjectUtil = require('typhonjs-escomplex-commons/dist/utils/ObjectUtil'); | ||
var _ObjectUtil2 = _interopRequireDefault(_ObjectUtil); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -35,331 +31,78 @@ | ||
var PluginMetricsModule = function () { | ||
function PluginMetricsModule() { | ||
_classCallCheck(this, PluginMetricsModule); | ||
} | ||
function PluginMetricsModule() { | ||
_classCallCheck(this, PluginMetricsModule); | ||
} | ||
_createClass(PluginMetricsModule, [{ | ||
key: 'onConfigure', | ||
_createClass(PluginMetricsModule, [{ | ||
key: 'onConfigure', | ||
// ESComplexModule plugin callbacks ------------------------------------------------------------------------------ | ||
// ESComplexModule plugin callbacks ------------------------------------------------------------------------------ | ||
/** | ||
* Loads any default settings that are not already provided by any user options. | ||
* | ||
* @param {object} ev - escomplex plugin event data. | ||
* | ||
* The following options are: | ||
* ``` | ||
* (boolean) newmi - Boolean indicating whether the maintainability index should be rebased on a scale from | ||
* 0 to 100; defaults to false. | ||
* ``` | ||
*/ | ||
value: function onConfigure(ev) { | ||
ev.data.settings.newmi = typeof ev.data.options.newmi === 'boolean' ? ev.data.options.newmi : false; | ||
} | ||
/** | ||
* Loads any default settings that are not already provided by any user options. | ||
* | ||
* @param {object} ev - escomplex plugin event data. | ||
* | ||
* The following options are: | ||
* ``` | ||
* (boolean) newmi - Boolean indicating whether the maintainability index should be rebased on a scale from | ||
* 0 to 100; defaults to false. | ||
* ``` | ||
*/ | ||
value: function onConfigure(ev) { | ||
ev.data.settings.newmi = typeof ev.data.options.newmi === 'boolean' ? ev.data.options.newmi : false; | ||
} | ||
/** | ||
* During AST traversal when a node is entered it is processed immediately if the node type corresponds to a | ||
* loaded trait syntax. Any new report scopes are handled in `onEnterNode`. | ||
* | ||
* @param {object} ev - escomplex plugin event data. | ||
*/ | ||
/** | ||
* During AST traversal when a node is entered it is processed immediately if the node type corresponds to a | ||
* loaded trait syntax. | ||
* | ||
* @param {object} ev - escomplex plugin event data. | ||
*/ | ||
}, { | ||
key: 'onEnterNode', | ||
value: function onEnterNode(ev) { | ||
var report = ev.data.report; | ||
var node = ev.data.node; | ||
var parent = ev.data.parent; | ||
var syntax = this.syntaxes[node.type]; | ||
}, { | ||
key: 'onEnterNode', | ||
value: function onEnterNode(ev) { | ||
var report = ev.data.report; | ||
var scopeControl = ev.data.scopeControl; | ||
var node = ev.data.node; | ||
var parent = ev.data.parent; | ||
var syntax = ev.data.syntaxes[node.type]; | ||
// Process node syntax. | ||
if (syntax !== null && (typeof syntax === 'undefined' ? 'undefined' : _typeof(syntax)) === 'object') { | ||
for (var key in syntax) { | ||
switch (syntax[key].metric) { | ||
case 'cyclomatic': | ||
report.incrementCyclomatic(syntax[key].valueOf(node, parent)); | ||
break; | ||
case 'dependencies': | ||
report.addDependencies(syntax[key].valueOf(node, parent)); | ||
break; | ||
case 'lloc': | ||
report.incrementLogicalSloc(syntax[key].valueOf(node, parent)); | ||
break; | ||
} | ||
// Process operands / operators HalsteadArray entries | ||
if (syntax[key] instanceof _HalsteadArray2.default) { | ||
report.processHalsteadItems(syntax[key].metric, syntax[key].valueOf(node, parent)); | ||
} | ||
} | ||
// Handle creating new scope if applicable. | ||
if (syntax.newScope) { | ||
switch (syntax.newScope.valueOf(node, parent)) { | ||
case 'class': | ||
report.createScope('class', _TraitUtil2.default.safeName(node.id), node.loc.start.line, node.loc.end.line); | ||
break; | ||
case 'method': | ||
report.createScope('method', _TraitUtil2.default.safeComputedName(node, parent), node.loc.start.line, node.loc.end.line, node.params.length); | ||
break; | ||
} | ||
} | ||
// Return any child keys to ignore in AST walking or an empty array. | ||
ev.data.ignoreKeys = syntax.ignoreKeys ? syntax.ignoreKeys.valueOf(node, parent) : []; | ||
} | ||
// Process node syntax. | ||
if ((typeof syntax === 'undefined' ? 'undefined' : _typeof(syntax)) === 'object') { | ||
_ModuleMetricControl2.default.processSyntax(report, scopeControl, syntax, node, parent); | ||
} | ||
} | ||
/** | ||
* During AST traversal when a node is exited it is processed immediately if the node type corresponds to a | ||
* loaded trait syntax. If a node has a new report scope it is popped in `onExitNode`. | ||
* | ||
* @param {object} ev - escomplex plugin event data. | ||
*/ | ||
/** | ||
* Performs final calculations based on collected report data. | ||
* | ||
* @param {object} ev - escomplex plugin event data. | ||
*/ | ||
}, { | ||
key: 'onExitNode', | ||
value: function onExitNode(ev) { | ||
var report = ev.data.report; | ||
var node = ev.data.node; | ||
var parent = ev.data.parent; | ||
var syntax = this.syntaxes[node.type]; | ||
}, { | ||
key: 'onModuleEnd', | ||
value: function onModuleEnd(ev) { | ||
_ModuleMetricCalculate2.default.calculateMetrics(ev.data.report, ev.data.settings); | ||
} | ||
if (syntax !== null && (typeof syntax === 'undefined' ? 'undefined' : _typeof(syntax)) === 'object' && syntax.newScope) { | ||
switch (syntax.newScope.valueOf(node, parent)) { | ||
case 'class': | ||
report.popScope('class'); | ||
break; | ||
/** | ||
* A new module report scope has been created. Update any associated metrics regarding the new scope. | ||
* | ||
* @param {object} ev - escomplex plugin event data. | ||
*/ | ||
case 'method': | ||
report.popScope('method'); | ||
break; | ||
} | ||
} | ||
} | ||
}, { | ||
key: 'onScopeCreated', | ||
value: function onScopeCreated(ev) { | ||
var report = ev.data.report; | ||
var scopeControl = ev.data.scopeControl; | ||
var newScope = ev.data.newScope; | ||
/** | ||
* Performs final calculations based on collected report data. | ||
* | ||
* @param {object} ev - escomplex plugin event data. | ||
*/ | ||
_ModuleMetricControl2.default.createScope(report, scopeControl, newScope); | ||
} | ||
}]); | ||
}, { | ||
key: 'onModuleEnd', | ||
value: function onModuleEnd(ev) { | ||
this._calculateMetrics(ev.data.report); | ||
} | ||
/** | ||
* Stores settings and syntaxes, initializes local variables and creates the initial aggregate report. | ||
* | ||
* @param {object} ev - escomplex plugin event data. | ||
*/ | ||
}, { | ||
key: 'onModuleStart', | ||
value: function onModuleStart(ev) { | ||
/** | ||
* Stores the settings for all ESComplexModule plugins. | ||
* @type {object} | ||
*/ | ||
this.settings = ev.data.settings; | ||
/** | ||
* Stores the trait syntaxes loaded by other ESComplexModule plugins. | ||
* @type {object} | ||
*/ | ||
this.syntaxes = ev.data.syntaxes; | ||
} | ||
// Module metrics calculation ------------------------------------------------------------------------------------ | ||
/** | ||
* Calculates cyclomatic density - Proposed as a modification to cyclomatic complexity by Geoffrey K. Gill and | ||
* Chris F. Kemerer in 1991, this metric simply re-expresses it as a percentage of the logical lines of code. Lower | ||
* is better. | ||
* | ||
* @param {MethodReport} report - A MethodReport to perform calculations on. | ||
* | ||
* @private | ||
*/ | ||
}, { | ||
key: '_calculateCyclomaticDensity', | ||
value: function _calculateCyclomaticDensity(report) { | ||
report.cyclomaticDensity = report.sloc.logical === 0 ? 0 : report.cyclomatic / report.sloc.logical * 100; | ||
} | ||
/** | ||
* Calculates Halstead metrics. In 1977, Maurice Halstead developed a set of metrics which are calculated based on | ||
* the number of distinct operators, the number of distinct operands, the total number of operators and the total | ||
* number of operands in each function. This site picks out three Halstead measures in particular: difficulty, | ||
* volume and effort. | ||
* | ||
* @param {HalsteadData} halstead - A HalsteadData instance to perform calculations on. | ||
* | ||
* @see https://en.wikipedia.org/wiki/Halstead_complexity_measures | ||
* | ||
* @private | ||
*/ | ||
}, { | ||
key: '_calculateHalsteadMetrics', | ||
value: function _calculateHalsteadMetrics(halstead) { | ||
halstead.length = halstead.operators.total + halstead.operands.total; | ||
/* istanbul ignore if */ | ||
if (halstead.length === 0) { | ||
halstead.reset(); | ||
} else { | ||
halstead.vocabulary = halstead.operators.distinct + halstead.operands.distinct; | ||
halstead.difficulty = halstead.operators.distinct / 2 * (halstead.operands.distinct === 0 ? 1 : halstead.operands.total / halstead.operands.distinct); | ||
halstead.volume = halstead.length * (Math.log(halstead.vocabulary) / Math.log(2)); | ||
halstead.effort = halstead.difficulty * halstead.volume; | ||
halstead.bugs = halstead.volume / 3000; | ||
halstead.time = halstead.effort / 18; | ||
} | ||
} | ||
/** | ||
* Designed in 1991 by Paul Oman and Jack Hagemeister at the University of Idaho, this metric is calculated at the | ||
* whole program or module level from averages of the other 3 metrics, using the following formula: | ||
* ``` | ||
* 171 - | ||
* (3.42 * ln(mean effort)) - | ||
* (0.23 * ln(mean cyclomatic complexity)) - | ||
* (16.2 * ln(mean logical LOC)) | ||
* ``` | ||
* Values are on a logarithmic scale ranging from negative infinity up to 171, with greater numbers indicating a | ||
* higher level of maintainability. In their original paper, Oman and Hagemeister identified 65 as the threshold | ||
* value below which a program should be considered difficult to maintain. | ||
* | ||
* @param {ClassReport|ModuleReport} report - A ClassReport or ModuleReport to perform calculations on. | ||
* @param {number} averageCyclomatic - Average cyclomatic metric across a ClassReport / ModuleReport. | ||
* @param {number} averageEffort - Average Halstead effort across a ClassReport / ModuleReport. | ||
* @param {number} averageLoc - Average SLOC metric across a ClassReport / ModuleReport. | ||
* | ||
* @private | ||
*/ | ||
}, { | ||
key: '_calculateMaintainabilityIndex', | ||
value: function _calculateMaintainabilityIndex(report, averageCyclomatic, averageEffort, averageLoc) { | ||
/* istanbul ignore if */ | ||
if (averageCyclomatic === 0) { | ||
throw new Error('Encountered function with cyclomatic complexity zero!'); | ||
} | ||
report.maintainability = 171 - 3.42 * Math.log(averageEffort) - 0.23 * Math.log(averageCyclomatic) - 16.2 * Math.log(averageLoc); | ||
/* istanbul ignore if */ | ||
if (report.maintainability > 171) { | ||
report.maintainability = 171; | ||
} | ||
/* istanbul ignore if */ | ||
if (this.settings.newmi) { | ||
report.maintainability = Math.max(0, report.maintainability * 100 / 171); | ||
} | ||
} | ||
/** | ||
* Coordinates calculating all metrics. All module and class methods are traversed. If there are no module or class | ||
* methods respectively the aggregate MethodReport is used for calculations. | ||
* | ||
* @param {ModuleReport} report - The ModuleReport being processed. | ||
* | ||
* @private | ||
*/ | ||
}, { | ||
key: '_calculateMetrics', | ||
value: function _calculateMetrics(report) { | ||
var _this = this; | ||
var moduleMethodCount = report.methods.length; | ||
var moduleMethodAverages = report.methodAverage; | ||
var moduleMethodAverageKeys = _ObjectUtil2.default.getAccessorList(moduleMethodAverages); | ||
// Handle module methods. | ||
report.methods.forEach(function (methodReport) { | ||
moduleMethodAverageKeys.forEach(function (averageKey) { | ||
_this._calculateCyclomaticDensity(methodReport); | ||
_this._calculateHalsteadMetrics(methodReport.halstead); | ||
var targetValue = _ObjectUtil2.default.safeAccess(methodReport, averageKey, 0); | ||
_ObjectUtil2.default.safeSet(moduleMethodAverages, averageKey, targetValue, 'add'); | ||
}); | ||
}); | ||
// Handle module class reports. | ||
report.classes.forEach(function (classReport) { | ||
var classMethodAverages = classReport.methodAverage; | ||
var classMethodCount = classReport.methods.length; | ||
moduleMethodCount += classMethodCount; | ||
// Process all class methods. | ||
classReport.methods.forEach(function (methodReport) { | ||
_this._calculateCyclomaticDensity(methodReport); | ||
_this._calculateHalsteadMetrics(methodReport.halstead); | ||
moduleMethodAverageKeys.forEach(function (averageKey) { | ||
var targetValue = _ObjectUtil2.default.safeAccess(methodReport, averageKey, 0); | ||
_ObjectUtil2.default.safeSet(moduleMethodAverages, averageKey, targetValue, 'add'); | ||
_ObjectUtil2.default.safeSet(classMethodAverages, averageKey, targetValue, 'add'); | ||
}); | ||
}); | ||
_this._calculateCyclomaticDensity(classReport.aggregateMethodReport); | ||
_this._calculateHalsteadMetrics(classReport.aggregateMethodReport.halstead); | ||
// If there are no class methods use the class aggregate MethodReport. | ||
if (classMethodCount === 0) { | ||
// Sane handling of classes that contain no methods. | ||
moduleMethodAverageKeys.forEach(function (averageKey) { | ||
var targetValue = _ObjectUtil2.default.safeAccess(classReport.aggregateMethodReport, averageKey, 0); | ||
_ObjectUtil2.default.safeSet(classMethodAverages, averageKey, targetValue, 'add'); | ||
}); | ||
classMethodCount = 1; | ||
} | ||
moduleMethodAverageKeys.forEach(function (averageKey) { | ||
_ObjectUtil2.default.safeSet(classMethodAverages, averageKey, classMethodCount, 'div'); | ||
}); | ||
_this._calculateMaintainabilityIndex(classReport, classMethodAverages.cyclomatic, classMethodAverages.halstead.effort, classMethodAverages.sloc.logical); | ||
}); | ||
this._calculateCyclomaticDensity(report.aggregateMethodReport); | ||
this._calculateHalsteadMetrics(report.aggregateMethodReport.halstead); | ||
// If there are no module methods use the module aggregate MethodReport. | ||
if (moduleMethodCount === 0) { | ||
// Sane handling of classes that contain no methods. | ||
moduleMethodAverageKeys.forEach(function (averageKey) { | ||
var targetValue = _ObjectUtil2.default.safeAccess(report.aggregateMethodReport, averageKey, 0); | ||
_ObjectUtil2.default.safeSet(moduleMethodAverages, averageKey, targetValue, 'add'); | ||
}); | ||
// Sane handling of modules that contain no methods. | ||
moduleMethodCount = 1; | ||
} | ||
moduleMethodAverageKeys.forEach(function (averageKey) { | ||
_ObjectUtil2.default.safeSet(moduleMethodAverages, averageKey, moduleMethodCount, 'div'); | ||
}); | ||
this._calculateMaintainabilityIndex(report, moduleMethodAverages.cyclomatic, moduleMethodAverages.halstead.effort, moduleMethodAverages.sloc.logical); | ||
} | ||
}]); | ||
return PluginMetricsModule; | ||
return PluginMetricsModule; | ||
}(); | ||
@@ -366,0 +109,0 @@ |
{ | ||
"name": "escomplex-plugin-metrics-module", | ||
"version": "0.0.10", | ||
"version": "0.0.11", | ||
"homepage": "https://github.com/typhonjs-node-escomplex/escomplex-plugin-metrics-module/", | ||
@@ -23,6 +23,6 @@ "description": "Provides the core module metric / report generation plugin for typhonjs-escomplex module processing.", | ||
"dependencies": { | ||
"typhonjs-escomplex-commons": "^0.0.14" | ||
"typhonjs-escomplex-commons": "^0.0.15" | ||
}, | ||
"devDependencies": { | ||
"escomplex-plugin-syntax-babylon": "^0.0.10", | ||
"escomplex-plugin-syntax-babylon": "^0.0.11", | ||
"typhonjs-ast-walker": "^0.1.0", | ||
@@ -29,0 +29,0 @@ "typhonjs-config-eslint": "^0.4.0", |
@@ -1,6 +0,4 @@ | ||
import HalsteadArray from 'typhonjs-escomplex-commons/src/module/traits/HalsteadArray'; | ||
import TraitUtil from 'typhonjs-escomplex-commons/src/module/traits/TraitUtil'; | ||
import ModuleMetricCalculate from './ModuleMetricCalculate'; | ||
import ModuleMetricControl from './ModuleMetricControl'; | ||
import ObjectUtil from 'typhonjs-escomplex-commons/src/utils/ObjectUtil'; | ||
/** | ||
@@ -34,3 +32,3 @@ * Provides a typhonjs-escomplex-module / ESComplexModule plugin which gathers and calculates all default metrics. | ||
* During AST traversal when a node is entered it is processed immediately if the node type corresponds to a | ||
* loaded trait syntax. Any new report scopes are handled in `onEnterNode`. | ||
* loaded trait syntax. | ||
* | ||
@@ -42,83 +40,12 @@ * @param {object} ev - escomplex plugin event data. | ||
const report = ev.data.report; | ||
const scopeControl = ev.data.scopeControl; | ||
const node = ev.data.node; | ||
const parent = ev.data.parent; | ||
const syntax = this.syntaxes[node.type]; | ||
const syntax = ev.data.syntaxes[node.type]; | ||
// Process node syntax. | ||
if (syntax !== null && typeof syntax === 'object') | ||
{ | ||
for (const key in syntax) | ||
{ | ||
switch (syntax[key].metric) | ||
{ | ||
case 'cyclomatic': | ||
report.incrementCyclomatic(syntax[key].valueOf(node, parent)); | ||
break; | ||
case 'dependencies': | ||
report.addDependencies(syntax[key].valueOf(node, parent)); | ||
break; | ||
case 'lloc': | ||
report.incrementLogicalSloc(syntax[key].valueOf(node, parent)); | ||
break; | ||
} | ||
// Process operands / operators HalsteadArray entries | ||
if (syntax[key] instanceof HalsteadArray) | ||
{ | ||
report.processHalsteadItems(syntax[key].metric, syntax[key].valueOf(node, parent)); | ||
} | ||
} | ||
// Handle creating new scope if applicable. | ||
if (syntax.newScope) | ||
{ | ||
switch (syntax.newScope.valueOf(node, parent)) | ||
{ | ||
case 'class': | ||
report.createScope('class', TraitUtil.safeName(node.id), node.loc.start.line, node.loc.end.line); | ||
break; | ||
case 'method': | ||
report.createScope('method', TraitUtil.safeComputedName(node, parent), node.loc.start.line, | ||
node.loc.end.line, node.params.length); | ||
break; | ||
} | ||
} | ||
// Return any child keys to ignore in AST walking or an empty array. | ||
ev.data.ignoreKeys = syntax.ignoreKeys ? syntax.ignoreKeys.valueOf(node, parent) : []; | ||
} | ||
if (typeof syntax === 'object') { ModuleMetricControl.processSyntax(report, scopeControl, syntax, node, parent); } | ||
} | ||
/** | ||
* During AST traversal when a node is exited it is processed immediately if the node type corresponds to a | ||
* loaded trait syntax. If a node has a new report scope it is popped in `onExitNode`. | ||
* | ||
* @param {object} ev - escomplex plugin event data. | ||
*/ | ||
onExitNode(ev) | ||
{ | ||
const report = ev.data.report; | ||
const node = ev.data.node; | ||
const parent = ev.data.parent; | ||
const syntax = this.syntaxes[node.type]; | ||
if (syntax !== null && typeof syntax === 'object' && syntax.newScope) | ||
{ | ||
switch (syntax.newScope.valueOf(node, parent)) | ||
{ | ||
case 'class': | ||
report.popScope('class'); | ||
break; | ||
case 'method': | ||
report.popScope('method'); | ||
break; | ||
} | ||
} | ||
} | ||
/** | ||
* Performs final calculations based on collected report data. | ||
@@ -130,214 +57,18 @@ * | ||
{ | ||
this._calculateMetrics(ev.data.report); | ||
ModuleMetricCalculate.calculateMetrics(ev.data.report, ev.data.settings); | ||
} | ||
/** | ||
* Stores settings and syntaxes, initializes local variables and creates the initial aggregate report. | ||
* A new module report scope has been created. Update any associated metrics regarding the new scope. | ||
* | ||
* @param {object} ev - escomplex plugin event data. | ||
*/ | ||
onModuleStart(ev) | ||
onScopeCreated(ev) | ||
{ | ||
/** | ||
* Stores the settings for all ESComplexModule plugins. | ||
* @type {object} | ||
*/ | ||
this.settings = ev.data.settings; | ||
const report = ev.data.report; | ||
const scopeControl = ev.data.scopeControl; | ||
const newScope = ev.data.newScope; | ||
/** | ||
* Stores the trait syntaxes loaded by other ESComplexModule plugins. | ||
* @type {object} | ||
*/ | ||
this.syntaxes = ev.data.syntaxes; | ||
ModuleMetricControl.createScope(report, scopeControl, newScope); | ||
} | ||
// Module metrics calculation ------------------------------------------------------------------------------------ | ||
/** | ||
* Calculates cyclomatic density - Proposed as a modification to cyclomatic complexity by Geoffrey K. Gill and | ||
* Chris F. Kemerer in 1991, this metric simply re-expresses it as a percentage of the logical lines of code. Lower | ||
* is better. | ||
* | ||
* @param {MethodReport} report - A MethodReport to perform calculations on. | ||
* | ||
* @private | ||
*/ | ||
_calculateCyclomaticDensity(report) | ||
{ | ||
report.cyclomaticDensity = report.sloc.logical === 0 ? 0 : (report.cyclomatic / report.sloc.logical) * 100; | ||
} | ||
/** | ||
* Calculates Halstead metrics. In 1977, Maurice Halstead developed a set of metrics which are calculated based on | ||
* the number of distinct operators, the number of distinct operands, the total number of operators and the total | ||
* number of operands in each function. This site picks out three Halstead measures in particular: difficulty, | ||
* volume and effort. | ||
* | ||
* @param {HalsteadData} halstead - A HalsteadData instance to perform calculations on. | ||
* | ||
* @see https://en.wikipedia.org/wiki/Halstead_complexity_measures | ||
* | ||
* @private | ||
*/ | ||
_calculateHalsteadMetrics(halstead) | ||
{ | ||
halstead.length = halstead.operators.total + halstead.operands.total; | ||
/* istanbul ignore if */ | ||
if (halstead.length === 0) | ||
{ | ||
halstead.reset(); | ||
} | ||
else | ||
{ | ||
halstead.vocabulary = halstead.operators.distinct + halstead.operands.distinct; | ||
halstead.difficulty = (halstead.operators.distinct / 2) | ||
* (halstead.operands.distinct === 0 ? 1 : halstead.operands.total / halstead.operands.distinct); | ||
halstead.volume = halstead.length * (Math.log(halstead.vocabulary) / Math.log(2)); | ||
halstead.effort = halstead.difficulty * halstead.volume; | ||
halstead.bugs = halstead.volume / 3000; | ||
halstead.time = halstead.effort / 18; | ||
} | ||
} | ||
/** | ||
* Designed in 1991 by Paul Oman and Jack Hagemeister at the University of Idaho, this metric is calculated at the | ||
* whole program or module level from averages of the other 3 metrics, using the following formula: | ||
* ``` | ||
* 171 - | ||
* (3.42 * ln(mean effort)) - | ||
* (0.23 * ln(mean cyclomatic complexity)) - | ||
* (16.2 * ln(mean logical LOC)) | ||
* ``` | ||
* Values are on a logarithmic scale ranging from negative infinity up to 171, with greater numbers indicating a | ||
* higher level of maintainability. In their original paper, Oman and Hagemeister identified 65 as the threshold | ||
* value below which a program should be considered difficult to maintain. | ||
* | ||
* @param {ClassReport|ModuleReport} report - A ClassReport or ModuleReport to perform calculations on. | ||
* @param {number} averageCyclomatic - Average cyclomatic metric across a ClassReport / ModuleReport. | ||
* @param {number} averageEffort - Average Halstead effort across a ClassReport / ModuleReport. | ||
* @param {number} averageLoc - Average SLOC metric across a ClassReport / ModuleReport. | ||
* | ||
* @private | ||
*/ | ||
_calculateMaintainabilityIndex(report, averageCyclomatic, averageEffort, averageLoc) | ||
{ | ||
/* istanbul ignore if */ | ||
if (averageCyclomatic === 0) { throw new Error('Encountered function with cyclomatic complexity zero!'); } | ||
report.maintainability = | ||
171 | ||
- (3.42 * Math.log(averageEffort)) | ||
- (0.23 * Math.log(averageCyclomatic)) | ||
- (16.2 * Math.log(averageLoc)); | ||
/* istanbul ignore if */ | ||
if (report.maintainability > 171) { report.maintainability = 171; } | ||
/* istanbul ignore if */ | ||
if (this.settings.newmi) { report.maintainability = Math.max(0, (report.maintainability * 100) / 171); } | ||
} | ||
/** | ||
* Coordinates calculating all metrics. All module and class methods are traversed. If there are no module or class | ||
* methods respectively the aggregate MethodReport is used for calculations. | ||
* | ||
* @param {ModuleReport} report - The ModuleReport being processed. | ||
* | ||
* @private | ||
*/ | ||
_calculateMetrics(report) | ||
{ | ||
let moduleMethodCount = report.methods.length; | ||
const moduleMethodAverages = report.methodAverage; | ||
const moduleMethodAverageKeys = ObjectUtil.getAccessorList(moduleMethodAverages); | ||
// Handle module methods. | ||
report.methods.forEach((methodReport) => | ||
{ | ||
moduleMethodAverageKeys.forEach((averageKey) => | ||
{ | ||
this._calculateCyclomaticDensity(methodReport); | ||
this._calculateHalsteadMetrics(methodReport.halstead); | ||
const targetValue = ObjectUtil.safeAccess(methodReport, averageKey, 0); | ||
ObjectUtil.safeSet(moduleMethodAverages, averageKey, targetValue, 'add'); | ||
}); | ||
}); | ||
// Handle module class reports. | ||
report.classes.forEach((classReport) => | ||
{ | ||
const classMethodAverages = classReport.methodAverage; | ||
let classMethodCount = classReport.methods.length; | ||
moduleMethodCount += classMethodCount; | ||
// Process all class methods. | ||
classReport.methods.forEach((methodReport) => | ||
{ | ||
this._calculateCyclomaticDensity(methodReport); | ||
this._calculateHalsteadMetrics(methodReport.halstead); | ||
moduleMethodAverageKeys.forEach((averageKey) => | ||
{ | ||
const targetValue = ObjectUtil.safeAccess(methodReport, averageKey, 0); | ||
ObjectUtil.safeSet(moduleMethodAverages, averageKey, targetValue, 'add'); | ||
ObjectUtil.safeSet(classMethodAverages, averageKey, targetValue, 'add'); | ||
}); | ||
}); | ||
this._calculateCyclomaticDensity(classReport.aggregateMethodReport); | ||
this._calculateHalsteadMetrics(classReport.aggregateMethodReport.halstead); | ||
// If there are no class methods use the class aggregate MethodReport. | ||
if (classMethodCount === 0) | ||
{ | ||
// Sane handling of classes that contain no methods. | ||
moduleMethodAverageKeys.forEach((averageKey) => | ||
{ | ||
const targetValue = ObjectUtil.safeAccess(classReport.aggregateMethodReport, averageKey, 0); | ||
ObjectUtil.safeSet(classMethodAverages, averageKey, targetValue, 'add'); | ||
}); | ||
classMethodCount = 1; | ||
} | ||
moduleMethodAverageKeys.forEach((averageKey) => | ||
{ | ||
ObjectUtil.safeSet(classMethodAverages, averageKey, classMethodCount, 'div'); | ||
}); | ||
this._calculateMaintainabilityIndex(classReport, classMethodAverages.cyclomatic, | ||
classMethodAverages.halstead.effort, classMethodAverages.sloc.logical); | ||
}); | ||
this._calculateCyclomaticDensity(report.aggregateMethodReport); | ||
this._calculateHalsteadMetrics(report.aggregateMethodReport.halstead); | ||
// If there are no module methods use the module aggregate MethodReport. | ||
if (moduleMethodCount === 0) | ||
{ | ||
// Sane handling of classes that contain no methods. | ||
moduleMethodAverageKeys.forEach((averageKey) => | ||
{ | ||
const targetValue = ObjectUtil.safeAccess(report.aggregateMethodReport, averageKey, 0); | ||
ObjectUtil.safeSet(moduleMethodAverages, averageKey, targetValue, 'add'); | ||
}); | ||
// Sane handling of modules that contain no methods. | ||
moduleMethodCount = 1; | ||
} | ||
moduleMethodAverageKeys.forEach((averageKey) => | ||
{ | ||
ObjectUtil.safeSet(moduleMethodAverages, averageKey, moduleMethodCount, 'div'); | ||
}); | ||
this._calculateMaintainabilityIndex(report, moduleMethodAverages.cyclomatic, | ||
moduleMethodAverages.halstead.effort, moduleMethodAverages.sloc.logical); | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
65363
12
872
1
+ Addedtyphonjs-escomplex-commons@0.0.15(transitive)
- Removedtyphonjs-escomplex-commons@0.0.14(transitive)