From fa4019b992510560be8c6d1b51bc2dc2f6b41546 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Tue, 24 Apr 2018 15:27:56 +0200 Subject: SONAR-10638 Create Java API for analyzers to report significant code --- .../org/sonar/api/batch/sensor/SensorContext.java | 11 +++ .../api/batch/sensor/code/NewSignificantCode.java | 50 +++++++++++++ .../code/internal/DefaultSignificantCode.java | 76 ++++++++++++++++++++ .../batch/sensor/code/internal/package-info.java | 21 ++++++ .../sonar/api/batch/sensor/code/package-info.java | 21 ++++++ .../sensor/internal/InMemorySensorStorage.java | 21 ++++-- .../batch/sensor/internal/SensorContextTester.java | 18 +++++ .../api/batch/sensor/internal/SensorStorage.java | 6 ++ .../code/internal/DefaultSignificantCodeTest.java | 81 ++++++++++++++++++++++ .../sensor/internal/SensorContextTesterTest.java | 3 +- 10 files changed, 301 insertions(+), 7 deletions(-) create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/NewSignificantCode.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/internal/DefaultSignificantCode.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/internal/package-info.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/package-info.java create mode 100644 sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/code/internal/DefaultSignificantCodeTest.java (limited to 'sonar-plugin-api') diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java index 287fc2f89b3..ed4ffe22b3c 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java @@ -26,6 +26,7 @@ import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.InputModule; import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.sensor.code.NewSignificantCode; import org.sonar.api.batch.sensor.coverage.NewCoverage; import org.sonar.api.batch.sensor.cpd.NewCpdTokens; import org.sonar.api.batch.sensor.error.NewAnalysisError; @@ -162,6 +163,16 @@ public interface SensorContext { */ NewAnalysisError newAnalysisError(); + /** + * Builder to declare which parts of the code is significant code. + * Ranges that are not reported as significant code will be ignored and won't be considered when calculating which lines were modified. + * + * If the significant code is not reported for a file, it is assumed that the entire file is significant code. + * + * @since 7.2 + */ + NewSignificantCode newSignificantCode(); + /** * Add a property to the scanner context. This context is available * in Compute Engine when processing the report. diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/NewSignificantCode.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/NewSignificantCode.java new file mode 100644 index 00000000000..dcaf087ddad --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/NewSignificantCode.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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.api.batch.sensor.code; + +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.TextRange; + +/** + * This object is used to report ranges of a file which contain significant code. + * Lines that are left out (no range reported) will be ignored and won't be considered when calculating which lines were modified. + * It's particularly useful when portions of the lines are automatically modified by code editors and do not contain + * code or comments were issues might be created. + * + * Don't forget to call {@link #save()} after setting the file and the ranges where the significant code is. + * + * @since 7.2 + */ +public interface NewSignificantCode { + /** + * The file for which significant code is being reported. This field must be set before saving the error. + */ + NewSignificantCode onFile(InputFile file); + + /** + * Add a range of significant code. The range may only include a single line and only one range is accepted per line. + */ + NewSignificantCode addRange(TextRange range); + + /** + * Save the reported information for the given file. + */ + void save(); +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/internal/DefaultSignificantCode.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/internal/DefaultSignificantCode.java new file mode 100644 index 00000000000..97d2e25a9bd --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/internal/DefaultSignificantCode.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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.api.batch.sensor.code.internal; + +import com.google.common.base.Preconditions; +import java.util.SortedMap; +import java.util.TreeMap; +import javax.annotation.Nullable; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.sensor.code.NewSignificantCode; +import org.sonar.api.batch.sensor.internal.DefaultStorable; +import org.sonar.api.batch.sensor.internal.SensorStorage; + +public class DefaultSignificantCode extends DefaultStorable implements NewSignificantCode { + private SortedMap significantCodePerLine = new TreeMap<>(); + private InputFile inputFile; + + public DefaultSignificantCode() { + super(); + } + + public DefaultSignificantCode(@Nullable SensorStorage storage) { + super(storage); + } + + @Override + public DefaultSignificantCode onFile(InputFile inputFile) { + this.inputFile = inputFile; + return this; + } + + @Override + public DefaultSignificantCode addRange(TextRange range) { + Preconditions.checkState(this.inputFile != null, "addRange() should be called after on()"); + + int line = range.start().line(); + + Preconditions.checkArgument(line == range.end().line(), "Ranges of significant code must be located in a single line"); + Preconditions.checkState(!significantCodePerLine.containsKey(line), "Significant code was already reported for line '%s'. Can only report once per line.", line); + + significantCodePerLine.put(line, range); + return this; + } + + @Override + protected void doSave() { + Preconditions.checkState(inputFile != null, "Call onFile() first"); + storage.store(this); + } + + public InputFile inputFile() { + return inputFile; + } + + public SortedMap significantCodePerLine() { + return significantCodePerLine; + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/internal/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/internal/package-info.java new file mode 100644 index 00000000000..3a93ee2e007 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/internal/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.api.batch.sensor.code.internal; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/package-info.java new file mode 100644 index 00000000000..e8b9a012e09 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/package-info.java @@ -0,0 +1,21 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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. + */ +@javax.annotation.ParametersAreNonnullByDefault +package org.sonar.api.batch.sensor.code; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java index ef225532139..e46b8121830 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import org.sonar.api.batch.sensor.code.internal.DefaultSignificantCode; import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage; import org.sonar.api.batch.sensor.cpd.internal.DefaultCpdTokens; import org.sonar.api.batch.sensor.error.AnalysisError; @@ -35,7 +36,6 @@ import org.sonar.api.batch.sensor.issue.ExternalIssue; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.measure.Measure; import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable; -import org.sonar.api.utils.SonarException; import static com.google.common.base.Preconditions.checkArgument; @@ -52,6 +52,7 @@ class InMemorySensorStorage implements SensorStorage { Multimap coverageByComponent = ArrayListMultimap.create(); Map symbolsPerComponent = new HashMap<>(); Map contextProperties = new HashMap<>(); + Map significantCodePerComponent = new HashMap<>(); @Override public void store(Measure measure) { @@ -59,7 +60,7 @@ class InMemorySensorStorage implements SensorStorage { String componentKey = measure.inputComponent().key(); String metricKey = measure.metric().key(); if (measuresByComponentAndMetric.contains(componentKey, metricKey)) { - throw new SonarException("Can not add the same measure twice"); + throw new IllegalStateException("Can not add the same measure twice"); } measuresByComponentAndMetric.row(componentKey).put(metricKey, measure); } @@ -74,7 +75,7 @@ class InMemorySensorStorage implements SensorStorage { String fileKey = highlighting.inputFile().key(); // Emulate duplicate storage check if (highlightingByComponent.containsKey(fileKey)) { - throw new UnsupportedOperationException("Trying to save highlighting twice for the same file is not supported: " + highlighting.inputFile().relativePath()); + throw new UnsupportedOperationException("Trying to save highlighting twice for the same file is not supported: " + highlighting.inputFile()); } highlightingByComponent.put(fileKey, highlighting); } @@ -90,7 +91,7 @@ class InMemorySensorStorage implements SensorStorage { String fileKey = defaultCpdTokens.inputFile().key(); // Emulate duplicate storage check if (cpdTokensByComponent.containsKey(fileKey)) { - throw new UnsupportedOperationException("Trying to save CPD tokens twice for the same file is not supported: " + defaultCpdTokens.inputFile().relativePath()); + throw new UnsupportedOperationException("Trying to save CPD tokens twice for the same file is not supported: " + defaultCpdTokens.inputFile()); } cpdTokensByComponent.put(fileKey, defaultCpdTokens); } @@ -100,7 +101,7 @@ class InMemorySensorStorage implements SensorStorage { String fileKey = symbolTable.inputFile().key(); // Emulate duplicate storage check if (symbolsPerComponent.containsKey(fileKey)) { - throw new UnsupportedOperationException("Trying to save symbol table twice for the same file is not supported: " + symbolTable.inputFile().relativePath()); + throw new UnsupportedOperationException("Trying to save symbol table twice for the same file is not supported: " + symbolTable.inputFile()); } symbolsPerComponent.put(fileKey, symbolTable); } @@ -121,4 +122,14 @@ class InMemorySensorStorage implements SensorStorage { public void store(ExternalIssue issue) { allExternalIssues.add(issue); } + + @Override + public void store(DefaultSignificantCode significantCode) { + String fileKey = significantCode.inputFile().key(); + // Emulate duplicate storage check + if (significantCodePerComponent.containsKey(fileKey)) { + throw new UnsupportedOperationException("Trying to save significant code information twice for the same file is not supported: " + significantCode.inputFile()); + } + significantCodePerComponent.put(fileKey, significantCode); + } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java index b13340fb1c1..76977f55bd7 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java @@ -47,6 +47,8 @@ import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.code.NewSignificantCode; +import org.sonar.api.batch.sensor.code.internal.DefaultSignificantCode; import org.sonar.api.batch.sensor.coverage.NewCoverage; import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage; import org.sonar.api.batch.sensor.cpd.NewCpdTokens; @@ -267,6 +269,17 @@ public class SensorContextTester implements SensorContext { .reduce(null, SensorContextTester::maxOrNull); } + @CheckForNull + public TextRange significantCodeTextRange(String fileKey, int line) { + if (sensorStorage.significantCodePerComponent.containsKey(fileKey)) { + return sensorStorage.significantCodePerComponent.get(fileKey) + .significantCodePerLine() + .get(line); + } + return null; + + } + @CheckForNull public static Integer maxOrNull(@Nullable Integer o1, @Nullable Integer o2) { return o1 == null ? o2 : Math.max(o1, o2); @@ -366,4 +379,9 @@ public class SensorContextTester implements SensorContext { DefaultInputFile file = (DefaultInputFile) inputFile; file.setPublished(true); } + + @Override + public NewSignificantCode newSignificantCode() { + return new DefaultSignificantCode(sensorStorage); + } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java index d8c7cb5648f..ede3c9e9910 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java @@ -20,6 +20,7 @@ package org.sonar.api.batch.sensor.internal; import org.sonar.api.batch.ScannerSide; +import org.sonar.api.batch.sensor.code.internal.DefaultSignificantCode; import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage; import org.sonar.api.batch.sensor.cpd.internal.DefaultCpdTokens; import org.sonar.api.batch.sensor.error.AnalysisError; @@ -71,4 +72,9 @@ public interface SensorStorage { * @since 6.1 */ void storeProperty(String key, String value); + + /** + * @since 7.2 + */ + void store(DefaultSignificantCode significantCode); } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/code/internal/DefaultSignificantCodeTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/code/internal/DefaultSignificantCodeTest.java new file mode 100644 index 00000000000..73b5f57ed9b --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/code/internal/DefaultSignificantCodeTest.java @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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.api.batch.sensor.code.internal; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.api.batch.sensor.internal.SensorStorage; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class DefaultSignificantCodeTest { + private SensorStorage sensorStorage = mock(SensorStorage.class); + private DefaultSignificantCode underTest = new DefaultSignificantCode(sensorStorage); + private InputFile inputFile = TestInputFileBuilder.create("module", "file1.xoo") + .setContents("this is\na file\n with some code") + .build(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void should_save_ranges() { + underTest.onFile(inputFile) + .addRange(inputFile.selectLine(1)) + .save(); + verify(sensorStorage).store(underTest); + } + + @Test + public void fail_if_save_without_file() { + exception.expect(IllegalStateException.class); + exception.expectMessage("Call onFile() first"); + underTest.save(); + } + + @Test + public void fail_if_add_range_to_same_line_twice() { + underTest.onFile(inputFile); + underTest.addRange(inputFile.selectLine(1)); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Significant code was already reported for line '1'."); + underTest.addRange(inputFile.selectLine(1)); + } + + @Test + public void fail_if_range_includes_many_lines() { + underTest.onFile(inputFile); + + exception.expect(IllegalArgumentException.class); + exception.expectMessage("Ranges of significant code must be located in a single line"); + underTest.addRange(inputFile.newRange(1, 1, 2, 1)); + } + + @Test + public void fail_if_add_range_before_setting_file() { + exception.expect(IllegalStateException.class); + exception.expectMessage("addRange() should be called after on()"); + underTest.addRange(inputFile.selectLine(1)); + } +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java index c43f0275b34..a4d659e29f0 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java @@ -47,7 +47,6 @@ import org.sonar.api.config.internal.MapSettings; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleType; -import org.sonar.api.utils.SonarException; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; @@ -176,7 +175,7 @@ public class SensorContextTesterTest { assertThat(tester.measure("foo", "directories")).isNotNull(); } - @Test(expected = SonarException.class) + @Test(expected = IllegalStateException.class) public void duplicateMeasures() { tester.newMeasure() .on(new TestInputFileBuilder("foo", "src/Foo.java").build()) -- cgit v1.2.3