Comparing version 0.1.3 to 0.2.0
{ | ||
"name": "escomplex", | ||
"version": "0.1.3", | ||
"description": "Software complexity analysis of Mozilla-format abstract syntax trees.", | ||
"version": "0.2.0", | ||
"description": "Software complexity analysis of JavaScript-family abstract syntax trees.", | ||
"homepage": "https://github.com/philbooth/escomplex", | ||
@@ -30,3 +30,4 @@ "bugs": "https://github.com/philbooth/escomplex/issues", | ||
"dependencies": { | ||
"check-types": "0.7.x" | ||
"check-types": "0.7.x", | ||
"matrix-utilities": "1.2.x" | ||
}, | ||
@@ -37,3 +38,3 @@ "devDependencies": { | ||
"chai": "1.8.x", | ||
"escomplex-ast-moz": "0.1.0", | ||
"escomplex-ast-moz": "0.1.x", | ||
"esprima": "1.0.x", | ||
@@ -44,5 +45,7 @@ "coffee-script-redux": "2.0.x" | ||
"lint": "./node_modules/jshint/bin/jshint src --config config/jshint.json", | ||
"test": "./node_modules/mocha/bin/mocha --ui tdd --reporter spec --colors test" | ||
"test": "npm run test-module && npm run test-project", | ||
"test-module": "./node_modules/mocha/bin/mocha --ui tdd --reporter spec --colors test/module", | ||
"test-project": "./node_modules/mocha/bin/mocha --ui tdd --reporter spec --colors test/project" | ||
} | ||
} | ||
309
README.md
# escomplex | ||
THIS PROJECT IS (kind of) BRAND NEW AND NOT SAFE TO USE YET! :) | ||
[![Build status][ci-image]][ci-status] | ||
@@ -9,11 +7,38 @@ | ||
of JavaScript-family abstract syntax trees. | ||
The back-end for [complexity-report]. | ||
* [Metrics][#metrics] | ||
* [Abstract syntax trees][#abstract-syntax-trees] | ||
* [Installation][#installation] | ||
* [Usage][#usage] | ||
* [Related projects][#related-projects] | ||
* [Development][#development] | ||
* [License][#license] | ||
* [Abstract syntax trees](#abstract-syntax-trees) | ||
* [Syntax tree walkers](#syntax-tree-walkers) | ||
* [Metrics](#metrics) | ||
* [Links to research](#links-to-research) | ||
* [Installation](#installation) | ||
* [Usage](#usage) | ||
* [Related projects](#related-projects) | ||
* [Development](#development) | ||
* [License](#license) | ||
## Abstract syntax trees | ||
This library deliberately excludes | ||
logic for parsing source code | ||
and for navigating parse trees. | ||
Both the syntax tree | ||
and a matching [syntax tree walker](#syntax-tree-walkers) | ||
are inputs to escomplex, | ||
meaning it is not tied | ||
to any particular language, | ||
parser | ||
or data format. | ||
## Syntax tree walkers | ||
* [escomplex-ast-moz]: | ||
Walks syntax trees | ||
that conform to the format | ||
defined in Mozilla's [Parser API][api]. | ||
This format is returned | ||
by [Esprima] | ||
and [Acorn], | ||
two popular JavaScript parsers. | ||
## Metrics | ||
@@ -23,18 +48,90 @@ | ||
* lines of code; | ||
* number of parameters; | ||
* cyclomatic complexity; | ||
* Halstead metrics; | ||
* maintainability index; | ||
* dependencies (CommonJS and AMD); | ||
* first-order density. | ||
* Lines of code: | ||
Both physical (the number of lines in a module or function) | ||
and logical (a count of the imperative statements). | ||
A crude measure. | ||
* Number of parameters: | ||
Analysed statically | ||
from the function signature, | ||
so no accounting is made | ||
for functions that rely on the `arguments` object. | ||
Lower is better. | ||
* Cyclomatic complexity: | ||
Defined by Thomas J. McCabe in 1976, | ||
this is a count of the number of cycles | ||
in the program flow control graph. | ||
Effectively the number of distinct paths | ||
through a block of code. | ||
Lower is better. | ||
* Cyclomatic complexity 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. | ||
* Halstead metrics: | ||
Defined by Maurice Halstead in 1977, | ||
these metrics are calculated | ||
from the numbers of operators | ||
and operands in each function. | ||
Lower is better. | ||
* Maintainability index: | ||
Defined by Paul Oman & Jack Hagemeister in 1991, | ||
this is a logarithmic scale | ||
from negative infinity to 171, | ||
calculated from | ||
the logical lines of code, | ||
the cyclomatix complexity | ||
and the Halstead effort. | ||
Higher is better. | ||
* Dependencies: | ||
A count of the calls | ||
to CommonJS and AMD `require`. | ||
Analysed statically | ||
from the function signature, | ||
so no accounting is made | ||
for dynamic calls | ||
where a variable or function is | ||
obscuring the nature of the dependency. | ||
Lower is better. | ||
* First-order density: | ||
The percentage of all possible internal dependencies | ||
that are actually realised in the project. | ||
Lower is better. | ||
* Change cost: | ||
The percentage of modules affected, | ||
on average, | ||
when one module in the project | ||
is changed. | ||
Lower is better. | ||
* Core size: | ||
the percentage of modules | ||
that are both widely depended on | ||
and themselves depend on other modules. | ||
Lower is better. | ||
## Abstract syntax trees | ||
It is important to note | ||
that none of these metrics | ||
can compete with the insight | ||
of a competent developer. | ||
At best, | ||
they are an automatable warning system, | ||
which can help to identify areas of code | ||
that warrant closer inspection. | ||
* [Esprima][esprima]; | ||
* [Acorn][acorn]; | ||
* [CoffeeScriptRedux][coffee]; | ||
* [LiveScript][live]; | ||
## Links to research | ||
## Abstract syntax tree walkers | ||
* [A Complexity Measure][mccabe], | ||
by Thomas J McCabe. | ||
* [Cyclomatic Complexity Density and Software Maintenance Productivity][gillkemerer], | ||
by Geoffrey K. Gill and Chris F. Kemerer. | ||
* [Resolving the Mysteries of the Halstead Measures][horstzuse], | ||
by Horst Zuse. | ||
* [Exploring the Structure of Complex Software Designs: An Empirical Study of Open Source and Proprietary Code][dsm], | ||
by Alan MacCormack, John Rusnak and Carliss Baldwin. | ||
* [The Impact of Software Design Structure on Product Maintenance Costs and Measurement of Economic Benefits of Product Redesign][akaikine], | ||
by Andrei Akaikine. | ||
* [A Systematic Review of Software Maintainability Prediction and Metrics][review], | ||
by Mehwish Riaz, Emilia Mendes and Ewan Tempero. | ||
@@ -68,3 +165,3 @@ ## Installation | ||
```javascript | ||
var result = escomplex.analyse(ast, options); | ||
var result = escomplex.analyse(ast, walker, options); | ||
``` | ||
@@ -75,8 +172,21 @@ | ||
an abstract syntax tree | ||
as defined by Mozilla's Parser API | ||
or an array of said syntax trees. | ||
or an array of syntax trees. | ||
If it is an array, | ||
each tree should include | ||
an extra property, `path`, | ||
that is either a relative | ||
or full path to equivalent module | ||
on disk. | ||
As well as identifying | ||
each of the result objects, | ||
that path is also used | ||
during dependency analysis. | ||
The second argument, `options`, | ||
The second argument, `walker`, | ||
must be a [syntax tree walker](#syntax-tree-walkers). | ||
The third argument, `options`, | ||
is an optional object | ||
containing properties that modify some of the complexity calculations: | ||
containing properties that modify | ||
some of the complexity calculations: | ||
@@ -107,17 +217,127 @@ * `options.logicalor`: | ||
the result will be a report object | ||
detailing the complexity of that syntax tree. | ||
If `ast` is an array, | ||
the result will be an array of complexity reports. | ||
containing the following properties: | ||
TODO: Properties on the returned object | ||
* `report.maintainability`: | ||
The maintainability index for the module. | ||
* `report.dependencies`: | ||
The array of CommonJS/AMD dependencies for the module. | ||
* `report.aggregate.sloc.physical`: | ||
Physical lines of code for the module. | ||
Will be `undefined` | ||
if the syntax tree | ||
is not annotated | ||
with line number data. | ||
* `report.aggregate.sloc.logical`: | ||
Logical lines of code for the module. | ||
* `report.aggregate.params`: | ||
Parameter count for the module. | ||
* `report.aggregate.cyclomatic`: | ||
Cyclomatic complexity for the module. | ||
* `report.aggregate.cyclomaticDensity`: | ||
Cyclomatic complexity density for the module. | ||
* `report.aggregate.halstead.vocabulary`: | ||
Halstead vocabulary size for the module. | ||
* `report.aggregate.halstead.difficulty`: | ||
Halstead difficulty for the module. | ||
* `report.aggregate.halstead.volume`: | ||
Halstead volume for the module. | ||
* `report.aggregate.halstead.effort`: | ||
Halstead effort for the module. | ||
* `report.aggregate.halstead.bugs`: | ||
Halstead bugs for the module. | ||
* `report.aggregate.halstead.time`: | ||
Halstead time for the module. | ||
* `report.functions[n].name`: | ||
Function name. | ||
* `report.functions[n].line`: | ||
Line number that the function starts on. | ||
Will be `undefined` | ||
if the syntax tree | ||
is not annotated | ||
with line number data. | ||
* `report.functions[n].sloc.physical`: | ||
Physical lines of code for the function. | ||
Will be `undefined` | ||
if the syntax tree | ||
is not annotated | ||
with line number data. | ||
* `report.functions[n].sloc.logical`: | ||
Logical lines of code for the function. | ||
* `report.functions[n].params`: | ||
Parameter count for the function. | ||
* `report.functions[n].cyclomatic`: | ||
Cyclomatic complexity for the function. | ||
* `report.functions[n].cyclomaticDensity`: | ||
Cyclomatic complexity density for the function. | ||
* `report.functions[n].halstead.vocabulary`: | ||
Halstead vocabulary size for the function. | ||
* `report.functions[n].halstead.difficulty`: | ||
Halstead difficulty for the function. | ||
* `report.functions[n].halstead.volume`: | ||
Halstead volume for the function. | ||
* `report.functions[n].halstead.effort`: | ||
Halstead effort for the function. | ||
* `report.functions[n].halstead.bugs`: | ||
Halstead bugs for the function. | ||
* `report.functions[n].halstead.time`: | ||
Halstead time for the function. | ||
## Related projects | ||
If an array of syntax trees | ||
is passed in the `ast` argument, | ||
the result will be an object | ||
containing the following properties: | ||
TODO | ||
* `result.reports`: | ||
An array of report objects, | ||
each one in the same format described above | ||
but with an extra property `path` | ||
that matches the `path` property | ||
from its corresponding syntax tree. | ||
This `path` property is required | ||
because the reports array gets sorted | ||
during dependency analysis. | ||
* `result.adjacencyMatrix`: | ||
The adjacency | ||
design structure matrix (DSM) | ||
for the project. | ||
This is a two-dimensional array, | ||
each dimension with the same order and length | ||
as the `reports` array. | ||
Each row and column | ||
represents its equivalent | ||
indexed module | ||
from the `reports` array, | ||
with values along the horizontal | ||
being `1` | ||
when that module | ||
directly depends on another | ||
and values along the vertical | ||
being `1` | ||
when that module | ||
is directly depended on by another. | ||
All other values are `0`. | ||
* `result.firstOrderDensity`: | ||
The first-order density for the project. | ||
* `result.visibilityMatrix`: | ||
The visibility DSM for the project. | ||
Like the adjacency matrix, | ||
but expanded to incorporate | ||
indirect dependencies. | ||
* `result.changeCost`: | ||
The change cost for the project. | ||
* `result.coreSize`: | ||
The core size for the project. | ||
## Development | ||
TODO | ||
Source code is in `/src`. | ||
Unit tests are in `/test`. | ||
You can run them with `npm test`. | ||
You can run the linter with `npm run lint`. | ||
Make sure you've installed | ||
all the dependencies | ||
with `npm install` | ||
first. | ||
## What license is it released under? | ||
## License | ||
@@ -128,15 +348,16 @@ [MIT][license] | ||
[ci-status]: http://travis-ci.org/#!/philbooth/escomplex | ||
[complexity-report]: https://github.com/philbooth/complexity-report | ||
[escomplex-ast-moz]: https://github.com/philbooth/escomplex-ast-moz | ||
[api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API | ||
[esprima]: http://esprima.org/ | ||
[acorn]: http://marijnhaverbeke.nl/acorn | ||
[coffee]: https://github.com/michaelficarra/CoffeeScriptRedux | ||
[live]: https://github.com/gkz/LiveScript | ||
[escomplex-ast-csr]: https://github.com/philbooth/escomplex-ast-csr | ||
[coffeescriptredux]: https://github.com/michaelficarra/CoffeeScriptRedux | ||
[mccabe]: http://www.literateprogramming.com/mccabe.pdf | ||
[gillkemerer]: http://www.pitt.edu/~ckemerer/CK%20research%20papers/CyclomaticComplexityDensity_GillKemerer91.pdf | ||
[horstzuse]: http://horst-zuse.homepage.t-online.de/z-halstead-final-05-1.pdf | ||
[dsm]: http://www.people.hbs.edu/cbaldwin/DR2/MRBDesignStructure17thSep1.pdf | ||
[akaikine]: http://sdm.mit.edu/docs/akaikine_thesis.pdf | ||
[review]: http://www.rose-hulman.edu/Users/faculty/young/CS-Classes/csse575/Resources/maintainabilityMeas05314233.pdf | ||
[license]: https://github.com/philbooth/escomplex/blob/master/COPYING | ||
[msvariant]: http://blogs.msdn.com/b/codeanalysis/archive/2007/11/20/maintainability-index-range-and-meaning.aspx | ||
[jarrod]: http://jarrodoverson.com/blog/about | ||
[plato]: https://github.com/jsoverson/plato | ||
[grunt-complexity]: https://github.com/vigetlabs/grunt-complexity | ||
[bob]: https://github.com/cliffano/bob | ||
[cardio]: https://github.com/auchenberg/cardio | ||
[brackets-crjs]: https://github.com/sahlas/brackets-crjs | ||
[node]: http://nodejs.org/ | ||
@@ -143,0 +364,0 @@ [npm]: https://npmjs.org/ |
@@ -39,3 +39,3 @@ /*globals exports, require */ | ||
processLloc(node, syntax, currentReport); | ||
processComplexity(node, syntax, currentReport); | ||
processCyclomatic(node, syntax, currentReport); | ||
processOperators(node, syntax, currentReport); | ||
@@ -55,3 +55,3 @@ processOperands(node, syntax, currentReport); | ||
report.functions.push(currentReport); | ||
report.aggregate.complexity.params += parameterCount; | ||
report.aggregate.params += parameterCount; | ||
@@ -65,3 +65,3 @@ scopeStack.push(currentReport); | ||
if (scopeStack.length > 0) { | ||
currentReport = scopeStack[scopeStack.length - 1] | ||
currentReport = scopeStack[scopeStack.length - 1]; | ||
} else { | ||
@@ -94,10 +94,8 @@ currentReport = undefined; | ||
name: name, | ||
complexity: { | ||
sloc: { | ||
logical: 0 | ||
}, | ||
cyclomatic: 1, | ||
halstead: createInitialHalsteadState(), | ||
params: params | ||
} | ||
sloc: { | ||
logical: 0 | ||
}, | ||
cyclomatic: 1, | ||
halstead: createInitialHalsteadState(), | ||
params: params | ||
}; | ||
@@ -107,3 +105,3 @@ | ||
result.line = lines.start.line; | ||
result.complexity.sloc.physical = lines.end.line - lines.start.line + 1; | ||
result.sloc.physical = lines.end.line - lines.start.line + 1; | ||
} | ||
@@ -144,18 +142,18 @@ | ||
function incrementLogicalSloc (currentReport, amount) { | ||
report.aggregate.complexity.sloc.logical += amount; | ||
report.aggregate.sloc.logical += amount; | ||
if (currentReport) { | ||
currentReport.complexity.sloc.logical += amount; | ||
currentReport.sloc.logical += amount; | ||
} | ||
} | ||
function processComplexity (node, syntax, currentReport) { | ||
incrementCounter(node, syntax, 'complexity', incrementComplexity, currentReport); | ||
function processCyclomatic (node, syntax, currentReport) { | ||
incrementCounter(node, syntax, 'cyclomatic', incrementCyclomatic, currentReport); | ||
} | ||
function incrementComplexity (currentReport, amount) { | ||
report.aggregate.complexity.cyclomatic += amount; | ||
function incrementCyclomatic (currentReport, amount) { | ||
report.aggregate.cyclomatic += amount; | ||
if (currentReport) { | ||
currentReport.complexity.cyclomatic += amount; | ||
currentReport.cyclomatic += amount; | ||
} | ||
@@ -214,7 +212,7 @@ } | ||
function isHalsteadMetricDistinct (baseReport, metric, identifier) { | ||
return baseReport.complexity.halstead[metric].identifiers.indexOf(identifier) === -1; | ||
return baseReport.halstead[metric].identifiers.indexOf(identifier) === -1; | ||
} | ||
function recordDistinctHalsteadMetric (baseReport, metric, identifier) { | ||
baseReport.complexity.halstead[metric].identifiers.push(identifier); | ||
baseReport.halstead[metric].identifiers.push(identifier); | ||
} | ||
@@ -224,3 +222,3 @@ | ||
if (baseReport) { | ||
baseReport.complexity.halstead[metric][type] += 1; | ||
baseReport.halstead[metric][type] += 1; | ||
} | ||
@@ -255,3 +253,3 @@ } | ||
loc: 0, | ||
complexity: 1, | ||
cyclomatic: 1, | ||
effort: 2, | ||
@@ -262,4 +260,5 @@ params: 3 | ||
for (i = 0; i < report.functions.length; i += 1) { | ||
data = report.functions[i].complexity; | ||
data = report.functions[i]; | ||
calculateCyclomaticDensity(data); | ||
calculateHalsteadMetrics(data.halstead); | ||
@@ -269,6 +268,7 @@ sumMaintainabilityMetrics(sums, indices, data); | ||
calculateHalsteadMetrics(report.aggregate.complexity.halstead); | ||
calculateCyclomaticDensity(report.aggregate); | ||
calculateHalsteadMetrics(report.aggregate.halstead); | ||
if (i === 0) { | ||
// Sane handling of modules that contain no functions. | ||
sumMaintainabilityMetrics(sums, indices, report.aggregate.complexity); | ||
sumMaintainabilityMetrics(sums, indices, report.aggregate); | ||
i = 1; | ||
@@ -281,3 +281,3 @@ } | ||
averages[indices.effort], | ||
averages[indices.complexity], | ||
averages[indices.cyclomatic], | ||
averages[indices.loc], | ||
@@ -290,2 +290,6 @@ settings | ||
function calculateCyclomaticDensity (data) { | ||
data.cyclomaticDensity = (data.cyclomatic / data.sloc.logical) * 100; | ||
} | ||
function calculateHalsteadMetrics (data) { | ||
@@ -319,3 +323,3 @@ data.length = data.operators.total + data.operands.total; | ||
sums[indices.loc] += data.sloc.logical; | ||
sums[indices.complexity] += data.cyclomatic; | ||
sums[indices.cyclomatic] += data.cyclomatic; | ||
sums[indices.effort] += data.halstead.effort; | ||
@@ -325,4 +329,4 @@ sums[indices.params] += data.params; | ||
function calculateMaintainabilityIndex (averageEffort, averageComplexity, averageLoc, settings) { | ||
if (averageComplexity === 0) { | ||
function calculateMaintainabilityIndex (averageEffort, averageCyclomatic, averageLoc, settings) { | ||
if (averageCyclomatic === 0) { | ||
throw new Error('Encountered function with cyclomatic complexity zero!'); | ||
@@ -337,3 +341,3 @@ } | ||
(3.42 * Math.log(averageEffort)) - | ||
(0.23 * Math.log(averageComplexity)) - | ||
(0.23 * Math.log(averageCyclomatic)) - | ||
(16.2 * Math.log(averageLoc)); | ||
@@ -343,5 +347,5 @@ } | ||
if (settings.newmi) { | ||
report.maintainability = Math.max(0, (report.maintainability*100)/171); | ||
report.maintainability = Math.max(0, (report.maintainability * 100) / 171); | ||
} | ||
} | ||
@@ -5,3 +5,3 @@ /*globals exports, require */ | ||
var path, check, moduleAnalyser; | ||
var path, check, matrix, moduleAnalyser; | ||
@@ -12,2 +12,3 @@ exports.analyse = analyse; | ||
check = require('check-types'); | ||
matrix = require('matrix-utilities'); | ||
moduleAnalyser = require('./module'); | ||
@@ -18,3 +19,3 @@ | ||
var reports; | ||
var reports, result; | ||
@@ -34,19 +35,23 @@ check.verifyArray(modules, 'Invalid modules'); | ||
return { | ||
result = { | ||
reports: reports, | ||
matrices: [ createAdjacencyMatrix(reports) ] | ||
}; | ||
createAdjacencyMatrix(result); | ||
createVisibilityMatrix(result); | ||
setCoreSize(result); | ||
return result; | ||
} | ||
// TODO: Move this dependency stuff into a separate module | ||
function createAdjacencyMatrix (reports) { | ||
var matrix = new Array(reports.length), density = 0; | ||
function createAdjacencyMatrix (result) { | ||
var adjacencyMatrix = new Array(result.reports.length), density = 0; | ||
reports.sort(function (lhs, rhs) { | ||
result.reports.sort(function (lhs, rhs) { | ||
return comparePaths(lhs.path, rhs.path); | ||
}).forEach(function (ignore, x) { | ||
matrix[x] = new Array(reports.length); | ||
reports.forEach(function (ignore, y) { | ||
matrix[x][y] = getAdjacencyMatrixValue(reports, x, y); | ||
if (matrix[x][y] === 1) { | ||
adjacencyMatrix[x] = new Array(result.reports.length); | ||
result.reports.forEach(function (ignore, y) { | ||
adjacencyMatrix[x][y] = getAdjacencyMatrixValue(result.reports, x, y); | ||
if (adjacencyMatrix[x][y] === 1) { | ||
density += 1; | ||
@@ -57,6 +62,4 @@ } | ||
return { | ||
matrix: matrix, | ||
density: density | ||
}; | ||
result.adjacencyMatrix = adjacencyMatrix; | ||
result.firstOrderDensity = percentifyDensity(density, adjacencyMatrix); | ||
} | ||
@@ -80,3 +83,3 @@ | ||
if (x === y) { | ||
return null; | ||
return 0; | ||
} | ||
@@ -138,1 +141,104 @@ | ||
function percentifyDensity (density, matrix) { | ||
return percentify(density, matrix.length * matrix.length); | ||
} | ||
function percentify (value, limit) { | ||
if (limit === 0) { | ||
return 0; | ||
} | ||
return (value / limit) * 100; | ||
} | ||
function createVisibilityMatrix (result) { | ||
var product = result.adjacencyMatrix, sum = result.adjacencyMatrix, changeCost = 0, visibilityMatrix; | ||
result.adjacencyMatrix.forEach(function () { | ||
product = matrix.multiply(product, result.adjacencyMatrix); | ||
sum = matrix.add(product, sum); | ||
}); | ||
result.adjacencyMatrix.forEach(function (ignore, index) { | ||
sum[index][index] = 1; | ||
}); | ||
visibilityMatrix = sum.map(function (row, rowIndex) { | ||
return row.map(function (value, columnIndex) { | ||
if (value > 0) { | ||
changeCost += 1; | ||
if (columnIndex !== rowIndex) { | ||
return 1; | ||
} | ||
} | ||
return 0; | ||
}); | ||
}); | ||
result.visibilityMatrix = visibilityMatrix; | ||
result.changeCost = percentifyDensity(changeCost, visibilityMatrix); | ||
} | ||
function setCoreSize (result) { | ||
var fanIn, fanOut, boundaries, coreSize; | ||
if (result.firstOrderDensity === 0) { | ||
result.coreSize = 0; | ||
return; | ||
} | ||
fanIn = new Array(result.visibilityMatrix.length); | ||
fanOut = new Array(result.visibilityMatrix.length); | ||
boundaries = {}; | ||
coreSize = 0; | ||
result.visibilityMatrix.forEach(function (row, rowIndex) { | ||
fanIn[rowIndex] = row.reduce(function (sum, value, valueIndex) { | ||
if (rowIndex === 0) { | ||
fanOut[valueIndex] = value; | ||
} else { | ||
fanOut[valueIndex] += value; | ||
} | ||
return sum + value; | ||
}, 0); | ||
}); | ||
// Boundary values can also be chosen by looking for discontinuity in the | ||
// distribution of values, but I've chosen the median to keep it simple. | ||
boundaries.fanIn = getMedian(fanIn.slice()); | ||
boundaries.fanOut = getMedian(fanOut.slice()); | ||
result.visibilityMatrix.forEach(function (ignore, index) { | ||
if (fanIn[index] >= boundaries.fanIn && fanOut[index] >= boundaries.fanOut) { | ||
coreSize += 1; | ||
} | ||
}); | ||
result.coreSize = percentify(coreSize, result.visibilityMatrix.length); | ||
} | ||
function getMedian (values) { | ||
values.sort(compareNumbers); | ||
if (check.isOddNumber(values.length)) { | ||
return values[(values.length - 1) / 2]; | ||
} | ||
return (values[(values.length - 2) / 2] + values[values.length / 2]) / 2; | ||
} | ||
function compareNumbers (lhs, rhs) { | ||
if (lhs < rhs) { | ||
return -1; | ||
} | ||
if (lhs > rhs) { | ||
return 1; | ||
} | ||
return 0; | ||
} | ||
@@ -84,20 +84,20 @@ /*globals require, suite, test, setup, teardown */ | ||
test('matrices array exists', function () { | ||
assert.isArray(result.matrices); | ||
test('adjacency matrix exists', function () { | ||
assert.isArray(result.adjacencyMatrix); | ||
}); | ||
test('matrices array has one item', function () { | ||
assert.lengthOf(result.matrices, 1); | ||
test('adjacency matrix has zero length', function () { | ||
assert.lengthOf(result.adjacencyMatrix, 0); | ||
}); | ||
test('adjacency matrix exists', function () { | ||
assert.isArray(result.matrices[0].matrix); | ||
test('first-order density is correct', function () { | ||
assert.strictEqual(result.firstOrderDensity, 0); | ||
}); | ||
test('adjacency matrix has zero length', function () { | ||
assert.lengthOf(result.matrices[0].matrix, 0); | ||
test('change cost is correct', function () { | ||
assert.strictEqual(result.changeCost, 0); | ||
}); | ||
test('first-order density is correct', function () { | ||
assert.strictEqual(result.matrices[0].density, 0); | ||
test('core size is correct', function () { | ||
assert.strictEqual(result.coreSize, 0); | ||
}); | ||
@@ -125,13 +125,17 @@ }); | ||
test('first report aggregate has correct physical lines of code', function () { | ||
assert.strictEqual(result.reports[0].aggregate.complexity.sloc.physical, 1); | ||
assert.strictEqual(result.reports[0].aggregate.sloc.physical, 1); | ||
}); | ||
test('first report aggregate has correct logical lines of code', function () { | ||
assert.strictEqual(result.reports[0].aggregate.complexity.sloc.logical, 4); | ||
assert.strictEqual(result.reports[0].aggregate.sloc.logical, 4); | ||
}); | ||
test('first report aggregate has correct cyclomatic complexity', function () { | ||
assert.strictEqual(result.reports[0].aggregate.complexity.cyclomatic, 2); | ||
assert.strictEqual(result.reports[0].aggregate.cyclomatic, 2); | ||
}); | ||
test('first report aggregate has correct cyclomatic complexity density', function () { | ||
assert.strictEqual(result.reports[0].aggregate.cyclomaticDensity, 50); | ||
}); | ||
test('first report functions is empty', function () { | ||
@@ -142,15 +146,15 @@ assert.lengthOf(result.reports[0].functions, 0); | ||
test('first report aggregate has correct Halstead total operators', function () { | ||
assert.strictEqual(result.reports[0].aggregate.complexity.halstead.operators.total, 2); | ||
assert.strictEqual(result.reports[0].aggregate.halstead.operators.total, 2); | ||
}); | ||
test('first report aggregate has correct Halstead distinct operators', function () { | ||
assert.strictEqual(result.reports[0].aggregate.complexity.halstead.operators.distinct, 2); | ||
assert.strictEqual(result.reports[0].aggregate.halstead.operators.distinct, 2); | ||
}); | ||
test('first report aggregate has correct Halstead total operands', function () { | ||
assert.strictEqual(result.reports[0].aggregate.complexity.halstead.operands.total, 3); | ||
assert.strictEqual(result.reports[0].aggregate.halstead.operands.total, 3); | ||
}); | ||
test('first report aggregate has correct Halstead distinct operands', function () { | ||
assert.strictEqual(result.reports[0].aggregate.complexity.halstead.operands.distinct, 3); | ||
assert.strictEqual(result.reports[0].aggregate.halstead.operands.distinct, 3); | ||
}); | ||
@@ -160,4 +164,4 @@ | ||
assert.lengthOf( | ||
result.reports[0].aggregate.complexity.halstead.operators.identifiers, | ||
result.reports[0].aggregate.complexity.halstead.operators.distinct | ||
result.reports[0].aggregate.halstead.operators.identifiers, | ||
result.reports[0].aggregate.halstead.operators.distinct | ||
); | ||
@@ -168,4 +172,4 @@ }); | ||
assert.lengthOf( | ||
result.reports[0].aggregate.complexity.halstead.operands.identifiers, | ||
result.reports[0].aggregate.complexity.halstead.operands.distinct | ||
result.reports[0].aggregate.halstead.operands.identifiers, | ||
result.reports[0].aggregate.halstead.operands.distinct | ||
); | ||
@@ -175,27 +179,27 @@ }); | ||
test('first report aggregate has correct Halstead length', function () { | ||
assert.strictEqual(result.reports[0].aggregate.complexity.halstead.length, 5); | ||
assert.strictEqual(result.reports[0].aggregate.halstead.length, 5); | ||
}); | ||
test('first report aggregate has correct Halstead vocabulary', function () { | ||
assert.strictEqual(result.reports[0].aggregate.complexity.halstead.vocabulary, 5); | ||
assert.strictEqual(result.reports[0].aggregate.halstead.vocabulary, 5); | ||
}); | ||
test('first report aggregate has correct Halstead difficulty', function () { | ||
assert.strictEqual(result.reports[0].aggregate.complexity.halstead.difficulty, 1); | ||
assert.strictEqual(result.reports[0].aggregate.halstead.difficulty, 1); | ||
}); | ||
test('first report aggregate has correct Halstead volume', function () { | ||
assert.strictEqual(Math.round(result.reports[0].aggregate.complexity.halstead.volume), 12); | ||
assert.strictEqual(Math.round(result.reports[0].aggregate.halstead.volume), 12); | ||
}); | ||
test('first report aggregate has correct Halstead effort', function () { | ||
assert.strictEqual(Math.round(result.reports[0].aggregate.complexity.halstead.effort), 12); | ||
assert.strictEqual(Math.round(result.reports[0].aggregate.halstead.effort), 12); | ||
}); | ||
test('first report aggregate has correct Halstead bugs', function () { | ||
assert.strictEqual(Math.round(result.reports[0].aggregate.complexity.halstead.bugs), 0); | ||
assert.strictEqual(Math.round(result.reports[0].aggregate.halstead.bugs), 0); | ||
}); | ||
test('first report aggregate has correct Halstead time', function () { | ||
assert.strictEqual(Math.round(result.reports[0].aggregate.complexity.halstead.time), 1); | ||
assert.strictEqual(Math.round(result.reports[0].aggregate.halstead.time), 1); | ||
}); | ||
@@ -212,11 +216,11 @@ | ||
test('second report first function has correct parameter count', function () { | ||
assert.strictEqual(result.reports[1].functions[0].complexity.params, 2); | ||
assert.strictEqual(result.reports[1].functions[0].params, 2); | ||
}); | ||
test('second report second function has correct parameter count', function () { | ||
assert.strictEqual(result.reports[1].functions[1].complexity.params, 2); | ||
assert.strictEqual(result.reports[1].functions[1].params, 2); | ||
}); | ||
test('second report aggregate has correct parameter count', function () { | ||
assert.strictEqual(result.reports[1].aggregate.complexity.params, 4); | ||
assert.strictEqual(result.reports[1].aggregate.params, 4); | ||
}); | ||
@@ -231,2 +235,14 @@ | ||
}); | ||
test('first-order density is correct', function () { | ||
assert.strictEqual(result.firstOrderDensity, 0); | ||
}); | ||
test('change cost is correct', function () { | ||
assert.strictEqual(result.changeCost, 50); | ||
}); | ||
test('core size is correct', function () { | ||
assert.strictEqual(result.coreSize, 0); | ||
}); | ||
}); | ||
@@ -258,35 +274,138 @@ | ||
test('adjacency matrix is correct', function () { | ||
assert.lengthOf(result.matrices[0].matrix, 4); | ||
assert.lengthOf(result.adjacencyMatrix, 4); | ||
assert.lengthOf(result.matrices[0].matrix[0], 4); | ||
assert.isNull(result.matrices[0].matrix[0][0]); | ||
assert.strictEqual(result.matrices[0].matrix[0][1], 0); | ||
assert.strictEqual(result.matrices[0].matrix[0][2], 1); | ||
assert.strictEqual(result.matrices[0].matrix[0][3], 1); | ||
assert.lengthOf(result.adjacencyMatrix[0], 4); | ||
assert.strictEqual(result.adjacencyMatrix[0][0], 0); | ||
assert.strictEqual(result.adjacencyMatrix[0][1], 0); | ||
assert.strictEqual(result.adjacencyMatrix[0][2], 1); | ||
assert.strictEqual(result.adjacencyMatrix[0][3], 1); | ||
assert.lengthOf(result.matrices[0].matrix[1], 4); | ||
assert.strictEqual(result.matrices[0].matrix[1][0], 1); | ||
assert.isNull(result.matrices[0].matrix[1][1]); | ||
assert.strictEqual(result.matrices[0].matrix[1][2], 0); | ||
assert.strictEqual(result.matrices[0].matrix[1][3], 0); | ||
assert.lengthOf(result.adjacencyMatrix[1], 4); | ||
assert.strictEqual(result.adjacencyMatrix[1][0], 1); | ||
assert.strictEqual(result.adjacencyMatrix[1][1], 0); | ||
assert.strictEqual(result.adjacencyMatrix[1][2], 0); | ||
assert.strictEqual(result.adjacencyMatrix[1][3], 0); | ||
assert.lengthOf(result.matrices[0].matrix[2], 4); | ||
assert.strictEqual(result.matrices[0].matrix[2][0], 0); | ||
assert.strictEqual(result.matrices[0].matrix[2][1], 0); | ||
assert.isNull(result.matrices[0].matrix[2][2]); | ||
assert.strictEqual(result.matrices[0].matrix[2][3], 1); | ||
assert.lengthOf(result.adjacencyMatrix[2], 4); | ||
assert.strictEqual(result.adjacencyMatrix[2][0], 0); | ||
assert.strictEqual(result.adjacencyMatrix[2][1], 0); | ||
assert.strictEqual(result.adjacencyMatrix[2][2], 0); | ||
assert.strictEqual(result.adjacencyMatrix[2][3], 1); | ||
assert.lengthOf(result.matrices[0].matrix[3], 4); | ||
assert.strictEqual(result.matrices[0].matrix[3][0], 0); | ||
assert.strictEqual(result.matrices[0].matrix[3][1], 0); | ||
assert.strictEqual(result.matrices[0].matrix[3][2], 1); | ||
assert.isNull(result.matrices[0].matrix[3][3]); | ||
assert.lengthOf(result.adjacencyMatrix[3], 4); | ||
assert.strictEqual(result.adjacencyMatrix[3][0], 0); | ||
assert.strictEqual(result.adjacencyMatrix[3][1], 0); | ||
assert.strictEqual(result.adjacencyMatrix[3][2], 1); | ||
assert.strictEqual(result.adjacencyMatrix[3][3], 0); | ||
}); | ||
test('first order density is correct', function () { | ||
assert.strictEqual(result.matrices[0].density, 5); | ||
assert.strictEqual(result.firstOrderDensity, 31.25); | ||
}); | ||
test('change cost is correct', function () { | ||
assert.strictEqual(result.changeCost, 68.75); | ||
}); | ||
test('core size is correct', function () { | ||
assert.strictEqual(result.coreSize, 0); | ||
}); | ||
}); | ||
suite('MacCormack, Rusnak & Baldwin example:', function () { | ||
var result; | ||
setup(function () { | ||
result = cr.analyse([ | ||
{ ast: esprima.parse('"f";', { loc: true }), path: '/a/c/f.js' }, | ||
{ ast: esprima.parse('require("./f");"e";', { loc: true }), path: '/a/c/e.js' }, | ||
{ ast: esprima.parse('"d";', { loc: true }), path: '/a/b/d.js' }, | ||
{ ast: esprima.parse('require("./c/e");"c";', { loc: true }), path: '/a/c.js' }, | ||
{ ast: esprima.parse('require("./b/d");"b";', { loc: true }), path: '/a/b.js' }, | ||
{ ast: esprima.parse('require("./a/b");require("./a/c");"a";', { loc: true }), path: '/a.js' } | ||
], mozWalker); | ||
}); | ||
teardown(function () { | ||
result = undefined; | ||
}); | ||
test('reports are in correct order', function () { | ||
assert.strictEqual(result.reports[0].path, '/a.js'); | ||
assert.strictEqual(result.reports[1].path, '/a/b.js'); | ||
assert.strictEqual(result.reports[2].path, '/a/c.js'); | ||
assert.strictEqual(result.reports[3].path, '/a/b/d.js'); | ||
assert.strictEqual(result.reports[4].path, '/a/c/e.js'); | ||
assert.strictEqual(result.reports[5].path, '/a/c/f.js'); | ||
}); | ||
test('adjacency matrix is correct', function () { | ||
assert.lengthOf(result.adjacencyMatrix, 6); | ||
assert.lengthOf(result.adjacencyMatrix[0], 6); | ||
assert.strictEqual(result.adjacencyMatrix[0][0], 0); | ||
assert.strictEqual(result.adjacencyMatrix[0][1], 1); | ||
assert.strictEqual(result.adjacencyMatrix[0][2], 1); | ||
assert.strictEqual(result.adjacencyMatrix[0][3], 0); | ||
assert.strictEqual(result.adjacencyMatrix[0][4], 0); | ||
assert.strictEqual(result.adjacencyMatrix[0][5], 0); | ||
assert.lengthOf(result.adjacencyMatrix[1], 6); | ||
assert.strictEqual(result.adjacencyMatrix[1][0], 0); | ||
assert.strictEqual(result.adjacencyMatrix[1][1], 0); | ||
assert.strictEqual(result.adjacencyMatrix[1][2], 0); | ||
assert.strictEqual(result.adjacencyMatrix[1][3], 1); | ||
assert.strictEqual(result.adjacencyMatrix[1][4], 0); | ||
assert.strictEqual(result.adjacencyMatrix[1][5], 0); | ||
assert.lengthOf(result.adjacencyMatrix[2], 6); | ||
assert.strictEqual(result.adjacencyMatrix[2][0], 0); | ||
assert.strictEqual(result.adjacencyMatrix[2][1], 0); | ||
assert.strictEqual(result.adjacencyMatrix[2][2], 0); | ||
assert.strictEqual(result.adjacencyMatrix[2][3], 0); | ||
assert.strictEqual(result.adjacencyMatrix[2][4], 1); | ||
assert.strictEqual(result.adjacencyMatrix[2][5], 0); | ||
assert.lengthOf(result.adjacencyMatrix[3], 6); | ||
assert.strictEqual(result.adjacencyMatrix[3][0], 0); | ||
assert.strictEqual(result.adjacencyMatrix[3][1], 0); | ||
assert.strictEqual(result.adjacencyMatrix[3][2], 0); | ||
assert.strictEqual(result.adjacencyMatrix[3][3], 0); | ||
assert.strictEqual(result.adjacencyMatrix[3][4], 0); | ||
assert.strictEqual(result.adjacencyMatrix[3][5], 0); | ||
assert.lengthOf(result.adjacencyMatrix[4], 6); | ||
assert.strictEqual(result.adjacencyMatrix[4][0], 0); | ||
assert.strictEqual(result.adjacencyMatrix[4][1], 0); | ||
assert.strictEqual(result.adjacencyMatrix[4][2], 0); | ||
assert.strictEqual(result.adjacencyMatrix[4][3], 0); | ||
assert.strictEqual(result.adjacencyMatrix[4][4], 0); | ||
assert.strictEqual(result.adjacencyMatrix[4][5], 1); | ||
assert.lengthOf(result.adjacencyMatrix[5], 6); | ||
assert.strictEqual(result.adjacencyMatrix[5][0], 0); | ||
assert.strictEqual(result.adjacencyMatrix[5][1], 0); | ||
assert.strictEqual(result.adjacencyMatrix[5][2], 0); | ||
assert.strictEqual(result.adjacencyMatrix[5][3], 0); | ||
assert.strictEqual(result.adjacencyMatrix[5][4], 0); | ||
assert.strictEqual(result.adjacencyMatrix[5][5], 0); | ||
}); | ||
test('first order density is correct', function () { | ||
assert.isTrue(result.firstOrderDensity > 13.88); | ||
assert.isTrue(result.firstOrderDensity < 13.89); | ||
}); | ||
test('change cost is correct', function () { | ||
assert.isTrue(result.changeCost > 41.66); | ||
assert.isTrue(result.changeCost < 41.67); | ||
}); | ||
test('core size is correct', function () { | ||
assert.isTrue(result.coreSize > 16.66); | ||
assert.isTrue(result.coreSize < 16.67); | ||
}); | ||
}); | ||
}); | ||
}); | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
165529
363
2
14
3251
+ Addedmatrix-utilities@1.2.x
+ Addedmatrix-utilities@1.2.4(transitive)