]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10638 Create Java API for analyzers to report significant code
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Tue, 24 Apr 2018 13:27:56 +0000 (15:27 +0200)
committerSonarTech <sonartech@sonarsource.com>
Wed, 9 May 2018 18:20:46 +0000 (20:20 +0200)
19 files changed:
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/NewSignificantCode.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/internal/DefaultSignificantCode.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/internal/package-info.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/code/package-info.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java
sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/code/internal/DefaultSignificantCodeTest.java [new file with mode: 0644]
sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/noop/NoOpNewSignificantCode.java [new file with mode: 0644]
sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java
sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/FileStructure.java
sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/output/ScannerReportWriter.java
sonar-scanner-protocol/src/main/protobuf/scanner_report.proto
sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java

index 287fc2f89b338ac97007977d0c2bbd083bed40f8..ed4ffe22b3c9b7c2964dbe071e50287861c3a83b 100644 (file)
@@ -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 (file)
index 0000000..dcaf087
--- /dev/null
@@ -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 (file)
index 0000000..97d2e25
--- /dev/null
@@ -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<Integer, TextRange> 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<Integer, TextRange> 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 (file)
index 0000000..3a93ee2
--- /dev/null
@@ -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 (file)
index 0000000..e8b9a01
--- /dev/null
@@ -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;
index ef225532139ce05b723248acf23959fe770d5b77..e46b8121830005b94980dd91a4e45daebd53e0df 100644 (file)
@@ -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<String, DefaultCoverage> coverageByComponent = ArrayListMultimap.create();
   Map<String, DefaultSymbolTable> symbolsPerComponent = new HashMap<>();
   Map<String, String> contextProperties = new HashMap<>();
+  Map<String, DefaultSignificantCode> 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);
+  }
 }
index b13340fb1c1b61f4daea3e26b04c15ad81a51bba..76977f55bd7defbbcdb6e312168eb05395e8db6b 100644 (file)
@@ -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);
+  }
 }
index d8c7cb5648f2170cd4d4d03c053e3e896730197b..ede3c9e99105ce59bbd39803c3fa3d883aed02de 100644 (file)
@@ -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 (file)
index 0000000..73b5f57
--- /dev/null
@@ -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));
+  }
+}
index c43f0275b34d7453c5f6d721ea2145070f61e0de..a4d659e29f09b40d56f10bef82312178d038f61e 100644 (file)
@@ -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.<Integer>newMeasure()
       .on(new TestInputFileBuilder("foo", "src/Foo.java").build())
index 595a9d126fb8b7e1841a3eba968e2633bf739fa6..04b3ca16a2d30b37c25ae36ec637fc59e6885bda 100644 (file)
@@ -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);
+  }
 }
index 1ccd219c600630e0ac4eaa34256af58a289a07b5..c4ca921f7eae0d3d6f91729c71e1ee71d873f218 100644 (file)
@@ -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<ScannerReport.LineSgnificantCode> 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 (file)
index 0000000..f7c7f22
--- /dev/null
@@ -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
+  }
+
+}
index ee1c4a348724056ca5687ce2678ccecd9868970a..1d0e59b46c256970b6b2a89170ffc99dee9c9f15 100644 (file)
@@ -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
index bb039e2da18b2ff7682812839be33ff25bcb21d5..27b67e502208905d8d2beb6fb9ed31c880db7645 100644 (file)
@@ -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")
index 019e64d169d177486fd9d4e58d957e13839af7ae..44ae856a6c2c8b79a9f877315c4ea80e519c210f 100644 (file)
@@ -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;
   }
index ebb6fe925e27fdc0c22f17a90831df691007d018..7df46f355cc1fea5888c9350cd136820f29eb3c9 100644 (file)
@@ -73,6 +73,12 @@ public class ScannerReportWriter {
     return file;
   }
 
+  public File writeComponentSignificantCode(int componentRef, Iterable<ScannerReport.LineSgnificantCode> 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))) {
index 922adca6f5708e4b5dde037c82bb12eb0b32c320..66930fb3a698f97d0a6b7c53943221b8cae393c0 100644 (file)
@@ -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;
index f4ee4dc3ed30b55e9cf10efee1a0339329a56ed7..6d0223be28d5edf14488465f8cf3e442f25a4950 100644 (file)
@@ -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