From 47b471b942eadc1408ae8c00e740074acfdb69ec Mon Sep 17 00:00:00 2001 From: Gordon Daugherty Date: Sat, 8 Jul 2017 08:30:55 -0500 Subject: [PATCH] Added a plugin setting to enable an administrator to define a list of XML file patterns that should not be included in the lines of code metric. An example case is where we generate an xml report such as "effective-pom.xml" or "classloader-collisions.xml" prior to scanning the project with Sonarqube. In those cases we need Sonar to see the files so that our custom XML rules will raise issues based on their content. They do not represent lines of code for the project though so we need a way to prevent their content from being included in the project's LOC metric. This change provides that capability. --- .../java/org/sonar/plugins/xml/XmlPlugin.java | 6 ++ .../java/org/sonar/plugins/xml/XmlSensor.java | 46 ++++++++++++++- .../org/sonar/plugins/xml/XmlSensorTest.java | 58 ++++++++++++++++++- .../some-configuration-is-code-file.xml | 21 +++++++ .../resources/xmlsensor/some-data-file.xml | 7 +++ 5 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 sonar-xml-plugin/src/test/resources/xmlsensor/some-configuration-is-code-file.xml create mode 100644 sonar-xml-plugin/src/test/resources/xmlsensor/some-data-file.xml diff --git a/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/XmlPlugin.java b/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/XmlPlugin.java index 7b238270..856c6a8d 100644 --- a/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/XmlPlugin.java +++ b/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/XmlPlugin.java @@ -45,6 +45,12 @@ public void define(Context context) { .category("XML") .onQualifiers(Qualifiers.PROJECT) .build(), + PropertyDefinition.builder(XmlSensor.FILENAME_LIST_TO_EXCLUDE_FROM_LOC_METRIC_KEY) + .name("Filenames to ignore when counting lines of code") + .description("Comma-separated list of file name patterns to skip when counting lines of code. You may use regular expressions as long as they don't contain commas. Usage example: You generate an effective-pom.xml file during your scan and want your Sonarqube rules to run against it BUT don't want the line count for that file captured.") + .defaultValue("") + .category("XML") + .build(), Xml.class, XmlRulesDefinition.class, XmlSonarWayProfile.class, diff --git a/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/XmlSensor.java b/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/XmlSensor.java index 7bc6ff8a..c0528cf5 100644 --- a/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/XmlSensor.java +++ b/sonar-xml-plugin/src/main/java/org/sonar/plugins/xml/XmlSensor.java @@ -21,8 +21,11 @@ import com.google.common.annotations.VisibleForTesting; import java.io.IOException; +import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.regex.Pattern; + import org.sonar.api.batch.fs.FilePredicate; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; @@ -66,20 +69,24 @@ public class XmlSensor implements Sensor { */ private static final Logger LOG = Loggers.get(XmlSensor.class); + public static final String FILENAME_LIST_TO_EXCLUDE_FROM_LOC_METRIC_KEY = "sonar-xml.filename-list-to-exclude-from-lines-of-code-metric"; private static final Version V6_0 = Version.create(6, 0); private final Checks checks; private final FileSystem fileSystem; private final FilePredicate mainFilesPredicate; private final FileLinesContextFactory fileLinesContextFactory; + private final Pattern linesOfCodeCountingExclusionsRegexPattern; - public XmlSensor(FileSystem fileSystem, CheckFactory checkFactory, FileLinesContextFactory fileLinesContextFactory) { + public XmlSensor(FileSystem fileSystem, CheckFactory checkFactory, FileLinesContextFactory fileLinesContextFactory, SensorContext context) { this.fileLinesContextFactory = fileLinesContextFactory; this.checks = checkFactory.create(CheckRepository.REPOSITORY_KEY).addAnnotatedChecks((Iterable) CheckRepository.getCheckClasses()); this.fileSystem = fileSystem; this.mainFilesPredicate = fileSystem.predicates().and( fileSystem.predicates().hasType(InputFile.Type.MAIN), fileSystem.predicates().hasLanguage(Xml.KEY)); + String locExclusionRegexPatternString = buildLOCCountExclusionFilePatternsRegex(context.settings().getStringArray(FILENAME_LIST_TO_EXCLUDE_FROM_LOC_METRIC_KEY)); + this.linesOfCodeCountingExclusionsRegexPattern = "".equals(locExclusionRegexPatternString) ? null : Pattern.compile(locExclusionRegexPatternString); } public void analyse(SensorContext sensorContext) { @@ -87,7 +94,11 @@ public void analyse(SensorContext sensorContext) { } private void computeLinesMeasures(SensorContext context, XmlFile xmlFile) { - LineCounter.analyse(context, fileLinesContextFactory, xmlFile); + if (shouldCountLinesOfCode(context, xmlFile)) { + LineCounter.analyse(context, fileLinesContextFactory, xmlFile); + } else { + LOG.debug("Skipping lines of code computation for file '" + xmlFile.getInputFile().fileName() + "'"); + } } private void runChecks(SensorContext context, XmlFile xmlFile) { @@ -108,6 +119,37 @@ private void runChecks(SensorContext context, XmlFile xmlFile) { } } + private boolean shouldCountLinesOfCode(SensorContext context, XmlFile xmlFile) { + boolean result = true; + + if (linesOfCodeCountingExclusionsRegexPattern != null && + linesOfCodeCountingExclusionsRegexPattern.matcher(xmlFile.getInputFile().fileName()).matches()) { + result = false; + } + + return result; + } + + private String buildLOCCountExclusionFilePatternsRegex(final String[] exclusionSettingValue) { + List ignoreTheseFiles = Arrays.asList(exclusionSettingValue); + + StringBuilder regex = new StringBuilder(); + boolean firstPass = true; + + for (String currentFilePattern : ignoreTheseFiles) { + if (firstPass) { + firstPass = false; + } + else { + regex.append("|"); + } + + regex.append(currentFilePattern); + } + + return regex.toString(); + } + private static void saveSyntaxHighlighting(SensorContext context, List highlightingDataList, InputFile inputFile) { NewHighlighting highlighting = context.newHighlighting().onFile(inputFile); diff --git a/sonar-xml-plugin/src/test/java/org/sonar/plugins/xml/XmlSensorTest.java b/sonar-xml-plugin/src/test/java/org/sonar/plugins/xml/XmlSensorTest.java index 0f97bdff..5bafa65c 100644 --- a/sonar-xml-plugin/src/test/java/org/sonar/plugins/xml/XmlSensorTest.java +++ b/sonar-xml-plugin/src/test/java/org/sonar/plugins/xml/XmlSensorTest.java @@ -28,6 +28,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collection; import java.util.regex.Pattern; import org.assertj.core.api.Condition; import org.junit.Rule; @@ -42,6 +43,7 @@ import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; import org.sonar.api.batch.sensor.internal.SensorContextTester; import org.sonar.api.batch.sensor.issue.Issue; +import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.FileLinesContext; import org.sonar.api.measures.FileLinesContextFactory; @@ -102,6 +104,48 @@ public void should_execute_on_file_with_chars_before_prolog() throws Exception { assertThat(context.allIssues()).extracting("ruleKey").containsOnly(ruleKey); } + + @Test + public void should_skip_line_count_for_only_specified_filenames() throws Exception { + init(true, "some-data-file.xml,bogus-filename.xml"); + fs.add(createInputFile("xmlsensor/some-data-file.xml")); + fs.add(createInputFile("xmlsensor/some-configuration-is-code-file.xml")); + + // Run + sensor.analyse(context); + + // Check + Measure nclocMeasure = context.measure("modulekey:xmlsensor/some-configuration-is-code-file.xml", "ncloc"); + assertThat(nclocMeasure.value()).isEqualTo(20); + System.out.println(nclocMeasure.value()); + + nclocMeasure = context.measure("xmlsensor/some-data-file.xml", "ncloc"); + assertThat(nclocMeasure).isNull(); + + assertLog("Skipping lines of code computation for file 'some-data-file.xml'", false); + assertNoLog("Skipping lines of code computation for file 'some-configuration-is-code-file.xml'", false); + } + + @Test + public void should_skip_line_count_for_only_specified_filenames_REGEX() throws Exception { + init(true, "some-(data|spring-config)-file.xml,bogus-filename.xml"); + fs.add(createInputFile("xmlsensor/some-data-file.xml")); + fs.add(createInputFile("xmlsensor/some-configuration-is-code-file.xml")); + + // Run + sensor.analyse(context); + + // Check + Measure nclocMeasure = context.measure("modulekey:xmlsensor/some-configuration-is-code-file.xml", "ncloc"); + assertThat(nclocMeasure.value()).isEqualTo(20); + System.out.println(nclocMeasure.value()); + + nclocMeasure = context.measure("xmlsensor/some-data-file.xml", "ncloc"); + assertThat(nclocMeasure).isNull(); + + assertLog("Skipping lines of code computation for file 'some-data-file.xml'", false); + assertNoLog("Skipping lines of code computation for file 'some-configuration-is-code-file.xml'", false); + } /** * Has issue for rule NewlineCheck, but should not be reported. @@ -140,9 +184,17 @@ public void should_not_execute_test_on_corrupted_file_and_should_not_raise_parsi } private void init(boolean activateParsingErrorCheck) throws Exception { + init(activateParsingErrorCheck, null); + } + + private void init(boolean activateParsingErrorCheck, String linesOfCodeCountingExclusionPatternsSetting) throws Exception { File moduleBaseDir = new File("src/test/resources"); context = SensorContextTester.create(moduleBaseDir); - + if (linesOfCodeCountingExclusionPatternsSetting != null) { + context.settings().appendProperty(XmlSensor.FILENAME_LIST_TO_EXCLUDE_FROM_LOC_METRIC_KEY, + linesOfCodeCountingExclusionPatternsSetting); + } + fs = new DefaultFileSystem(moduleBaseDir); fs.setWorkDir(temporaryFolder.newFolder("temp")); @@ -166,7 +218,7 @@ private void init(boolean activateParsingErrorCheck) throws Exception { FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class); when(fileLinesContextFactory.createFor(any(InputFile.class))).thenReturn(mock(FileLinesContext.class)); - sensor = new XmlSensor(fs, checkFactory, fileLinesContextFactory); + sensor = new XmlSensor(fs, checkFactory, fileLinesContextFactory, context); } private DefaultInputFile createInputFile(String name) { @@ -229,7 +281,7 @@ public void should_analyze_file_with_its_own_encoding() throws IOException { FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class); when(fileLinesContextFactory.createFor(any(InputFile.class))).thenReturn(mock(FileLinesContext.class)); - sensor = new XmlSensor(fileSystem, checkFactory, fileLinesContextFactory); + sensor = new XmlSensor(fileSystem, checkFactory, fileLinesContextFactory, context); sensor.analyse(context); String componentKey = modulekey + ":" + filename; diff --git a/sonar-xml-plugin/src/test/resources/xmlsensor/some-configuration-is-code-file.xml b/sonar-xml-plugin/src/test/resources/xmlsensor/some-configuration-is-code-file.xml new file mode 100644 index 00000000..c57a3bcb --- /dev/null +++ b/sonar-xml-plugin/src/test/resources/xmlsensor/some-configuration-is-code-file.xml @@ -0,0 +1,21 @@ + + + + 4.0.0 + test + test + 1.0 + pom + test + + src/main/code + + + src + xml + false + xml + xhtml1-strict + + \ No newline at end of file diff --git a/sonar-xml-plugin/src/test/resources/xmlsensor/some-data-file.xml b/sonar-xml-plugin/src/test/resources/xmlsensor/some-data-file.xml new file mode 100644 index 00000000..fc25af70 --- /dev/null +++ b/sonar-xml-plugin/src/test/resources/xmlsensor/some-data-file.xml @@ -0,0 +1,7 @@ + + + 1 + 2 + 3 + + \ No newline at end of file