Comparing version 0.1.2 to 0.1.3
{ | ||
"name": "escomplex", | ||
"version": "0.1.2", | ||
"version": "0.1.3", | ||
"description": "Software complexity analysis of Mozilla-format abstract syntax trees.", | ||
@@ -30,3 +30,3 @@ "homepage": "https://github.com/philbooth/escomplex", | ||
"dependencies": { | ||
"check-types": "0.6.x" | ||
"check-types": "0.7.x" | ||
}, | ||
@@ -37,3 +37,5 @@ "devDependencies": { | ||
"chai": "1.8.x", | ||
"esprima": "1.0.x" | ||
"escomplex-ast-moz": "0.1.0", | ||
"esprima": "1.0.x", | ||
"coffee-script-redux": "2.0.x" | ||
}, | ||
@@ -40,0 +42,0 @@ "scripts": { |
@@ -8,3 +8,3 @@ # escomplex | ||
Software complexity analysis | ||
of Mozilla-format abstract syntax trees. | ||
of JavaScript-family abstract syntax trees. | ||
@@ -33,23 +33,2 @@ * [Metrics][#metrics] | ||
Mozilla's [Parser API][api] | ||
has become a de-facto standard | ||
for the in-memory data representation | ||
of parsed JavaScript programs. | ||
It defines an abstract syntax tree format | ||
composed of objects that publish their type information, | ||
allowing consuming programs to easily navigate those trees | ||
using generic logic. | ||
By accepting a syntax tree | ||
in such a widely supported format, | ||
escomplex is decoupled from | ||
a specific input language. | ||
Any language | ||
that compiles to JavaScript | ||
and has a conforming parser | ||
can be the subject of | ||
complexity analysis by this library. | ||
Some examples of conforming parsers are: | ||
* [Esprima][esprima]; | ||
@@ -60,2 +39,4 @@ * [Acorn][acorn]; | ||
## Abstract syntax tree walkers | ||
## Installation | ||
@@ -62,0 +43,0 @@ |
@@ -20,12 +20,13 @@ /** | ||
* code complexity. | ||
* @param walker {object} The AST walker to use against `ast`. | ||
* @param [options] {object} Options to modify the complexity calculation. | ||
* | ||
*/ | ||
function analyse (ast, options) { | ||
function analyse (ast, walker, options) { | ||
if (check.isArray(ast)) { | ||
return require('./project').analyse(ast, options); | ||
return require('./project').analyse(ast, walker, options); | ||
} | ||
return require('./module').analyse(ast, options); | ||
return require('./module').analyse(ast, walker, options); | ||
} | ||
@@ -5,16 +5,14 @@ /*globals exports, require */ | ||
var check, syntaxDefinitions, safeName, syntaxes, report, clearDependencies; | ||
var check = require('check-types'), report; | ||
exports.analyse = analyse; | ||
check = require('check-types'); | ||
safeName = require('./safeName'); | ||
syntaxDefinitions = require('./syntax'); | ||
function analyse (ast, walker, options) { | ||
// TODO: Asynchronise | ||
function analyse (ast, options) { | ||
// TODO: Asynchronize. | ||
var settings, currentReport, clearDependencies = true, scopeStack = []; | ||
var settings; | ||
check.verifyObject(ast, 'Invalid syntax tree'); | ||
check.verifyObject(walker, 'Invalid walker'); | ||
check.verifyFunction(walker.walk, 'Invalid walker.walk method'); | ||
@@ -27,7 +25,10 @@ if (check.isObject(options)) { | ||
syntaxes = syntaxDefinitions.get(settings); | ||
// TODO: loc is moz-specific, move to walker? | ||
report = createReport(ast.loc); | ||
clearDependencies = true; | ||
processTree(ast.body, undefined, undefined); | ||
walker.walk(ast, settings, { | ||
processNode: processNode, | ||
createScope: createScope, | ||
popScope: popScope | ||
}); | ||
@@ -37,2 +38,34 @@ calculateMetrics(settings); | ||
return report; | ||
function processNode (node, syntax) { | ||
processLloc(node, syntax, currentReport); | ||
processComplexity(node, syntax, currentReport); | ||
processOperators(node, syntax, currentReport); | ||
processOperands(node, syntax, currentReport); | ||
if (processDependencies(node, syntax, clearDependencies)) { | ||
// HACK: This will fail with async or if other syntax than CallExpression introduces dependencies. | ||
// TODO: Come up with a less crude approach. | ||
clearDependencies = false; | ||
} | ||
} | ||
function createScope (name, loc, parameterCount) { | ||
currentReport = createFunctionReport(name, loc, parameterCount); | ||
report.functions.push(currentReport); | ||
report.aggregate.complexity.params += parameterCount; | ||
scopeStack.push(currentReport); | ||
} | ||
function popScope () { | ||
scopeStack.pop(); | ||
if (scopeStack.length > 0) { | ||
currentReport = scopeStack[scopeStack.length - 1] | ||
} else { | ||
currentReport = undefined; | ||
} | ||
} | ||
} | ||
@@ -62,2 +95,5 @@ | ||
complexity: { | ||
sloc: { | ||
logical: 0 | ||
}, | ||
cyclomatic: 1, | ||
@@ -71,6 +107,3 @@ halstead: createInitialHalsteadState(), | ||
result.line = lines.start.line; | ||
result.complexity.sloc = { | ||
physical: lines.end.line - lines.start.line + 1, | ||
logical: 0 | ||
}; | ||
result.complexity.sloc.physical = lines.end.line - lines.start.line + 1; | ||
} | ||
@@ -96,39 +129,9 @@ | ||
function processTree (tree, assignedName, currentReport) { | ||
check.verifyArray(tree, 'Invalid syntax tree'); | ||
tree.forEach(function (node) { | ||
processNode(node, assignedName, currentReport); | ||
}); | ||
function processLloc (node, syntax, currentReport) { | ||
incrementCounter(node, syntax, 'lloc', incrementLogicalSloc, currentReport); | ||
} | ||
function processNode (node, assignedName, currentReport) { | ||
var syntax; | ||
function incrementCounter (node, syntax, name, incrementFn, currentReport) { | ||
var amount = syntax[name]; | ||
if (check.isObject(node)) { | ||
syntax = syntaxes[node.type]; | ||
if (check.isObject(syntax)) { | ||
processLloc(node, currentReport); | ||
processComplexity(node, currentReport); | ||
processOperators(node, currentReport); | ||
processOperands(node, currentReport); | ||
processDependencies(node); | ||
if (syntax.newScope) { | ||
processChildrenInNewScope(node, assignedName); | ||
} else { | ||
processChildren(node, currentReport); | ||
} | ||
} | ||
} | ||
} | ||
function processLloc (node, currentReport) { | ||
incrementCounter(node, 'lloc', incrementLogicalSloc, currentReport); | ||
} | ||
function incrementCounter (node, name, incrementFn, currentReport) { | ||
var amount = syntaxes[node.type][name]; | ||
if (check.isNumber(amount)) { | ||
@@ -142,13 +145,11 @@ incrementFn(currentReport, amount); | ||
function incrementLogicalSloc (currentReport, amount) { | ||
if (check.isObject(report.aggregate.complexity.sloc)) { | ||
report.aggregate.complexity.sloc.logical += amount; | ||
report.aggregate.complexity.sloc.logical += amount; | ||
if (currentReport) { | ||
currentReport.complexity.sloc.logical += amount; | ||
} | ||
if (currentReport) { | ||
currentReport.complexity.sloc.logical += amount; | ||
} | ||
} | ||
function processComplexity (node, currentReport) { | ||
incrementCounter(node, 'complexity', incrementComplexity, currentReport); | ||
function processComplexity (node, syntax, currentReport) { | ||
incrementCounter(node, syntax, 'complexity', incrementComplexity, currentReport); | ||
} | ||
@@ -164,13 +165,11 @@ | ||
function processOperators (node, currentReport) { | ||
processHalsteadMetric(node, 'operators', currentReport); | ||
function processOperators (node, syntax, currentReport) { | ||
processHalsteadMetric(node, syntax, 'operators', currentReport); | ||
} | ||
function processOperands (node, currentReport) { | ||
processHalsteadMetric(node, 'operands', currentReport); | ||
function processOperands (node, syntax, currentReport) { | ||
processHalsteadMetric(node, syntax, 'operands', currentReport); | ||
} | ||
function processHalsteadMetric (node, metric, currentReport) { | ||
var syntax = syntaxes[node.type]; | ||
function processHalsteadMetric (node, syntax, metric, currentReport) { | ||
if (check.isArray(syntax[metric])) { | ||
@@ -234,4 +233,4 @@ syntax[metric].forEach(function (s) { | ||
function processDependencies (node) { | ||
var syntax = syntaxes[node.type], dependencies; | ||
function processDependencies (node, syntax, clearDependencies) { | ||
var dependencies; | ||
@@ -244,36 +243,8 @@ if (check.isFunction(syntax.dependencies)) { | ||
// HACK: This will fail with async or if other syntax than CallExpression introduces dependencies. | ||
// TODO: Come up with a less crude approach. | ||
clearDependencies = false; | ||
return true; | ||
} | ||
} | ||
function processChildrenInNewScope (node, assignedName) { | ||
var newReport = createFunctionReport(safeName(node.id, assignedName), node.loc, node.params.length); | ||
report.functions.push(newReport); | ||
report.aggregate.complexity.params += node.params.length; | ||
processChildren(node, newReport); | ||
return false; | ||
} | ||
function processChildren (node, currentReport) { | ||
var syntax = syntaxes[node.type]; | ||
if (check.isArray(syntax.children)) { | ||
syntax.children.forEach(function (child) { | ||
processChild( | ||
node[child], | ||
check.isFunction(syntax.assignableName) ? syntax.assignableName(node) : '', | ||
currentReport | ||
); | ||
}); | ||
} | ||
} | ||
function processChild (child, assignedName, currentReport) { | ||
var fn = check.isArray(child) ? processTree : processNode; | ||
fn(child, assignedName, currentReport); | ||
} | ||
function calculateMetrics (settings) { | ||
@@ -344,5 +315,3 @@ var i, data, averages, | ||
function sumMaintainabilityMetrics (sums, indices, data) { | ||
if (check.isObject(data.sloc)) { | ||
sums[indices.loc] += data.sloc.logical; | ||
} | ||
sums[indices.loc] += data.sloc.logical; | ||
sums[indices.complexity] += data.cyclomatic; | ||
@@ -354,7 +323,2 @@ sums[indices.effort] += data.halstead.effort; | ||
function calculateMaintainabilityIndex (averageEffort, averageComplexity, averageLoc, settings) { | ||
if (check.isObject(report.aggregate.complexity.sloc) === false) { | ||
// Can't calculate maintainability index if caller didn't provide SLOC data. | ||
return; | ||
} | ||
if (averageComplexity === 0) { | ||
@@ -361,0 +325,0 @@ throw new Error('Encountered function with cyclomatic complexity zero!'); |
@@ -13,3 +13,3 @@ /*globals exports, require */ | ||
function analyse (modules, options) { | ||
function analyse (modules, walker, options) { | ||
// TODO: Asynchronize. | ||
@@ -26,3 +26,3 @@ | ||
report = moduleAnalyser.analyse(m.ast, options); | ||
report = moduleAnalyser.analyse(m.ast, walker, options); | ||
report.path = m.path; | ||
@@ -29,0 +29,0 @@ |
@@ -5,5 +5,6 @@ /*globals require, suite, test, setup, teardown */ | ||
var assert, mockery, esprima, modulePath, | ||
var assert, mozWalker, esprima, modulePath, | ||
assert = require('chai').assert; | ||
mozWalker = require('escomplex-ast-moz'); | ||
esprima = require('esprima'); | ||
@@ -51,3 +52,3 @@ | ||
} | ||
}); | ||
}, mozWalker); | ||
}); | ||
@@ -58,3 +59,3 @@ }); | ||
assert.doesNotThrow(function () { | ||
cr.analyse([]); | ||
cr.analyse([], mozWalker); | ||
}); | ||
@@ -67,3 +68,3 @@ }); | ||
setup(function () { | ||
result = cr.analyse([]); | ||
result = cr.analyse([], mozWalker); | ||
}); | ||
@@ -115,3 +116,3 @@ | ||
{ ast: esprima.parse('if (true) { "foo"; } else { "bar"; }', { loc: true }), path: 'a' } | ||
]); | ||
], mozWalker); | ||
}); | ||
@@ -239,3 +240,3 @@ | ||
{ ast: esprima.parse('require("./a/b");require("./a/c");"a";', { loc: true }), path: '/a.js' } | ||
]); | ||
], mozWalker); | ||
}); | ||
@@ -242,0 +243,0 @@ |
Sorry, the diff of this file is too big to display
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
6
160474
6
20
3256
142
+ Addedcheck-types@0.7.2(transitive)
- Removedcheck-types@0.6.5(transitive)
Updatedcheck-types@0.7.x