From fa4019b992510560be8c6d1b51bc2dc2f6b41546 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Tue, 24 Apr 2018 15:27:56 +0200 Subject: [PATCH] SONAR-10638 Create Java API for analyzers to report significant code --- .../sonar/api/batch/sensor/SensorContext.java | 11 +++ .../batch/sensor/code/NewSignificantCode.java | 50 ++++++++++++ .../code/internal/DefaultSignificantCode.java | 76 +++++++++++++++++ .../sensor/code/internal/package-info.java | 21 +++++ .../api/batch/sensor/code/package-info.java | 21 +++++ .../internal/InMemorySensorStorage.java | 21 +++-- .../sensor/internal/SensorContextTester.java | 18 +++++ .../batch/sensor/internal/SensorStorage.java | 6 ++ .../internal/DefaultSignificantCodeTest.java | 81 +++++++++++++++++++ .../internal/SensorContextTesterTest.java | 3 +- .../scanner/sensor/DefaultSensorContext.java | 11 +++ .../scanner/sensor/DefaultSensorStorage.java | 27 ++++++- .../sensor/noop/NoOpNewSignificantCode.java | 45 +++++++++++ .../sensor/DefaultSensorContextTest.java | 8 ++ .../sensor/DefaultSensorStorageTest.java | 38 +++++++++ .../protocol/output/FileStructure.java | 5 +- .../protocol/output/ScannerReportWriter.java | 6 ++ .../src/main/protobuf/scanner_report.proto | 6 ++ .../output/ScannerReportWriterTest.java | 15 ++++ 19 files changed, 459 insertions(+), 10 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 create mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewSignificantCode.java 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()) diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java index 595a9d126fb..04b3ca16a2d 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java @@ -29,6 +29,8 @@ import org.sonar.api.batch.fs.InputModule; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.rule.ActiveRules; 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; @@ -54,6 +56,7 @@ import org.sonar.scanner.sensor.noop.NoOpNewCoverage; import org.sonar.scanner.sensor.noop.NoOpNewCpdTokens; import org.sonar.scanner.sensor.noop.NoOpNewExternalIssue; import org.sonar.scanner.sensor.noop.NoOpNewHighlighting; +import org.sonar.scanner.sensor.noop.NoOpNewSignificantCode; import org.sonar.scanner.sensor.noop.NoOpNewSymbolTable; @ThreadSafe @@ -65,6 +68,7 @@ public class DefaultSensorContext implements SensorContext { static final NoOpNewAnalysisError NO_OP_NEW_ANALYSIS_ERROR = new NoOpNewAnalysisError(); static final NoOpNewCoverage NO_OP_NEW_COVERAGE = new NoOpNewCoverage(); static final NoOpNewExternalIssue NO_OP_NEW_EXTERNAL_ISSUE = new NoOpNewExternalIssue(); + static final NoOpNewSignificantCode NO_OP_NEW_SIGNIFICANT_CODE = new NoOpNewSignificantCode(); private final Settings mutableSettings; private final FileSystem fs; @@ -196,4 +200,11 @@ public class DefaultSensorContext implements SensorContext { file.setPublished(true); } + @Override + public NewSignificantCode newSignificantCode() { + if (analysisMode.isIssues()) { + return NO_OP_NEW_SIGNIFICANT_CODE; + } + return new DefaultSignificantCode(sensorStorage); + } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java index 1ccd219c600..c4ca921f7ea 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java @@ -36,6 +36,7 @@ import org.sonar.api.batch.fs.TextRange; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.measure.Metric; import org.sonar.api.batch.measure.MetricFinder; +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; @@ -424,7 +425,7 @@ public class DefaultSensorStorage implements SensorStorage { inputFile.setPublished(true); int componentRef = inputFile.batchId(); if (writer.hasComponentData(FileStructure.Domain.SYMBOLS, componentRef)) { - throw new UnsupportedOperationException("Trying to save symbol table twice for the same file is not supported: " + symbolTable.inputFile().absolutePath()); + throw new UnsupportedOperationException("Trying to save symbol table twice for the same file is not supported: " + symbolTable.inputFile()); } final ScannerReport.Symbol.Builder builder = ScannerReport.Symbol.newBuilder(); final ScannerReport.TextRange.Builder rangeBuilder = ScannerReport.TextRange.newBuilder(); @@ -507,4 +508,28 @@ public class DefaultSensorStorage implements SensorStorage { contextPropertiesCache.put(key, value); } + @Override + public void store(DefaultSignificantCode significantCode) { + ScannerReportWriter writer = reportPublisher.getWriter(); + DefaultInputFile inputFile = (DefaultInputFile) significantCode.inputFile(); + if (shouldSkipStorage(inputFile)) { + return; + } + inputFile.setPublished(true); + int componentRef = inputFile.batchId(); + if (writer.hasComponentData(FileStructure.Domain.SGNIFICANT_CODE, componentRef)) { + throw new UnsupportedOperationException( + "Trying to save significant code information twice for the same file is not supported: " + significantCode.inputFile()); + } + + List protobuf = significantCode.significantCodePerLine().values().stream() + .map(range -> ScannerReport.LineSgnificantCode.newBuilder() + .setLine(range.start().line()) + .setStartOffset(range.start().lineOffset()) + .setEndOffset(range.end().lineOffset()) + .build()) + .collect(Collectors.toList()); + + writer.writeComponentSignificantCode(componentRef, protobuf); + } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewSignificantCode.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewSignificantCode.java new file mode 100644 index 00000000000..f7c7f223759 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewSignificantCode.java @@ -0,0 +1,45 @@ +/* + * 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.scanner.sensor.noop; + +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.sensor.code.NewSignificantCode; + +public class NoOpNewSignificantCode implements NewSignificantCode { + + @Override + public NewSignificantCode onFile(InputFile file) { + // no op + return this; + } + + @Override + public NewSignificantCode addRange(TextRange range) { + // no op + return this; + } + + @Override + public void save() { + // no op + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java index ee1c4a34872..1d0e59b46c2 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java @@ -88,6 +88,14 @@ public class DefaultSensorContextTest { assertThat(adaptor.newMeasure()).isNotNull(); assertThat(adaptor.newAnalysisError()).isEqualTo(DefaultSensorContext.NO_OP_NEW_ANALYSIS_ERROR); assertThat(adaptor.isCancelled()).isFalse(); + assertThat(adaptor.newSignificantCode()).isNotNull(); + } + + @Test + public void shouldSkipSignificantCodeOnPreviewMode() { + when(analysisMode.isIssues()).thenReturn(true); + assertThat(adaptor.newSignificantCode()).isEqualTo(DefaultSensorContext.NO_OP_NEW_SIGNIFICANT_CODE); + } @Test diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java index bb039e2da18..27b67e50220 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java @@ -34,6 +34,7 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.batch.fs.internal.TestInputFileBuilder; import org.sonar.api.batch.measure.MetricFinder; +import org.sonar.api.batch.sensor.code.internal.DefaultSignificantCode; import org.sonar.api.batch.sensor.highlighting.TypeOfText; import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; import org.sonar.api.batch.sensor.issue.ExternalIssue; @@ -215,6 +216,33 @@ public class DefaultSensorStorageTest { verifyZeroInteractions(measureCache); } + @Test + public void should_skip_significant_code_on_pull_request_when_file_status_is_SAME() { + DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php") + .setStatus(InputFile.Status.SAME) + .setContents("foo") + .build(); + when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); + + underTest.store(new DefaultSignificantCode() + .onFile(file) + .addRange(file.selectLine(1))); + + assertThat(reportWriter.hasComponentData(FileStructure.Domain.SGNIFICANT_CODE, file.batchId())).isFalse(); + } + + @Test + public void should_save_significant_code() { + DefaultInputFile file = new TestInputFileBuilder("foo", "src/Foo.php") + .setContents("foo") + .build(); + underTest.store(new DefaultSignificantCode() + .onFile(file) + .addRange(file.selectLine(1))); + + assertThat(reportWriter.hasComponentData(FileStructure.Domain.SGNIFICANT_CODE, file.batchId())).isTrue(); + } + @Test public void should_save_project_measure() throws IOException { String projectKey = "myProject"; @@ -243,6 +271,16 @@ public class DefaultSensorStorageTest { underTest.store(h); } + @Test(expected = UnsupportedOperationException.class) + public void duplicateSignificantCode() throws Exception { + InputFile inputFile = new TestInputFileBuilder("foo", "src/Foo.java") + .setModuleBaseDir(temp.newFolder().toPath()).build(); + DefaultSignificantCode h = new DefaultSignificantCode(null) + .onFile(inputFile); + underTest.store(h); + underTest.store(h); + } + @Test(expected = UnsupportedOperationException.class) public void duplicateSymbolTable() throws Exception { InputFile inputFile = new TestInputFileBuilder("foo", "src/Foo.java") diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java index 019e64d169d..44ae856a6c2 100644 --- a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java @@ -42,7 +42,8 @@ public class FileStructure { COVERAGES("coverages-", Domain.PB), TESTS("tests-", Domain.PB), COVERAGE_DETAILS("coverage-details-", Domain.PB), - SOURCE("source-", ".txt"); + SOURCE("source-", ".txt"), + SGNIFICANT_CODE("sgnificant-code-", Domain.PB); private static final String PB = ".pb"; private final String filePrefix; @@ -82,7 +83,7 @@ public class FileStructure { public File contextProperties() { return new File(dir, "context-props.pb"); } - + public File root() { return dir; } diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java index ebb6fe925e2..7df46f355cc 100644 --- a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java @@ -73,6 +73,12 @@ public class ScannerReportWriter { return file; } + public File writeComponentSignificantCode(int componentRef, Iterable lineSignificantCode) { + File file = fileStructure.fileFor(FileStructure.Domain.SGNIFICANT_CODE, componentRef); + Protobuf.writeStream(lineSignificantCode, file, false); + return file; + } + public void appendComponentIssue(int componentRef, ScannerReport.Issue issue) { File file = fileStructure.fileFor(FileStructure.Domain.ISSUES, componentRef); try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file, true))) { diff --git a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto index 922adca6f57..66930fb3a69 100644 --- a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto +++ b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto @@ -263,6 +263,12 @@ message TextRange { int32 end_offset = 4; } +message LineSgnificantCode { + int32 line = 1; + int32 start_offset = 2; + int32 end_offset = 3; +} + message Symbol { TextRange declaration = 1; repeated TextRange reference = 2; diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java index f4ee4dc3ed3..6d0223be28d 100644 --- a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java +++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java @@ -283,6 +283,21 @@ public class ScannerReportWriterTest { assertThat(underTest.hasComponentData(FileStructure.Domain.SYNTAX_HIGHLIGHTINGS, 1)).isTrue(); } + @Test + public void write_line_significant_code() { + // no data yet + assertThat(underTest.hasComponentData(FileStructure.Domain.SGNIFICANT_CODE, 1)).isFalse(); + + underTest.writeComponentSignificantCode(1, asList( + ScannerReport.LineSgnificantCode.newBuilder() + .setLine(1) + .setStartOffset(2) + .setEndOffset(3) + .build())); + + assertThat(underTest.hasComponentData(FileStructure.Domain.SGNIFICANT_CODE, 1)).isTrue(); + } + @Test public void write_coverage() { // no data yet -- 2.39.5