*/
package org.sonar.server.computation.task.projectanalysis.component;
+import java.util.Optional;
+
/**
* The tree of components defined in the scanner report.
*/
*/
Component getComponentByRef(int ref);
+
+ /**
+ * Return a component by its batch reference. Returns {@link Optional#empty()} if there's
+ * no {@link Component} with the specified reference
+ *
+ * @throws IllegalStateException if the holder is empty (ie. there is no root yet)
+ * @deprecated This method was introduced as a quick fix for SONAR-10781. Ultimately one should never manipulate component
+ * ref that doesn't exists in the scanner report
+ */
+ @Deprecated
+ Optional<Component> getOptionalComponentByRef(int ref);
+
}
import com.google.common.collect.ImmutableMap;
import java.util.Map;
+import java.util.Optional;
import javax.annotation.CheckForNull;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
@Override
public Component getComponentByRef(int ref) {
+ return getOptionalComponentByRef(ref)
+ .orElseThrow(() -> new IllegalArgumentException(String.format("Component with ref '%s' can't be found", ref)));
+ }
+
+ @Override
+ public Optional<Component> getOptionalComponentByRef(int ref) {
checkInitialized();
ensureComponentByRefIsPopulated();
- Component component = componentsByRef.get(ref);
- checkArgument(component != null, "Component with ref '%s' can't be found", ref);
- return component;
+ return Optional.ofNullable(componentsByRef.get(ref));
}
private void ensureComponentByRefIsPopulated() {
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
if (flow.getLocationCount() > 0) {
DbIssues.Flow.Builder dbFlowBuilder = DbIssues.Flow.newBuilder();
for (ScannerReport.IssueLocation location : flow.getLocationList()) {
- dbFlowBuilder.addLocation(convertLocation(location));
+ convertLocation(location).ifPresent(dbFlowBuilder::addLocation);
}
dbLocationsBuilder.addFlow(dbFlowBuilder);
}
if (flow.getLocationCount() > 0) {
DbIssues.Flow.Builder dbFlowBuilder = DbIssues.Flow.newBuilder();
for (ScannerReport.IssueLocation location : flow.getLocationList()) {
- dbFlowBuilder.addLocation(convertLocation(location));
+ convertLocation(location).ifPresent(dbFlowBuilder::addLocation);
}
dbLocationsBuilder.addFlow(dbFlowBuilder);
}
return issue;
}
- private DbIssues.Location convertLocation(ScannerReport.IssueLocation source) {
+ private Optional<DbIssues.Location> convertLocation(ScannerReport.IssueLocation source) {
DbIssues.Location.Builder target = DbIssues.Location.newBuilder();
if (source.getComponentRef() != 0 && source.getComponentRef() != component.getReportAttributes().getRef()) {
- target.setComponentId(treeRootHolder.getComponentByRef(source.getComponentRef()).getUuid());
+ // SONAR-10781 Component might not exist because on short living branch and PR, only changed components are included in the report
+ Optional<Component> optionalComponent = treeRootHolder.getOptionalComponentByRef(source.getComponentRef());
+ if (!optionalComponent.isPresent()) {
+ return Optional.empty();
+ }
+ target.setComponentId(optionalComponent.get().getUuid());
}
if (isNotEmpty(source.getMsg())) {
target.setMsg(source.getMsg());
DbCommons.TextRange.Builder targetRange = convertTextRange(sourceRange);
target.setTextRange(targetRange);
}
- return target.build();
+ return Optional.of(target.build());
}
private DbCommons.TextRange.Builder convertTextRange(ScannerReport.TextRange sourceRange) {
}
}
+ @Test
+ public void getOptionalComponentByRef_returns_any_report_component_in_the_tree() {
+ underTest.setRoot(SOME_REPORT_COMPONENT_TREE);
+
+ for (int i = 1; i <= 4; i++) {
+ assertThat(underTest.getOptionalComponentByRef(i).get().getReportAttributes().getRef()).isEqualTo(i);
+ }
+ }
+
@Test
public void getComponentByRef_throws_IAE_if_holder_does_not_contain_specified_component() {
underTest.setRoot(SOME_REPORT_COMPONENT_TREE);
underTest.getComponentByRef(6);
}
+ @Test
+ public void getOptionalComponentByRef_returns_empty_if_holder_does_not_contain_specified_component() {
+ underTest.setRoot(SOME_REPORT_COMPONENT_TREE);
+
+ assertThat(underTest.getOptionalComponentByRef(6)).isEmpty();
+ }
+
@Test
public void getComponentByRef_throws_IAE_if_holder_contains_View_tree() {
underTest.setRoot(SOME_VIEWS_COMPONENT_TREE);
*/
package org.sonar.server.computation.task.projectanalysis.component;
+import java.util.Optional;
import org.junit.rules.ExternalResource;
public class TreeRootHolderRule extends ExternalResource implements TreeRootHolder {
public Component getComponentByRef(int ref) {
return delegate.getComponentByRef(ref);
}
+
+ @Override
+ public Optional<Component> getOptionalComponentByRef(int ref) {
+ return delegate.getOptionalComponentByRef(ref);
+ }
}
import org.sonar.api.utils.Duration;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Input;
+import org.sonar.db.protobuf.DbIssues;
import org.sonar.scanner.protocol.Constants;
import org.sonar.scanner.protocol.output.ScannerReport;
import org.sonar.scanner.protocol.output.ScannerReport.TextRange;
public class TrackerRawInputFactoryTest {
+ private static final String FILE_UUID = "fake_uuid";
+ private static final String ANOTHER_FILE_UUID = "another_fake_uuid";
private static int FILE_REF = 2;
+ private static int NOT_IN_REPORT_FILE_REF = 3;
+ private static int ANOTHER_FILE_REF = 4;
- private static ReportComponent PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1).build();
- private static ReportComponent FILE = ReportComponent.builder(Component.Type.FILE, FILE_REF).build();
+ private static ReportComponent FILE = ReportComponent.builder(Component.Type.FILE, FILE_REF).setUuid(FILE_UUID).build();
+ private static ReportComponent ANOTHER_FILE = ReportComponent.builder(Component.Type.FILE, ANOTHER_FILE_REF).setUuid(ANOTHER_FILE_UUID).build();
+ private static ReportComponent PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1).addChildren(FILE, ANOTHER_FILE).build();
@Rule
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT);
assertThat(issue.debt()).isNull();
}
+ // SONAR-10781
+ @Test
+ public void load_issues_from_report_missing_secondary_location_component() {
+ RuleKey ruleKey = RuleKey.of("java", "S001");
+ markRuleAsActive(ruleKey);
+ when(issueFilter.accept(any(), eq(FILE))).thenReturn(true);
+
+ when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
+ ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
+ .setTextRange(TextRange.newBuilder().setStartLine(2).build())
+ .setMsg("the message")
+ .setRuleRepository(ruleKey.repository())
+ .setRuleKey(ruleKey.rule())
+ .setSeverity(Constants.Severity.BLOCKER)
+ .setGap(3.14)
+ .addFlow(ScannerReport.Flow.newBuilder()
+ .addLocation(ScannerReport.IssueLocation.newBuilder()
+ .setComponentRef(FILE_REF)
+ .setMsg("Secondary location in same file")
+ .setTextRange(TextRange.newBuilder().setStartLine(2).build()))
+ .addLocation(ScannerReport.IssueLocation.newBuilder()
+ .setComponentRef(NOT_IN_REPORT_FILE_REF)
+ .setMsg("Secondary location in a missing file")
+ .setTextRange(TextRange.newBuilder().setStartLine(3).build()))
+ .addLocation(ScannerReport.IssueLocation.newBuilder()
+ .setComponentRef(ANOTHER_FILE_REF)
+ .setMsg("Secondary location in another file")
+ .setTextRange(TextRange.newBuilder().setStartLine(3).build()))
+ .build())
+ .build();
+ reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));
+ Input<DefaultIssue> input = underTest.create(FILE);
+
+ Collection<DefaultIssue> issues = input.getIssues();
+ assertThat(issues).hasSize(1);
+ DefaultIssue issue = Iterators.getOnlyElement(issues.iterator());
+
+ DbIssues.Locations locations = issue.getLocations();
+ // fields set by analysis report
+ assertThat(locations.getFlowList()).hasSize(1);
+ assertThat(locations.getFlow(0).getLocationList()).hasSize(2);
+ // Not component id if location is in the same file
+ assertThat(locations.getFlow(0).getLocation(0).getComponentId()).isEmpty();
+ assertThat(locations.getFlow(0).getLocation(1).getComponentId()).isEqualTo(ANOTHER_FILE_UUID);
+ }
+
@Test
public void load_external_issues_from_report() {
when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));