/*
* 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.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.Sensor;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.PersistenceMode;
import org.sonar.api.measures.RangeDistributionBuilder;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.ProjectFileSystem;
import org.sonar.api.rules.ActiveRule;
import org.sonar.api.rules.Violation;
import org.sonar.plugins.javascript.JavaScript;
import org.sonar.plugins.javascript.JavaScriptFile;
import org.sonar.plugins.javascript.jslint.JavaScriptRuleRepository;
import org.sonar.plugins.javascript.jslint.JsLintRuleManager;
public final class JavaScriptComplexitySensor implements Sensor {
private final static Logger LOG = LoggerFactory.getLogger(JavaScriptComplexitySensor.class);
private JavaScript javascript;
private RulesProfile rulesProfile;
private final Number[] FUNCTIONS_DISTRIB_BOTTOM_LIMITS = { 1, 2, 4, 6, 8, 10, 12, 20, 30 };
private final Number[] FILES_DISTRIB_BOTTOM_LIMITS = { 0, 5, 10, 20, 30, 60, 90 };
public JavaScriptComplexitySensor(JavaScript javascript, RulesProfile rulesProfile) {
this.javascript = javascript;
this.rulesProfile = rulesProfile;
}
public boolean shouldExecuteOnProject(Project project) {
return javascript.equals(project.getLanguage());
}
public void analyse(Project project, SensorContext sensorContext) {
for (File file : project.getFileSystem().getSourceFiles(javascript)) {
try {
analyzeFile(file, project.getFileSystem(), sensorContext);
} catch (Exception e) {
LOG.error("Can not analyze the file " + file.getAbsolutePath(), e);
}
}
}
protected void analyzeFile(File file, ProjectFileSystem projectFileSystem, SensorContext sensorContext) throws IOException {
try {
JavaScriptFile resource = JavaScriptFile.fromIOFile(file, projectFileSystem.getSourceDirs());
JavaScriptComplexityAnalyzer analyzer = new JavaScriptComplexityAnalyzer();
List<JavaScriptFunction> functions = analyzer.analyzeComplexity(new FileInputStream(file));
// COMPLEXITY
int fileComplexity = 0;
for (JavaScriptFunction function : functions) {
fileComplexity += function.getComplexity();
}
sensorContext.saveMeasure(resource, CoreMetrics.COMPLEXITY, Double.valueOf(fileComplexity));
// FILE_COMPLEXITY_DISTRIBUTION
RangeDistributionBuilder fileDistribution = new RangeDistributionBuilder(CoreMetrics.FILE_COMPLEXITY_DISTRIBUTION,
FILES_DISTRIB_BOTTOM_LIMITS);
fileDistribution.add(Double.valueOf(fileComplexity));
sensorContext.saveMeasure(resource, fileDistribution.build().setPersistenceMode(PersistenceMode.MEMORY));
// FUNCTION_COMPLEXITY
if ( !functions.isEmpty()) {
sensorContext.saveMeasure(resource, CoreMetrics.FUNCTION_COMPLEXITY, Double.valueOf(fileComplexity) / functions.size());
}
// FUNCTION_COMPLEXITY_DISTRIBUTION
RangeDistributionBuilder functionDistribution = new RangeDistributionBuilder(CoreMetrics.FUNCTION_COMPLEXITY_DISTRIBUTION,
FUNCTIONS_DISTRIB_BOTTOM_LIMITS);
for (JavaScriptFunction function : functions) {
functionDistribution.add(Double.valueOf(function.getComplexity()));
}
sensorContext.saveMeasure(resource, functionDistribution.build().setPersistenceMode(PersistenceMode.MEMORY));
// Maximum complexity exceed rule (CYCLOMATIC_COMPLEXITY)
ActiveRule rule = rulesProfile.getActiveRule(JavaScriptRuleRepository.REPOSITORY_KEY, JsLintRuleManager.CYCLOMATIC_COMPLEXITY_KEY);
if (rule != null) {
int maxAllowedComplexity = Integer.parseInt(rule.getActiveRuleParams().get(0).getValue());
for (JavaScriptFunction function : functions) {
if (function.getComplexity() > maxAllowedComplexity) {
Violation violation = Violation.create(rule, resource);
violation.setLineId(function.getLine());
violation
.setMessage("Cyclomatic Complexity is " + function.getComplexity() + " (max allowed is " + maxAllowedComplexity + ").");
sensorContext.saveViolation(violation);
}
}
}
} catch (JavaScriptPluginException e) {
LOG.error("Could not analyze file: " + file.getAbsoluteFile(), e);
}
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}