/*
* Sonar JavaScript Plugin
* Copyright (C) 2011 Eriks Nukis
* dev@sonar.codehaus.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.plugins.javascript.complexity;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.Token;
import org.antlr.runtime.TokenRewriteStream;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeAdaptor;
import org.antlr.runtime.tree.Tree;
import org.antlr.runtime.tree.TreeAdaptor;
import org.sonar.plugins.javascript.cpd.antlr.ES3Lexer;
import org.sonar.plugins.javascript.cpd.antlr.ES3Parser;
public class JavaScriptComplexityAnalyzer {
private List<JavaScriptFunction> functions = new ArrayList<JavaScriptFunction>();
private int anonymousFunctionCounter = 0;
private final static String ANONYMOUS_FUNCTION_NAME = "anonymousFunction";
private final static int[] BRANCHING_NODES = { ES3Parser.IF, ES3Parser.FOR, ES3Parser.WHILE, ES3Parser.SWITCH, ES3Parser.CASE,
ES3Parser.CATCH, ES3Parser.QUE, ES3Parser.DO, ES3Parser.LAND, ES3Parser.LOR };
private boolean isBranchingNode(int nodeCode) {
for (int code : BRANCHING_NODES) {
if (code == nodeCode) {
return true;
}
}
return false;
}
private void countBranchingStatements(CommonTree tree, JavaScriptFunction function) {
if (tree != null) {
if (tree.getType() == ES3Parser.FUNCTION) {
function = new JavaScriptFunction();
function.setLine(tree.getLine());
function.setColumn(tree.getCharPositionInLine());
function.setName(calculateFunctionName(tree));
functions.add(function);
}
if (function != null && isBranchingNode(tree.getType())) {
function.increaseComplexity();
}
for (int i = 0; i < tree.getChildCount(); i++) {
countBranchingStatements((CommonTree) tree.getChild(i), function);
}
}
}
private String calculateFunctionName(CommonTree tree) {
Tree node;
// function name1 () {};
node = tree.getChild(0);
if (node != null && node.getType() == ES3Parser.Identifier) {
return node.getText();
}
// var name2 = function () {}
// name3 = function () {}
// var a = {name3 : function (){}}
node = tree.getParent();
if (node != null && node.getChild(0) != null && node.getChild(0).getType() == ES3Parser.Identifier) {
return node.getChild(0).getText();
}
// someClass.someMember1.someMember2.someMethodDeep = function() {}
node = tree.getParent();
if (node != null && node.getChild(0) != null && node.getChild(0).getType() == ES3Parser.BYFIELD) {
Tree nameNode = node.getChild(0).getChild(1);
if (nameNode != null) {
return nameNode.getText();
}
}
// functionCall(function(o){})
// var a[0] = function(){}
// var b["abc"] = function(){}
// var c['abc'] = function(){}
return ANONYMOUS_FUNCTION_NAME + anonymousFunctionCounter++;
}
public List<JavaScriptFunction> analyzeComplexity(InputStream inputStream) throws JavaScriptPluginException {
CommonTree tree = getJavaScriptAst(inputStream);
countBranchingStatements(tree, null);
return functions;
}
protected CommonTree getJavaScriptAst(InputStream inputStream) throws JavaScriptPluginException {
CommonTree tree = null;
try {
ES3Lexer lexer = new ES3Lexer(new ANTLRInputStream(inputStream));
TokenRewriteStream tokens = new TokenRewriteStream(lexer);
ES3Parser parser = new ES3Parser(tokens);
parser.setTreeAdaptor(adaptor);
ES3Parser.program_return ret = parser.program();
tree = (CommonTree) ret.getTree();
} catch (IOException e) {
throw new JavaScriptPluginException("Could not read file", e);
} catch (RecognitionException e) {
throw new JavaScriptPluginException("Could not parse file", e);
}
return tree;
}
private static TreeAdaptor adaptor = new CommonTreeAdaptor() {
public Object create(Token payload) {
return new CommonTree(payload);
}
};
}