complexity-report
Advanced tools
Comparing version 0.2.7 to 0.2.8
@@ -10,3 +10,2 @@ /*jshint nomen:false */ | ||
commands = { | ||
minify: './node_modules/.bin/uglifyjs --no-copyright --lift-vars --output ./src/complexityReport.min.js ./src/complexityReport.js', | ||
test: './node_modules/.bin/mocha --ui tdd --reporter spec --colors --slow 50 ./test/complexityReport.js', | ||
@@ -17,9 +16,2 @@ lint: './node_modules/.bin/jshint ./src/complexityReport.js --config config/jshint.json', | ||
desc('Minify the source code for deployment.'); | ||
task('minify', [ 'prepare', 'lint', 'test' ], function () { | ||
runTask(minify, 'Minifying...'); | ||
}, { | ||
async: true | ||
}); | ||
desc('Run the unit tests.'); | ||
@@ -51,6 +43,2 @@ task('test', [ 'prepare' ], function () { | ||
function minify () { | ||
runCommand(commands.minify); | ||
} | ||
function test () { | ||
@@ -57,0 +45,0 @@ runCommand(commands.test); |
{ | ||
"name": "complexity-report", | ||
"version": "0.2.7", | ||
"version": "0.2.8", | ||
"author": "Phil Booth <pmbooth@gmail.com>", | ||
@@ -29,10 +29,10 @@ "description": "A tool for reporting code complexity metrics in JavaScript projects.", | ||
"check-types": "0.2.x", | ||
"esprima": "0.9.x", | ||
"esprima": "1.0.x", | ||
"commander": "1.0.x" | ||
}, | ||
"devDependencies": { | ||
"jake": "0.3.x", | ||
"jshint": "0.7.x", | ||
"mocha": "1.2.x", | ||
"chai": "1.1.x" | ||
"jake": "0.5.x", | ||
"jshint": "0.9.x", | ||
"mocha": "1.6.x", | ||
"chai": "1.3.x" | ||
}, | ||
@@ -39,0 +39,0 @@ "license": "MIT", |
@@ -36,8 +36,8 @@ # complexityReport.js | ||
defaults to `stdout`. | ||
* `-f <format`: Specify an output format for the report, | ||
* `-f <format>`: Specify an output format for the report, | ||
defaults to `plain`. | ||
* `-t <threshold>`: Specify the per-function complexity threshold | ||
(beyond which, will cause the process to fail when exiting). | ||
* `-l`: Disregads operator `||` as a source of cyclomatic complexity. | ||
* `-s`: Disegards `switch` statements as a source of cyclomatic complexity. | ||
(beyond which, the process will fail when exiting). | ||
* `-l`: Disregards operator `||` as a source of cyclomatic complexity. | ||
* `-s`: Disregards `switch` statements as a source of cyclomatic complexity. | ||
* `-i`: Treats `for`...`in` loops as a source of cyclomatic complexity. | ||
@@ -44,0 +44,0 @@ * `-c`: Treats `catch` clauses as a source of cyclomatic complexity. |
@@ -10,3 +10,3 @@ /** | ||
var check, esprima, syntaxHandlers, settings, report; | ||
var check, esprima, syntaxDefinitions, report, operators, operands; | ||
@@ -18,24 +18,2 @@ exports.run = run; | ||
syntaxHandlers = { | ||
IfStatement: processCondition, | ||
ConditionalExpression: processCondition, | ||
BlockStatement: processBlock, | ||
LogicalExpression: processLogical, | ||
SwitchStatement: processSwitch, | ||
SwitchCase: processCase, | ||
ForStatement: processLoop, | ||
ForInStatement: processForIn, | ||
WhileStatement: processLoop, | ||
DoWhileStatement: processLoop, | ||
TryStatement: processTry, | ||
CatchClause: processCatch, | ||
FunctionDeclaration: processFunction, | ||
FunctionExpression: processFunction, | ||
VariableDeclaration: processVariables, | ||
VariableDeclarator: processVariable, | ||
ReturnStatement: processReturn, | ||
ExpressionStatement: processExpression, | ||
CallExpression: processCall | ||
}; | ||
/** | ||
@@ -52,3 +30,3 @@ * Public function `run`. | ||
function run (source, options) { | ||
var ast; | ||
var settings, ast; | ||
@@ -63,3 +41,6 @@ check.verifyUnemptyString(source, 'Invalid source'); | ||
syntaxDefinitions = getSyntaxDefinitions(settings); | ||
report = createReport(); | ||
operators = {}; | ||
operands = {}; | ||
@@ -70,3 +51,3 @@ ast = esprima.parse(source, { | ||
processTree(ast.body); | ||
processTree(ast.body, undefined, undefined, {}, {}); | ||
@@ -85,2 +66,163 @@ return report; | ||
function getSyntaxDefinitions (settings) { | ||
// TODO: Refactor to traits. | ||
return { | ||
Literal: { | ||
operands: [ { | ||
name: function (node) { | ||
if (check.isString(node.value)) { | ||
// Avoid conflicts between string literals and identifiers. | ||
return '"' + node.value + '"'; | ||
} | ||
return node.value; | ||
} | ||
} ] | ||
}, | ||
Identifier: { | ||
operands: [ { name: function (node) { return node.name; } } ] | ||
}, | ||
BlockStatement: { | ||
children: [ 'body' ] | ||
}, | ||
ObjectExpression: { | ||
operands: [ { name: safeName } ], | ||
children: [ 'properties' ] | ||
}, | ||
Property: { | ||
operators: [ { name: ':' } ], | ||
children: [ 'key', 'value' ], | ||
getAssignedName: function (node) { return safeName(node.key); } | ||
}, | ||
ThisExpression: { | ||
operands: [ { name: 'this' } ] | ||
}, | ||
MemberExpression: { | ||
operators: [ { name: function (node) { return '.'; } } ], | ||
children: [ 'object', 'property' ] | ||
}, | ||
CallExpression: getFunctionCallSyntaxDefinition('()'), | ||
NewExpression: getFunctionCallSyntaxDefinition('new'), | ||
ExpressionStatement: { | ||
children: [ 'expression' ] | ||
}, | ||
VariableDeclaration: { | ||
operators: [ { name: function (node) { return node.kind; } } ], | ||
children: [ 'declarations' ] | ||
}, | ||
VariableDeclarator: { | ||
operators: [ { name: '=', optional: function (node) { return !!node.init; } } ], | ||
children: [ 'id', 'init' ], | ||
getAssignedName: function (node) { return safeName(node.id); } | ||
}, | ||
AssignmentExpression: { | ||
operators: [ { name: function (node) { return node.operator; } } ], | ||
children: [ 'left', 'right' ], | ||
getAssignedName: function (node) { | ||
if (node.left.type === 'MemberExpression') { | ||
return safeName(node.left.object) + '.' + node.left.property.name; | ||
} | ||
return safeName(node.left.id); | ||
} | ||
}, | ||
BinaryExpression: { | ||
operators: [ { name: function (node) { return node.operator; } } ], | ||
children: [ 'left', 'right' ] | ||
}, | ||
LogicalExpression: { | ||
complexity: function (node) { return settings.logicalor && node.operator === '||'; }, | ||
operators: [ { name: function (node) { return node.operator; } } ], | ||
children: [ 'left', 'right' ] | ||
}, | ||
IfStatement: { | ||
complexity: true, | ||
operators: [ | ||
{ name: 'if' }, | ||
{ name: 'else', optional: function (node) { return !!node.alternate; } } | ||
], | ||
children: [ 'test', 'consequent', 'alternate' ] | ||
}, | ||
ConditionalExpression: { | ||
complexity: true, | ||
operators: [ { name: ':?' } ], | ||
children: [ 'test', 'consequent', 'alternate' ] | ||
}, | ||
SwitchStatement: { | ||
operators: [ { name: 'switch' } ], | ||
children: [ 'discriminant', 'cases' ] | ||
}, | ||
SwitchCase: { | ||
complexity: function (node) { return settings.switchcase && node.test; }, | ||
operators: [ { name: function (node) { return node.test ? 'case' : 'default'; } } ], | ||
children: [ 'test', 'consequent' ] | ||
}, | ||
BreakStatement: { | ||
operators: [ { name: 'break' } ] | ||
}, | ||
ForStatement: getLoopSyntaxDefinition('for'), | ||
ForInStatement: { | ||
complexity: function (node) { return settings.forin; }, | ||
operators: [ { name: 'forin' } ], | ||
children: [ 'left', 'right', 'body' ] | ||
}, | ||
WhileStatement: getLoopSyntaxDefinition('while'), | ||
DoWhileStatement: getLoopSyntaxDefinition('dowhile'), | ||
FunctionDeclaration: getFunctionSyntaxDefinition(), | ||
FunctionExpression: getFunctionSyntaxDefinition(), | ||
ReturnStatement: { | ||
operators: [ { name: 'return' } ], | ||
children: [ 'argument' ] | ||
}, | ||
TryStatement: { | ||
children: [ 'block', 'handlers' ] | ||
}, | ||
CatchClause: { | ||
complexity: function (node) { return settings.trycatch; }, | ||
operators: [ { name: 'catch' } ], | ||
children: [ 'param', 'body' ] | ||
}, | ||
ThrowStatement: { | ||
operators: [ { name: 'throw' } ], | ||
children: [ 'argument' ] | ||
} | ||
}; | ||
} | ||
function safeName (object, defaultName) { | ||
if (check.isObject(object) && check.isUnemptyString(object.name)) { | ||
return object.name; | ||
} | ||
if (check.isUnemptyString(defaultName)) { | ||
return defaultName; | ||
} | ||
return '<anonymous>'; | ||
} | ||
function getFunctionCallSyntaxDefinition (type) { | ||
return { | ||
operators: [ { name: type } ], | ||
children: [ 'arguments', 'callee' ] | ||
}; | ||
} | ||
function getLoopSyntaxDefinition (type) { | ||
return { | ||
complexity: function (node) { return !!node.test; }, | ||
operators: [ { name: type } ], | ||
children: [ 'init', 'test', 'update', 'body' ] | ||
}; | ||
} | ||
function getFunctionSyntaxDefinition () { | ||
return { | ||
operators: [ { name: 'function' } ], | ||
operands: [ { name: function (node) { return safeName(node.id); } } ], | ||
children: [ 'params', 'body' ], | ||
isFunction: true | ||
}; | ||
} | ||
function createReport () { | ||
@@ -98,3 +240,4 @@ return { | ||
complexity: { | ||
cyclomatic: 1 | ||
cyclomatic: 1, | ||
halstead: createInitialHalsteadState() | ||
} | ||
@@ -104,3 +247,17 @@ }; | ||
function processTree (tree, currentReport) { | ||
function createInitialHalsteadState () { | ||
return { | ||
operators: createInitialHalsteadItemState(), | ||
operands: createInitialHalsteadItemState() | ||
}; | ||
} | ||
function createInitialHalsteadItemState () { | ||
return { | ||
distinct: 0, | ||
total: 0 | ||
}; | ||
} | ||
function processTree (tree, assignedName, currentReport, currentOperators, currentOperands) { | ||
var i; | ||
@@ -111,17 +268,38 @@ | ||
for (i = 0; i < tree.length; i += 1) { | ||
processNode(tree[i], currentReport); | ||
processNode(tree[i], assignedName, currentReport, currentOperators, currentOperands); | ||
} | ||
} | ||
function processNode (node, currentReport) { | ||
if (check.isObject(node) && check.isFunction(syntaxHandlers[node.type])) { | ||
syntaxHandlers[node.type](node, currentReport); | ||
function processNode (node, assignedName, currentReport, currentOperators, currentOperands) { | ||
var def; | ||
if (check.isObject(node)) { | ||
def = syntaxDefinitions[node.type]; | ||
if (check.isObject(syntaxDefinitions[node.type])) { | ||
processComplexity(node, currentReport); | ||
processOperators(node, currentOperators, currentReport); | ||
processOperands(node, currentOperands, currentReport); | ||
if (def.isFunction) { | ||
processChildrenInNewScope(node, assignedName); | ||
} else { | ||
processChildren(node, currentReport, currentOperators, currentOperands); | ||
} | ||
} | ||
} | ||
} | ||
function processCondition (condition, currentReport) { | ||
incrementComplexity(currentReport); | ||
function processComplexity (node, currentReport) { | ||
var def = syntaxDefinitions[node.type]; | ||
processNode(condition.consequent, currentReport); | ||
processNode(condition.alternate, currentReport); | ||
if ( | ||
def.complexity === true || | ||
( | ||
check.isFunction(def.complexity) && | ||
def.complexity(node) | ||
) | ||
) { | ||
incrementComplexity(currentReport); | ||
} | ||
} | ||
@@ -137,92 +315,81 @@ | ||
function processBlock (block, currentReport) { | ||
processTree(block.body, currentReport); | ||
function processOperators (node, currentOperators, currentReport) { | ||
processHalsteadMetric(node, 'operators', operators, currentOperators, currentReport); | ||
} | ||
function processLogical (logical, currentReport) { | ||
if (settings.logicalor && logical.operator === '||') { | ||
processCondition({ | ||
consequent: logical.left, | ||
alternate: logical.right | ||
}, currentReport); | ||
} | ||
function processOperands (node, currentOperands, currentReport) { | ||
processHalsteadMetric(node, 'operands', operands, currentOperands, currentReport); | ||
} | ||
function processSwitch (s, currentReport) { | ||
processTree(s.cases, currentReport); | ||
} | ||
function processHalsteadMetric (node, metric, items, currentItems, currentReport) { | ||
var def = syntaxDefinitions[node.type], i, name; | ||
function processCase (c, currentReport) { | ||
if (settings.switchcase && c.test) { | ||
processCondition({ | ||
consequent: { | ||
type: 'BlockStatement', | ||
body: c.consequent | ||
if (check.isArray(def[metric])) { | ||
for (i = 0; i < def[metric].length; i += 1) { | ||
if (check.isFunction(def[metric][i].name)) { | ||
name = def[metric][i].name(node); | ||
} else { | ||
name = def[metric][i].name; | ||
} | ||
}, currentReport); | ||
} else { | ||
processTree(c.consequent, currentReport); | ||
} | ||
} | ||
function processLoop (loop, currentReport) { | ||
if (loop.test) { | ||
processCondition({ | ||
consequent: loop.body | ||
}, currentReport); | ||
if ( | ||
check.isFunction(def[metric][i].optional) === false || | ||
def[metric][i].optional(node) === true | ||
) { | ||
halsteadItemEncountered(name, currentItems, items, currentReport, metric); | ||
} | ||
} | ||
} | ||
} | ||
function processForIn (forIn, currentReport) { | ||
if (settings.forin) { | ||
incrementComplexity(currentReport); | ||
} | ||
processNode(forIn.body, currentReport); | ||
function halsteadItemEncountered (name, currentItems, items, currentReport, metric) { | ||
incrementHalsteadItems(name, currentItems, currentReport, metric); | ||
incrementHalsteadItems(name, items, report.aggregate, metric); | ||
} | ||
function processTry (t, currentReport) { | ||
processNode(t.block, currentReport); | ||
processTree(t.handlers, currentReport); | ||
function incrementHalsteadItems (name, currentItems, baseReport, metric) { | ||
incrementDistinctHalsteadItems(name, currentItems, baseReport, metric); | ||
incrementTotalHalsteadItems(baseReport, metric); | ||
} | ||
function processCatch (c, currentReport) { | ||
if (settings.trycatch) { | ||
incrementComplexity(currentReport); | ||
function incrementDistinctHalsteadItems (name, currentItems, baseReport, metric) { | ||
if (Object.prototype.hasOwnProperty(name)) { | ||
// Avoid clashes with built-in property names. | ||
incrementDistinctHalsteadItems('_' + name, currentItems, baseReport, metric); | ||
} else if (!currentItems[name]) { | ||
incrementHalsteadMetric(baseReport, metric, 'distinct'); | ||
currentItems[name] = true; | ||
} | ||
} | ||
processNode(c.body, currentReport); | ||
function incrementTotalHalsteadItems (baseReport, metric) { | ||
incrementHalsteadMetric(baseReport, metric, 'total'); | ||
} | ||
function processFunction (fn, currentReport) { | ||
var name; | ||
if (check.isObject(fn.id)) { | ||
name = fn.id.name; | ||
function incrementHalsteadMetric (baseReport, metric, type) { | ||
if (baseReport) { | ||
baseReport.complexity.halstead[metric][type] += 1; | ||
} | ||
processFunctionBody(name, fn.body); | ||
} | ||
function processFunctionBody (name, body) { | ||
var currentReport = createFunctionReport(name, body.loc); | ||
function processChildrenInNewScope (node, assignedName) { | ||
var newReport = createFunctionReport(safeName(node.id, assignedName), node.loc); | ||
report.functions.push(currentReport); | ||
report.functions.push(newReport); | ||
processNode(body, currentReport); | ||
processChildren(node, newReport, {}, {}); | ||
} | ||
function processVariables (variables, currentReport) { | ||
processTree(variables.declarations, currentReport); | ||
} | ||
function processChildren (node, currentReport, currentOperators, currentOperands) { | ||
var def = syntaxDefinitions[node.type], i; | ||
function processVariable (variable, currentReport) { | ||
if (variable.init) { | ||
if ( | ||
variable.init.type === 'FunctionExpression' && | ||
check.isObject(variable.init.id) === false | ||
) { | ||
processFunctionBody(variable.id.name, variable.init.body); | ||
} else { | ||
processNode(variable.init, currentReport); | ||
if (check.isArray(def.children)) { | ||
for (i = 0; i < def.children.length; i += 1) { | ||
processChild( | ||
node[def.children[i]], | ||
check.isFunction(def.getAssignedName) ? def.getAssignedName(node) : '', | ||
currentReport, | ||
currentOperators, | ||
currentOperands | ||
); | ||
} | ||
@@ -232,15 +399,7 @@ } | ||
function processReturn (rtn, currentReport) { | ||
processNode(rtn.argument, currentReport); | ||
function processChild (child, assignedName, currentReport, currentOperators, currentOperands) { | ||
var fn = check.isArray(child) ? processTree : processNode; | ||
fn(child, assignedName, currentReport, currentOperators, currentOperands); | ||
} | ||
function processExpression (expression, currentReport) { | ||
processNode(expression.expression, currentReport); | ||
} | ||
function processCall (call, currentReport) { | ||
processTree(call['arguments'], currentReport); | ||
processNode(call.callee, currentReport); | ||
} | ||
}()); | ||
Sorry, the diff of this file is too big to display
94276
1874
+ Addedesprima@1.0.4(transitive)
- Removedesprima@0.9.9(transitive)
Updatedesprima@1.0.x