diff --git a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/CxxFunctionComplexitySquidSensor.java b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/CxxFunctionComplexitySquidSensor.java new file mode 100644 index 0000000000..a2a5278b34 --- /dev/null +++ b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/CxxFunctionComplexitySquidSensor.java @@ -0,0 +1,216 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ +package org.sonar.cxx.sensors.functioncomplexity; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Grammar; +import java.util.Hashtable; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputModule; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.cxx.CxxLanguage; +import org.sonar.cxx.api.CxxMetric; +import static org.sonar.cxx.checks.TooManyLinesOfCodeInFunctionCheck.getNumberOfLine; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.sensors.squid.SquidSensor; +import org.sonar.squidbridge.SquidAstVisitor; +import org.sonar.squidbridge.api.SourceFile; +import org.sonar.squidbridge.api.SourceFunction; +import org.sonar.squidbridge.checks.ChecksHelper; + +public class CxxFunctionComplexitySquidSensor extends SquidAstVisitor implements SquidSensor { + + private static final Logger LOG = Loggers.get(CxxFunctionComplexitySquidSensor.class); + + public static final String FUNCTION_COMPLEXITY_THRESHOLD_KEY = "funccomplexity.threshold"; + + private int cyclomaticComplexityThreshold; + + private int functionsBelowThreshold; + + private int functionsOverThreshold; + + private int linesOfCodeBelowThreshold; + + private int linesOfCodeOverThreshold; + + private Hashtable complexFunctionsPerFile = new Hashtable<>(); + + private Hashtable locInComplexFunctionsPerFile = new Hashtable<>(); + + private String fileName; + + public CxxFunctionComplexitySquidSensor(CxxLanguage language){ + this.cyclomaticComplexityThreshold = language.getIntegerOption(FUNCTION_COMPLEXITY_THRESHOLD_KEY).orElse(10); + if (LOG.isDebugEnabled()) { + LOG.debug("Cyclomatic complexity threshold: " + this.cyclomaticComplexityThreshold); + } + } + + @Override + public SquidAstVisitor getVisitor() { + return this; + } + + @Override + public void init() { + subscribeTo(CxxGrammarImpl.functionDefinition); + } + + @Override + public void leaveNode(AstNode node) { + SourceFunction sourceFunction = (SourceFunction) getContext().peekSourceCode(); + SourceFile sourceFile = (SourceFile)sourceFunction.getAncestor(SourceFile.class); + + int complexity = ChecksHelper.getRecursiveMeasureInt(sourceFunction, CxxMetric.COMPLEXITY); + int lineCount = getNumberOfLine(node); + + incrementFunctionByThresholdForAllFiles(complexity, lineCount); + incrementFunctionByThresholdForFile(sourceFile, complexity, lineCount); + } + + private void incrementFunctionByThresholdForAllFiles(int complexity, int lineCount){ + if (complexity > this.cyclomaticComplexityThreshold){ + this.functionsOverThreshold++; + this.linesOfCodeOverThreshold += lineCount; + } + else { + this.functionsBelowThreshold++; + this.linesOfCodeBelowThreshold += lineCount; + } + } + + private void incrementFunctionByThresholdForFile(SourceFile sourceFile, int complexity, int loc){ + if (!complexFunctionsPerFile.containsKey(sourceFile)) { + complexFunctionsPerFile.put(sourceFile, new FunctionCount()); + } + + if (!locInComplexFunctionsPerFile.containsKey(sourceFile)) { + locInComplexFunctionsPerFile.put(sourceFile, new FunctionCount()); + } + + FunctionCount functionCount = complexFunctionsPerFile.get(sourceFile); + FunctionCount locCount = locInComplexFunctionsPerFile.get(sourceFile); + if (complexity > this.cyclomaticComplexityThreshold){ + functionCount.countOverThreshold++; + locCount.countOverThreshold += loc; + } + else { + functionCount.countBelowThreshold++; + locCount.countBelowThreshold += loc; + } + } + + @Override + public void publishMeasureForFile(InputFile inputFile, SourceFile squidFile, SensorContext context) { + publishComplexFunctionMetricsForFile(inputFile, squidFile, context); + publishLocInComplexFunctionMetricsForFile(inputFile, squidFile, context); + } + + private void publishComplexFunctionMetricsForFile(InputFile inputFile, SourceFile squidFile, SensorContext context){ + FunctionCount c = complexFunctionsPerFile.get(squidFile); + if (c == null){ + c = new FunctionCount(); + c.countBelowThreshold = 0; + c.countOverThreshold = 0; + } + + context.newMeasure() + .forMetric(FunctionComplexityMetrics.COMPLEX_FUNCTIONS) + .on(inputFile) + .withValue((int)c.countOverThreshold) + .save(); + + context.newMeasure() + .forMetric(FunctionComplexityMetrics.COMPLEX_FUNCTIONS_PERC) + .on(inputFile) + .withValue(calculatePercentual((int)c.countOverThreshold, (int)c.countBelowThreshold)) + .save(); + } + + private void publishLocInComplexFunctionMetricsForFile(InputFile inputFile, SourceFile squidFile, SensorContext context){ + FunctionCount locCount = locInComplexFunctionsPerFile.get(squidFile); + + if (locCount == null){ + locCount = new FunctionCount(); + locCount.countBelowThreshold = 0; + locCount.countOverThreshold = 0; + } + + context.newMeasure() + .forMetric(FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC) + .on(inputFile) + .withValue(locCount.countOverThreshold) + .save(); + + context.newMeasure() + .forMetric(FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC_PERC) + .on(inputFile) + .withValue(calculatePercentual((int)locCount.countOverThreshold, (int)locCount.countBelowThreshold)) + .save(); + } + + @Override + public void publishMeasureForProject(InputModule module, SensorContext context) { + publishComplexFunctionMetrics(module, context); + publishLinesOfCodeInComplexFunctionMetrics(module, context); + } + + private void publishComplexFunctionMetrics(InputModule module, SensorContext context){ + context.newMeasure() + .forMetric(FunctionComplexityMetrics.COMPLEX_FUNCTIONS) + .on(module) + .withValue(functionsOverThreshold) + .save(); + + context.newMeasure() + .forMetric(FunctionComplexityMetrics.COMPLEX_FUNCTIONS_PERC) + .on(module) + .withValue(calculatePercentual(functionsOverThreshold, functionsBelowThreshold)) + .save(); + } + + private void publishLinesOfCodeInComplexFunctionMetrics(InputModule module, SensorContext context){ + context.newMeasure() + .forMetric(FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC) + .on(module) + .withValue(linesOfCodeOverThreshold) + .save(); + + context.newMeasure() + .forMetric(FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC_PERC) + .on(module) + .withValue(calculatePercentual(linesOfCodeOverThreshold, linesOfCodeBelowThreshold)) + .save(); + } + + private double calculatePercentual(int overThreshold, int belowThreshold){ + double total = (double)overThreshold + (double)belowThreshold; + if (total > 0) { + return ((float)overThreshold * 100.0) / ((float)overThreshold + (float)belowThreshold); + } + else { + return 0; + } + } + +} diff --git a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionComplexityMetrics.java b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionComplexityMetrics.java new file mode 100644 index 0000000000..1af73a714f --- /dev/null +++ b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionComplexityMetrics.java @@ -0,0 +1,63 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ +package org.sonar.cxx.sensors.functioncomplexity; + +import static java.util.Arrays.asList; +import java.util.List; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.Metrics; + +public class FunctionComplexityMetrics implements Metrics { + + public static final Metric COMPLEX_FUNCTIONS = new Metric.Builder("complex_functions", "Complex Functions", Metric.ValueType.INT) + .setDescription("Number of functions with high cyclomatic complexity") + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(Boolean.FALSE) + .setDomain(CoreMetrics.DOMAIN_COMPLEXITY) + .create(); + + public static final Metric COMPLEX_FUNCTIONS_PERC = new Metric.Builder("perc_complex_functions", "Complex Functions (%)", Metric.ValueType.PERCENT) + .setDescription("% of functions with high cyclomatic complexity") + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(Boolean.FALSE) + .setDomain(CoreMetrics.DOMAIN_COMPLEXITY) + .create(); + + public static final Metric COMPLEX_FUNCTIONS_LOC = new Metric.Builder("loc_in_complex_functions", "Complex Functions Lines of Code", Metric.ValueType.INT) + .setDescription("Number of lines of code in functions with high cyclomatic complexity") + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(Boolean.FALSE) + .setDomain(CoreMetrics.DOMAIN_COMPLEXITY) + .create(); + + public static final Metric COMPLEX_FUNCTIONS_LOC_PERC = new Metric.Builder("perc_loc_in_complex_functions", "Complex Functions Lines of Code (%)", Metric.ValueType.PERCENT) + .setDescription("% of lines of code in functions with high cyclomatic complexity") + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(Boolean.FALSE) + .setDomain(CoreMetrics.DOMAIN_COMPLEXITY) + .create(); + + @Override + public List getMetrics() { + return asList(COMPLEX_FUNCTIONS, COMPLEX_FUNCTIONS_PERC, COMPLEX_FUNCTIONS_LOC, COMPLEX_FUNCTIONS_LOC_PERC); + } + +} diff --git a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionCount.java b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionCount.java new file mode 100644 index 0000000000..ab912c0bb3 --- /dev/null +++ b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionCount.java @@ -0,0 +1,26 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ +package org.sonar.cxx.sensors.functioncomplexity; + +public class FunctionCount { + public int countOverThreshold; + + public int countBelowThreshold; +} diff --git a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionScore.java b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionScore.java new file mode 100644 index 0000000000..a827486c57 --- /dev/null +++ b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionScore.java @@ -0,0 +1,62 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ +package org.sonar.cxx.sensors.functioncomplexity; + +public class FunctionScore { + private int score; + + public int getScore(){ + return this.score; + } + + public void setScore(int value){ + this.score = value; + } + + private String componentName; + + public String getComponentName(){ + return this.componentName; + } + + public void setComponentName(String value){ + this.componentName = value; + } + + private String functionId; + + public String getFunctionId(){ + return this.functionId; + } + + public void setFunctionId(String value){ + this.functionId = value; + } + + public FunctionScore(){ + + } + + public FunctionScore(int score, String componentName, String functionId){ + this.score = score; + this.componentName = componentName; + this.functionId = functionId; + } +} diff --git a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionScoreComparator.java b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionScoreComparator.java new file mode 100644 index 0000000000..d5404b1178 --- /dev/null +++ b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/FunctionScoreComparator.java @@ -0,0 +1,40 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ + +package org.sonar.cxx.sensors.functioncomplexity; + +import java.util.Comparator; + +public class FunctionScoreComparator implements Comparator{ + + @Override + public int compare(FunctionScore o1, FunctionScore o2) { + if (o1 == o2) { + return 0; + } + else if (o1.getScore() > o2.getScore()) { + return -1; + } + else { + return 1; + } + } + +} diff --git a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/package-info.java b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/package-info.java new file mode 100644 index 0000000000..a18698c8e1 --- /dev/null +++ b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functioncomplexity/package-info.java @@ -0,0 +1,27 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ + +/** + * Package with sensor to get function complexity metrics + */ +@ParametersAreNonnullByDefault +package org.sonar.cxx.sensors.functioncomplexity; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functionsize/CxxFunctionSizeSquidSensor.java b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functionsize/CxxFunctionSizeSquidSensor.java new file mode 100644 index 0000000000..c9659c07e6 --- /dev/null +++ b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functionsize/CxxFunctionSizeSquidSensor.java @@ -0,0 +1,224 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ +package org.sonar.cxx.sensors.functionsize; + +import com.sonar.sslr.api.AstNode; +import com.sonar.sslr.api.Grammar; +import java.util.Hashtable; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputModule; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.cxx.CxxLanguage; +import static org.sonar.cxx.checks.TooManyLinesOfCodeInFunctionCheck.getNumberOfLine; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.sensors.functioncomplexity.FunctionCount; +import org.sonar.cxx.sensors.squid.SquidSensor; +import org.sonar.squidbridge.SquidAstVisitor; +import org.sonar.squidbridge.api.SourceFile; +import org.sonar.squidbridge.api.SourceFunction; + +public class CxxFunctionSizeSquidSensor extends SquidAstVisitor implements SquidSensor { + + private static final Logger LOG = Loggers.get(CxxFunctionSizeSquidSensor.class); + + public static final String FUNCTION_SIZE_THRESHOLD_KEY = "funcsize.threshold"; + + private int functionsBelowThreshold = 0; + + private int sizeThreshold = 0; + + private int functionsOverThreshold = 0; + + private int locBelowThreshold = 0; + + private int locOverThreshold = 0; + + private Hashtable bigFunctionsPerFile = new Hashtable<>(); + + private Hashtable locInBigFunctionsPerFile = new Hashtable<>(); + + public CxxFunctionSizeSquidSensor(CxxLanguage language){ + this.sizeThreshold = language.getIntegerOption(FUNCTION_SIZE_THRESHOLD_KEY).orElse(20); + if (LOG.isDebugEnabled()) { + LOG.debug("Function size threshold: " + this.sizeThreshold); + } + } + + @Override + public void init() { + subscribeTo(CxxGrammarImpl.functionBody); + } + + @Override + public void leaveNode(AstNode node) { + SourceFunction sourceFunction = (SourceFunction) getContext().peekSourceCode(); + SourceFile sourceFile = (SourceFile)sourceFunction.getAncestor(SourceFile.class); + + int lineCount = getNumberOfLine(node); + + incrementFunctionByThresholdForProject(lineCount); + incrementFunctionByThresholdForFile(sourceFile, lineCount); + } + + private void incrementFunctionByThresholdForFile(SourceFile sourceFile, int lineCount){ + if (!bigFunctionsPerFile.containsKey(sourceFile)) { + bigFunctionsPerFile.put(sourceFile, new FunctionCount()); + } + + if (!locInBigFunctionsPerFile.containsKey(sourceFile)) { + locInBigFunctionsPerFile.put(sourceFile, new FunctionCount()); + } + + FunctionCount count = bigFunctionsPerFile.get(sourceFile); + FunctionCount locCount = locInBigFunctionsPerFile.get(sourceFile); + if (lineCount > this.sizeThreshold){ + count.countOverThreshold++; + locCount.countOverThreshold += lineCount; + } + else { + count.countBelowThreshold++; + locCount.countBelowThreshold += lineCount; + } + } + + private void incrementFunctionByThresholdForProject(int lineCount){ + if (lineCount > sizeThreshold) { + this.functionsOverThreshold++; + this.locOverThreshold += lineCount; + } + else { + this.functionsBelowThreshold++; + this.locBelowThreshold += lineCount; + } + } + + + @Override + public SquidAstVisitor getVisitor() { + return this; + } + + @Override + public void publishMeasureForFile(InputFile inputFile, SourceFile squidFile, SensorContext context) { + publishBigFunctionMetrics(inputFile, squidFile, context); + publishLocInBigFunctionMetrics(inputFile, squidFile, context); + } + + @Override + public void publishMeasureForProject(InputModule module, SensorContext context) { + publishBigFunctionCountForProject(module, context); + publishLocInBigFunctionForProject(module, context); + } + + private void publishBigFunctionCountForProject(InputModule module, SensorContext context){ + context.newMeasure() + .forMetric(FunctionSizeMetrics.BIG_FUNCTIONS) + .on(module) + .withValue(functionsOverThreshold) + .save(); + + context.newMeasure() + .forMetric(FunctionSizeMetrics.BIG_FUNCTIONS_PERC) + .on(module) + .withValue(calculatePercentual(functionsOverThreshold, functionsBelowThreshold)) + .save(); + } + + private void publishLocInBigFunctionForProject(InputModule module, SensorContext context){ + context.newMeasure() + .forMetric(FunctionSizeMetrics.LOC_IN_FUNCTIONS) + .on(module) + .withValue(locOverThreshold + locBelowThreshold) + .save(); + + context.newMeasure() + .forMetric(FunctionSizeMetrics.BIG_FUNCTIONS_LOC) + .on(module) + .withValue(locOverThreshold) + .save(); + + context.newMeasure() + .forMetric(FunctionSizeMetrics.BIG_FUNCTIONS_LOC_PERC) + .on(module) + .withValue(calculatePercentual(locOverThreshold, locBelowThreshold)) + .save(); + } + + private double calculatePercentual(int overThreshold, int belowThreshold){ + double total = (double)overThreshold + (double)belowThreshold; + if (total > 0) { + return ((float)overThreshold * 100.0) / ((float)overThreshold + (float)belowThreshold); + } + else { + return 0; + } + } + + private void publishBigFunctionMetrics(InputFile inputFile, SourceFile squidFile, SensorContext context) { + FunctionCount c = bigFunctionsPerFile.get(squidFile); + if (c == null){ + c = new FunctionCount(); + c.countBelowThreshold = 0; + c.countOverThreshold = 0; + } + + context.newMeasure() + .forMetric(FunctionSizeMetrics.BIG_FUNCTIONS) + .on(inputFile) + .withValue((int)c.countOverThreshold) + .save(); + + context.newMeasure() + .forMetric(FunctionSizeMetrics.BIG_FUNCTIONS_PERC) + .on(inputFile) + .withValue(calculatePercentual(c.countOverThreshold, c.countBelowThreshold)) + .save(); + } + + private void publishLocInBigFunctionMetrics(InputFile inputFile, SourceFile squidFile, SensorContext context) { + FunctionCount c = locInBigFunctionsPerFile.get(squidFile); + if (c == null) { + c = new FunctionCount(); + c.countBelowThreshold = 0; + c.countOverThreshold = 0; + } + + context.newMeasure() + .forMetric(FunctionSizeMetrics.LOC_IN_FUNCTIONS) + .on(inputFile) + .withValue(c.countOverThreshold + c.countBelowThreshold) + .save(); + + context.newMeasure() + .forMetric(FunctionSizeMetrics.BIG_FUNCTIONS_LOC) + .on(inputFile) + .withValue(c.countOverThreshold) + .save(); + + context.newMeasure() + .forMetric(FunctionSizeMetrics.BIG_FUNCTIONS_LOC_PERC) + .on(inputFile) + .withValue(calculatePercentual(c.countOverThreshold, c.countBelowThreshold)) + .save(); + } + +} diff --git a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functionsize/FunctionSizeMetrics.java b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functionsize/FunctionSizeMetrics.java new file mode 100644 index 0000000000..11c9b636f7 --- /dev/null +++ b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functionsize/FunctionSizeMetrics.java @@ -0,0 +1,71 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ + +package org.sonar.cxx.sensors.functionsize; + +import static java.util.Arrays.asList; +import java.util.List; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.Metrics; + +public class FunctionSizeMetrics implements Metrics { + + public static final Metric BIG_FUNCTIONS = new Metric.Builder("big_functions", "Big Functions", Metric.ValueType.INT) + .setDescription("Number of functions with too many lines") + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(Boolean.FALSE) + .setDomain(CoreMetrics.DOMAIN_SIZE) + .create(); + + public static final Metric BIG_FUNCTIONS_LOC = new Metric.Builder("loc_in_big_functions", "Big Functions Lines of Code", Metric.ValueType.INT) + .setDescription("Number of lines of code in functions with too many lines") + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(Boolean.FALSE) + .setDomain(CoreMetrics.DOMAIN_SIZE) + .create(); + + public static final Metric BIG_FUNCTIONS_PERC = new Metric.Builder("perc_big_functions", "Big Functions (%)", Metric.ValueType.PERCENT) + .setDescription("% of functions with too many lines") + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(Boolean.FALSE) + .setDomain(CoreMetrics.DOMAIN_SIZE) + .create(); + + public static final Metric BIG_FUNCTIONS_LOC_PERC = new Metric.Builder("perc_loc_in_big_functions", "Big Functions Lines of Code (%)", Metric.ValueType.PERCENT) + .setDescription("% of lines of code in functions with too many lines") + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(Boolean.FALSE) + .setDomain(CoreMetrics.DOMAIN_SIZE) + .create(); + + public static final Metric LOC_IN_FUNCTIONS = new Metric.Builder("loc_in_functions", "Lines of Code in Functions", Metric.ValueType.INT) + .setDescription("Number of lines of code in function bodies") + .setDirection(Metric.DIRECTION_WORST) + .setQualitative(Boolean.FALSE) + .setDomain(CoreMetrics.DOMAIN_SIZE) + .create(); + + @Override + public List getMetrics() { + return asList(BIG_FUNCTIONS, BIG_FUNCTIONS_PERC, BIG_FUNCTIONS_LOC, BIG_FUNCTIONS_LOC_PERC, LOC_IN_FUNCTIONS); + } + +} diff --git a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functionsize/package-info.java b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functionsize/package-info.java new file mode 100644 index 0000000000..8e5b24a56c --- /dev/null +++ b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/functionsize/package-info.java @@ -0,0 +1,27 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ + +/** + * Package with sensor to get function size metrics + */ +@ParametersAreNonnullByDefault +package org.sonar.cxx.sensors.functionsize; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/squid/CxxSquidSensor.java b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/squid/CxxSquidSensor.java index 5eeaff5692..5e8c6627c9 100644 --- a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/squid/CxxSquidSensor.java +++ b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/squid/CxxSquidSensor.java @@ -46,6 +46,8 @@ import org.sonar.cxx.CxxLanguage; import org.sonar.cxx.api.CxxMetric; import org.sonar.cxx.sensors.compiler.CxxCompilerSensor; +import org.sonar.cxx.sensors.functioncomplexity.CxxFunctionComplexitySquidSensor; +import org.sonar.cxx.sensors.functionsize.CxxFunctionSizeSquidSensor; import org.sonar.cxx.sensors.utils.CxxMetrics; import org.sonar.cxx.sensors.utils.CxxReportSensor; import org.sonar.cxx.sensors.utils.JsonCompilationDatabase; @@ -85,6 +87,12 @@ public class CxxSquidSensor implements Sensor { private final CxxLanguage language; + private List squidSensors = new ArrayList<>(); + + public List getSquidSensors(){ + return this.squidSensors; + } + /** * {@inheritDoc} */ @@ -110,6 +118,15 @@ public CxxSquidSensor(CxxLanguage language, if (this.language.getMetricsCache().isEmpty()) { new CxxMetrics(this.language); } + + registerSquidSensors(); + } + + protected void registerSquidSensors(){ + if (this.language.getKey() == "c++"){ + this.squidSensors.add(new CxxFunctionComplexitySquidSensor(this.language)); + this.squidSensors.add(new CxxFunctionSizeSquidSensor(this.language)); + } } @Override @@ -136,6 +153,9 @@ public void execute(SensorContext context) { this.language.getBooleanOption(CPD_IGNORE_LITERALS_KEY).orElse(Boolean.FALSE), this.language.getBooleanOption(CPD_IGNORE_IDENTIFIERS_KEY).orElse(Boolean.FALSE))); + for (SquidSensor sensor : squidSensors) + visitors.add(sensor.getVisitor()); + CxxConfiguration cxxConf = createConfiguration(context.fileSystem(), context); AstScanner scanner = CxxAstScanner.create(this.language, cxxConf, visitors.toArray(new SquidAstVisitor[visitors.size()])); @@ -214,6 +234,9 @@ private void save(Collection squidSourceFiles, SensorContext context .withValue(violationsCount) .save(); } + + for(SquidSensor sensor: squidSensors) + sensor.publishMeasureForProject(context.module(), context); } private void saveMeasures(InputFile inputFile, SourceFile squidFile, SensorContext context) { @@ -249,6 +272,9 @@ private void saveMeasures(InputFile inputFile, SourceFile squidFile, SensorConte context.newMeasure().forMetric(language.getMetric(CxxMetrics.PUBLIC_DOCUMENTED_API_DENSITY_KEY)) .on(inputFile).withValue(densityOfPublicDocumentedApi).save(); } + + for(SquidSensor sensor: squidSensors) + sensor.publishMeasureForFile(inputFile, squidFile, context); } private int saveViolations(InputFile inputFile, SourceFile squidFile, SensorContext sensorContext) { diff --git a/cxx-sensors/src/main/java/org/sonar/cxx/sensors/squid/SquidSensor.java b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/squid/SquidSensor.java new file mode 100644 index 0000000000..9ec255c243 --- /dev/null +++ b/cxx-sensors/src/main/java/org/sonar/cxx/sensors/squid/SquidSensor.java @@ -0,0 +1,33 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ +package org.sonar.cxx.sensors.squid; + +import com.sonar.sslr.api.Grammar; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputModule; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.squidbridge.SquidAstVisitor; +import org.sonar.squidbridge.api.SourceFile; + +public interface SquidSensor { + SquidAstVisitor getVisitor(); + void publishMeasureForFile(InputFile inputFile, SourceFile squidFile, SensorContext context); + void publishMeasureForProject(InputModule module, SensorContext context); +} diff --git a/cxx-sensors/src/test/java/org/sonar/cxx/sensors/functioncomplexity/CxxFunctionComplexitySquidSensorTest.java b/cxx-sensors/src/test/java/org/sonar/cxx/sensors/functioncomplexity/CxxFunctionComplexitySquidSensorTest.java new file mode 100644 index 0000000000..017cf2f000 --- /dev/null +++ b/cxx-sensors/src/test/java/org/sonar/cxx/sensors/functioncomplexity/CxxFunctionComplexitySquidSensorTest.java @@ -0,0 +1,164 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ +package org.sonar.cxx.sensors.functioncomplexity; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.Collection; +import java.util.Optional; +import static org.assertj.core.api.Assertions.*; +import org.junit.*; +import static org.mockito.Mockito.*; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.api.batch.measure.Metric; +import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.batch.sensor.measure.Measure; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; +import org.sonar.cxx.CxxAstScanner; +import org.sonar.cxx.CxxLanguage; +import org.sonar.cxx.sensors.utils.TestUtils; +import org.sonar.squidbridge.api.SourceFile; + +public class CxxFunctionComplexitySquidSensorTest { + + private FileLinesContextFactory fileLinesContextFactory; + private FileLinesContext fileLinesContext; + private CxxLanguage language; + private SensorContextTester sensorContext; + private CxxFunctionComplexitySquidSensor sensor; + + @Before + public void setUp(){ + fileLinesContextFactory = mock(FileLinesContextFactory.class); + fileLinesContext = mock(FileLinesContext.class); + + language = TestUtils.mockCxxLanguage(); + when(language.getIntegerOption(CxxFunctionComplexitySquidSensor.FUNCTION_COMPLEXITY_THRESHOLD_KEY)).thenReturn(Optional.of(5)); + + sensor = new CxxFunctionComplexitySquidSensor(language); + } + + private DefaultInputFile getInputFile() throws IOException{ + File baseDir = TestUtils.loadResource("/org/sonar/cxx/sensors"); + File target = new File(baseDir, "FunctionComplexity.cc"); + + String content = new String(Files.readAllBytes(target.toPath()), "UTF-8"); + DefaultInputFile inputFile = TestInputFileBuilder.create("ProjectKey", baseDir, target).setContents(content) + .setCharset(Charset.forName("UTF-8")).setLanguage(language.getKey()) + .setType(InputFile.Type.MAIN).build(); + + sensorContext = SensorContextTester.create(baseDir); + sensorContext.fileSystem().add(inputFile); + + when(fileLinesContextFactory.createFor(inputFile)).thenReturn(fileLinesContext); + + return inputFile; + } + + private DefaultInputFile getEmptyInputFile() throws IOException{ + File baseDir = TestUtils.loadResource("/org/sonar/cxx/sensors"); + File target = new File(baseDir, "EmptyFile.cc"); + + String content = new String(Files.readAllBytes(target.toPath()), "UTF-8"); + DefaultInputFile inputFile = TestInputFileBuilder.create("ProjectKey", baseDir, target).setContents(content) + .setCharset(Charset.forName("UTF-8")).setLanguage(language.getKey()) + .setType(InputFile.Type.MAIN).build(); + + sensorContext = SensorContextTester.create(baseDir); + sensorContext.fileSystem().add(inputFile); + + when(fileLinesContextFactory.createFor(inputFile)).thenReturn(fileLinesContext); + + return inputFile; + } + + public boolean containsAll(Collection c){ + return false; + } + + private T getMeasureValue(SensorContextTester sensorContext, String componentKey, Metric metric){ + Collection measures = sensorContext.measures(componentKey); + T value = null; + for(Measure m : measures){ + if (m.metric() == metric) { + value = (T) m.value(); + } + } + return value; + } + + @Test + public void testPublishMeasuresForProject() throws IOException { + DefaultInputFile inputFile = getInputFile(); + + CxxAstScanner.scanSingleFile(inputFile, sensorContext, TestUtils.mockCxxLanguage(), sensor.getVisitor()); + sensor.publishMeasureForProject(sensorContext.module(), sensorContext); + + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS)).isEqualTo(4); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC)).isEqualTo(44); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_PERC)).isEqualTo(40.0); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC_PERC)).isEqualTo(80); + } + + @Test + public void testPublishMeasuresForEmptyProject() throws IOException { + DefaultInputFile inputFile = getEmptyInputFile(); + + CxxAstScanner.scanSingleFile(inputFile, sensorContext, TestUtils.mockCxxLanguage(), sensor.getVisitor()); + sensor.publishMeasureForProject(sensorContext.module(), sensorContext); + + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_PERC)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC_PERC)).isEqualTo(0); + } + + @Test + public void testPublishMeasuresForFile() throws IOException { + DefaultInputFile inputFile = getInputFile(); + + SourceFile squidFile = CxxAstScanner.scanSingleFile(inputFile, sensorContext, TestUtils.mockCxxLanguage(), sensor.getVisitor()); + sensor.publishMeasureForFile(inputFile, squidFile, sensorContext); + + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS)).isEqualTo(4); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC)).isEqualTo(44); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_PERC)).isEqualTo(40.0); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC_PERC)).isEqualTo(80); + } + + @Test + public void testPublishMeasuresForEmptyFile() throws IOException { + DefaultInputFile inputFile = getEmptyInputFile(); + + SourceFile squidFile = CxxAstScanner.scanSingleFile(inputFile, sensorContext, TestUtils.mockCxxLanguage(), sensor.getVisitor()); + sensor.publishMeasureForFile(inputFile, squidFile, sensorContext); + + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_PERC)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionComplexityMetrics.COMPLEX_FUNCTIONS_LOC_PERC)).isEqualTo(0); + } +} diff --git a/cxx-sensors/src/test/java/org/sonar/cxx/sensors/functionsize/CxxFunctionSizeSquidSensorTest.java b/cxx-sensors/src/test/java/org/sonar/cxx/sensors/functionsize/CxxFunctionSizeSquidSensorTest.java new file mode 100644 index 0000000000..2149f8d47b --- /dev/null +++ b/cxx-sensors/src/test/java/org/sonar/cxx/sensors/functionsize/CxxFunctionSizeSquidSensorTest.java @@ -0,0 +1,167 @@ +/* + * Sonar C++ Plugin (Community) + * Copyright (C) 2010-2018 SonarOpenCommunity + * http://github.com/SonarOpenCommunity/sonar-cxx + * + * 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 02110-1301, USA. + */ +package org.sonar.cxx.sensors.functionsize; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.Collection; +import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Before; +import org.junit.Test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.api.batch.measure.Metric; +import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.batch.sensor.measure.Measure; +import org.sonar.api.config.Configuration; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; +import org.sonar.cxx.CxxAstScanner; +import org.sonar.cxx.CxxLanguage; +import org.sonar.cxx.sensors.utils.TestUtils; +import org.sonar.squidbridge.api.SourceFile; + +public class CxxFunctionSizeSquidSensorTest { + private FileLinesContextFactory fileLinesContextFactory; + private FileLinesContext fileLinesContext; + private CxxLanguage language; + private SensorContextTester sensorContext; + private CxxFunctionSizeSquidSensor sensor; + private Configuration configuration; + + @Before + public void setUp(){ + fileLinesContextFactory = mock(FileLinesContextFactory.class); + fileLinesContext = mock(FileLinesContext.class); + + language = TestUtils.mockCxxLanguage(); + when(language.getIntegerOption(CxxFunctionSizeSquidSensor.FUNCTION_SIZE_THRESHOLD_KEY)).thenReturn(Optional.of(10)); + + sensor = new CxxFunctionSizeSquidSensor(this.language); + } + + private DefaultInputFile getInputFile() throws IOException{ + File baseDir = TestUtils.loadResource("/org/sonar/cxx/sensors"); + File target = new File(baseDir, "FunctionComplexity.cc"); + + String content = new String(Files.readAllBytes(target.toPath()), "UTF-8"); + DefaultInputFile inputFile = TestInputFileBuilder.create("ProjectKey", baseDir, target).setContents(content) + .setCharset(Charset.forName("UTF-8")).setLanguage(language.getKey()) + .setType(InputFile.Type.MAIN).build(); + + sensorContext = SensorContextTester.create(baseDir); + sensorContext.fileSystem().add(inputFile); + + when(fileLinesContextFactory.createFor(inputFile)).thenReturn(fileLinesContext); + + return inputFile; + } + + private DefaultInputFile getEmptyInputFile() throws IOException{ + File baseDir = TestUtils.loadResource("/org/sonar/cxx/sensors"); + File target = new File(baseDir, "EmptyFile.cc"); + + String content = new String(Files.readAllBytes(target.toPath()), "UTF-8"); + DefaultInputFile inputFile = TestInputFileBuilder.create("ProjectKey", baseDir, target).setContents(content) + .setCharset(Charset.forName("UTF-8")).setLanguage(language.getKey()) + .setType(InputFile.Type.MAIN).build(); + + sensorContext = SensorContextTester.create(baseDir); + sensorContext.fileSystem().add(inputFile); + + when(fileLinesContextFactory.createFor(inputFile)).thenReturn(fileLinesContext); + + return inputFile; + } + + private T getMeasureValue(SensorContextTester sensorContext, String componentKey, Metric metric){ + Collection measures = sensorContext.measures(componentKey); + T value = null; + for(Measure m : measures){ + if (m.metric() == metric) { + value = (T) m.value(); + } + } + return value; + } + + @Test + public void testPublishMeasuresForProject() throws IOException { + DefaultInputFile inputFile = getInputFile(); + + CxxAstScanner.scanSingleFile(inputFile, sensorContext, TestUtils.mockCxxLanguage(), sensor.getVisitor()); + sensor.publishMeasureForProject(sensorContext.module(), sensorContext); + + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionSizeMetrics.BIG_FUNCTIONS)).isEqualTo(4); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionSizeMetrics.LOC_IN_FUNCTIONS)).isEqualTo(55); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionSizeMetrics.BIG_FUNCTIONS_LOC)).isEqualTo(44); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionSizeMetrics.BIG_FUNCTIONS_PERC)).isEqualTo(40.0); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionSizeMetrics.BIG_FUNCTIONS_LOC_PERC)).isEqualTo(80); + } + + @Test + public void testPublishMeasuresForEmptyProject() throws IOException { + DefaultInputFile inputFile = getEmptyInputFile(); + + CxxAstScanner.scanSingleFile(inputFile, sensorContext, TestUtils.mockCxxLanguage(), sensor.getVisitor()); + sensor.publishMeasureForProject(sensorContext.module(), sensorContext); + + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionSizeMetrics.BIG_FUNCTIONS)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionSizeMetrics.LOC_IN_FUNCTIONS)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionSizeMetrics.BIG_FUNCTIONS_LOC)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionSizeMetrics.BIG_FUNCTIONS_PERC)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, sensorContext.module().key(), FunctionSizeMetrics.BIG_FUNCTIONS_LOC_PERC)).isEqualTo(0); + } + + @Test + public void testPublishMeasuresForFile() throws IOException { + DefaultInputFile inputFile = getInputFile(); + + SourceFile squidFile = CxxAstScanner.scanSingleFile(inputFile, sensorContext, TestUtils.mockCxxLanguage(), sensor.getVisitor()); + sensor.publishMeasureForFile(inputFile, squidFile, sensorContext); + + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionSizeMetrics.BIG_FUNCTIONS)).isEqualTo(4); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionSizeMetrics.LOC_IN_FUNCTIONS)).isEqualTo(55); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionSizeMetrics.BIG_FUNCTIONS_LOC)).isEqualTo(44); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionSizeMetrics.BIG_FUNCTIONS_PERC)).isEqualTo(40.0); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionSizeMetrics.BIG_FUNCTIONS_LOC_PERC)).isEqualTo(80); + } + + @Test + public void testPublishMeasuresForEmptyFile() throws IOException { + DefaultInputFile inputFile = getEmptyInputFile(); + + SourceFile squidFile = CxxAstScanner.scanSingleFile(inputFile, sensorContext, TestUtils.mockCxxLanguage(), sensor.getVisitor()); + sensor.publishMeasureForFile(inputFile, squidFile, sensorContext); + + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionSizeMetrics.BIG_FUNCTIONS)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionSizeMetrics.LOC_IN_FUNCTIONS)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionSizeMetrics.BIG_FUNCTIONS_LOC)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionSizeMetrics.BIG_FUNCTIONS_PERC)).isEqualTo(0); + assertThat(getMeasureValue(sensorContext, inputFile.key(), FunctionSizeMetrics.BIG_FUNCTIONS_LOC_PERC)).isEqualTo(0); + } +} diff --git a/cxx-sensors/src/test/java/org/sonar/plugins/cxx/squid/CxxSquidSensorTest.java b/cxx-sensors/src/test/java/org/sonar/plugins/cxx/squid/CxxSquidSensorTest.java index 494d4e7609..89846f342e 100644 --- a/cxx-sensors/src/test/java/org/sonar/plugins/cxx/squid/CxxSquidSensorTest.java +++ b/cxx-sensors/src/test/java/org/sonar/plugins/cxx/squid/CxxSquidSensorTest.java @@ -19,6 +19,7 @@ */ package org.sonar.plugins.cxx.squid; +import com.sonar.sslr.api.Grammar; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -29,9 +30,9 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.InputModule; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.TestInputFileBuilder; import org.sonar.api.batch.rule.ActiveRules; @@ -45,7 +46,10 @@ import org.sonar.cxx.CxxLanguage; import org.sonar.cxx.sensors.coverage.CxxCoverageSensor; import org.sonar.cxx.sensors.squid.CxxSquidSensor; +import org.sonar.cxx.sensors.squid.SquidSensor; import org.sonar.cxx.sensors.utils.TestUtils; +import org.sonar.squidbridge.SquidAstVisitor; +import org.sonar.squidbridge.api.SourceFile; public class CxxSquidSensorTest { @@ -182,7 +186,7 @@ public void testForceIncludedFiles() throws UnsupportedEncodingException, IOExce Collection measures = context.measures("ProjectKey:src/src1.cc"); - // These checks actually check the force include feature, since only if it works the metric values will be like follows + // These checks actually check the force include feature, since only if it works the metric values will be like follows assertThat(GetIntegerMeasureByKey(measures, CoreMetrics.FILES).value()).isEqualTo(1); assertThat(GetIntegerMeasureByKey(measures, CoreMetrics.NCLOC).value()).isEqualTo(1); assertThat(GetIntegerMeasureByKey(measures, CoreMetrics.STATEMENTS).value()).isEqualTo(2); @@ -211,6 +215,34 @@ public void testBehaviourOnCircularIncludes() throws UnsupportedEncodingExceptio assertThat(GetIntegerMeasureByKey(measures, CoreMetrics.NCLOC).value()).isEqualTo(1); } + @Test + public void testSquidSensors() throws IOException{ + File baseDir = TestUtils.loadResource("/org/sonar/cxx/sensors/codechunks-project"); + File target = new File(baseDir, "code_chunks.cc"); + SensorContextTester context = SensorContextTester.create(baseDir); + + String content = new String(Files.readAllBytes(target.toPath()), "UTF-8"); + DefaultInputFile inputFile = TestInputFileBuilder.create("ProjectKey", baseDir, target).setContents(content) + .setCharset(Charset.forName("UTF-8")).setLanguage(language.getKey()) + .setType(InputFile.Type.MAIN).build(); + + SquidAstVisitor mockVisitor = (SquidAstVisitor) mock(SquidAstVisitor.class); + SquidSensor squidSensorMock = mock(SquidSensor.class); + when(squidSensorMock.getVisitor()).thenReturn(mockVisitor); + + sensor.getSquidSensors().clear(); + sensor.getSquidSensors().add(squidSensorMock); + + context.fileSystem().add(inputFile); + sensor.execute(context); + + verify(squidSensorMock, times(1)).getVisitor(); + verify(squidSensorMock, times(1)).publishMeasureForFile(eq(inputFile), any(SourceFile.class), eq(context)); + verify(squidSensorMock, times(1)).publishMeasureForProject(any(InputModule.class), eq(context)); + } + + + private Measure GetIntegerMeasureByKey(Collection measures, Metric metric) { for (Measure measure : measures) { if (measure.metric().equals(metric)) { diff --git a/cxx-sensors/src/test/resources/org/sonar/cxx/sensors/EmptyFile.cc b/cxx-sensors/src/test/resources/org/sonar/cxx/sensors/EmptyFile.cc new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/cxx-sensors/src/test/resources/org/sonar/cxx/sensors/EmptyFile.cc @@ -0,0 +1 @@ + diff --git a/cxx-sensors/src/test/resources/org/sonar/cxx/sensors/FunctionComplexity.cc b/cxx-sensors/src/test/resources/org/sonar/cxx/sensors/FunctionComplexity.cc new file mode 100644 index 0000000000..62c6a1d609 --- /dev/null +++ b/cxx-sensors/src/test/resources/org/sonar/cxx/sensors/FunctionComplexity.cc @@ -0,0 +1,87 @@ +void func1() { + return; +} + +int func2(int a) { + if( a ) { + return 1; + } else { + return 0; + } +} + +int func3(int a, int b) { + if( a ) { + if( b ) { + return a+b ? 1 : 2; + } else { + return a+b ? 3 : 4; + } + } else { + if( b ) { + return a+b ? 5 : 6; + } else { + return a+b ? 7 : 8; + } + } +} + +class MyClass { +public: + MyClass() {}; + int Method1(int a, int b) {} + int Method2(int a, int b) { + if( a ) { + if( b ) { + return a+b ? 1 : 2; + } else { + return a+b ? 3 : 4; + } + } else { + if( b ) { + return a+b ? 5 : 6; + } else { + return a+b ? 7 : 8; + } + } + } + int Method3(int a, int b); +}; + +int MyClass::Method3(int a, int b) { + if( a ) { + if( b ) { + return a+b ? 1 : 2; + } else { + return a+b ? 3 : 4; + } + } else { + if( b ) { + return a+b ? 5 : 6; + } else { + return a+b ? 7 : 8; + } + } +} + +template +class MyTemplate { +public: + MyTemplate() {}; + int Method1(T a, T b) {} + int Method2(T a, T b) { + if( a ) { + if( b ) { + return a+b ? 1 : 2; + } else { + return a+b ? 3 : 4; + } + } else { + if( b ) { + return a+b ? 5 : 6; + } else { + return a+b ? 7 : 8; + } + } + } +}; diff --git a/cxx-squid/src/main/java/org/sonar/cxx/CxxLanguage.java b/cxx-squid/src/main/java/org/sonar/cxx/CxxLanguage.java index f3507b706d..f39da974ae 100644 --- a/cxx-squid/src/main/java/org/sonar/cxx/CxxLanguage.java +++ b/cxx-squid/src/main/java/org/sonar/cxx/CxxLanguage.java @@ -73,6 +73,10 @@ public String getPluginProperty(String key) { return "sonar." + getPropertiesKey() + "." + key; } + public Optional getIntegerOption(String key){ + return this.settings.getInt(getPluginProperty(key)); + } + public Optional getBooleanOption(String key) { return this.settings.getBoolean(getPluginProperty(key)); } diff --git a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java index 09d375643c..1b68988346 100644 --- a/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java +++ b/sonar-cxx-plugin/src/main/java/org/sonar/plugins/cxx/CxxPlugin.java @@ -50,6 +50,10 @@ import org.sonar.cxx.sensors.cppcheck.CxxCppCheckSensor; import org.sonar.cxx.sensors.drmemory.CxxDrMemoryRuleRepository; import org.sonar.cxx.sensors.drmemory.CxxDrMemorySensor; +import org.sonar.cxx.sensors.functioncomplexity.CxxFunctionComplexitySquidSensor; +import org.sonar.cxx.sensors.functioncomplexity.FunctionComplexityMetrics; +import org.sonar.cxx.sensors.functionsize.CxxFunctionSizeSquidSensor; +import org.sonar.cxx.sensors.functionsize.FunctionSizeMetrics; import org.sonar.cxx.sensors.other.CxxOtherRepository; import org.sonar.cxx.sensors.other.CxxOtherSensor; import org.sonar.cxx.sensors.pclint.CxxPCLintRuleRepository; @@ -330,6 +334,24 @@ private static List codeAnalysisProperties() { .type(PropertyType.TEXT) .subCategory(subcateg) .index(17) + .build(), + PropertyDefinition.builder(LANG_PROP_PREFIX + CxxFunctionComplexitySquidSensor.FUNCTION_COMPLEXITY_THRESHOLD_KEY) + .defaultValue("10") + .name("Cyclomatic complexity threshold") + .description("Cyclomatic complexity threshold used to classify a function as complex") + .subCategory(subcateg) + .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) + .type(PropertyType.INTEGER) + .index(18) + .build(), + PropertyDefinition.builder(LANG_PROP_PREFIX + CxxFunctionSizeSquidSensor.FUNCTION_SIZE_THRESHOLD_KEY) + .defaultValue("20") + .name("Function size threshold") + .description("Function size threshold to consider a function to be too big") + .subCategory(subcateg) + .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) + .type(PropertyType.INTEGER) + .index(19) .build() )); } @@ -495,6 +517,11 @@ public void define(Context context) { l.addAll(testingAndCoverageProperties()); l.addAll(compilerWarningsProperties()); l.addAll(duplicationsProperties()); + + //extra metrics + l.add(FunctionComplexityMetrics.class); + l.add(FunctionSizeMetrics.class); + context.addExtensions(l); } @@ -505,7 +532,7 @@ public List getSensorsImpl() { l.add(CxxCoverageAggregator.class); l.add(CxxUnitTestResultsAggregator.class); - // metrics + // metrics l.add(CxxMetricsImp.class); // issue sensors diff --git a/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/CxxPluginTest.java b/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/CxxPluginTest.java index 9d880c787e..95cb8553e4 100644 --- a/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/CxxPluginTest.java +++ b/sonar-cxx-plugin/src/test/java/org/sonar/plugins/cxx/CxxPluginTest.java @@ -35,6 +35,6 @@ public void testGetExtensions() throws Exception { Plugin.Context context = new Plugin.Context(runtime); CxxPlugin plugin = new CxxPlugin(); plugin.define(context); - assertThat(context.getExtensions()).hasSize(71); + assertThat(context.getExtensions()).hasSize(75); } }