--- /dev/null
+/*
+ * 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.server.computation.task.projectanalysis.issue;
+
+import java.util.Collections;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+
+@Immutable
+public class NewExternalRule implements Rule {
+ private final RuleKey key;
+ private final String name;
+ private final String descriptionUrl;
+ private final String severity;
+ private final RuleType type;
+
+ private NewExternalRule(Builder builder) {
+ this.key = checkNotNull(builder.key, "key");
+ this.name = checkNotEmpty(builder.name, "name");
+ this.descriptionUrl = builder.descriptionUrl;
+ this.severity = checkNotEmpty(builder.severity, "severity");
+ this.type = checkNotNull(builder.type, "type");
+ }
+
+ private static String checkNotEmpty(String str, String name) {
+ if (StringUtils.isEmpty(str)) {
+ throw new IllegalStateException("'" + name + "' not expected to be empty for an external rule");
+ }
+ return str;
+ }
+
+ private static <T> T checkNotNull(T obj, String name) {
+ if (obj == null) {
+ throw new IllegalStateException("'" + name + "' not expected to be null for an external rule");
+ }
+ return obj;
+ }
+
+ @CheckForNull
+ public String getDescriptionUrl() {
+ return descriptionUrl;
+ }
+
+ public String getSeverity() {
+ return severity;
+ }
+
+ @Override
+ public int getId() {
+ return 0;
+ }
+
+ @Override
+ public RuleKey getKey() {
+ return key;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public RuleStatus getStatus() {
+ return RuleStatus.defaultStatus();
+ }
+
+ @Override
+ public RuleType getType() {
+ return type;
+ }
+
+ @Override
+ public boolean isExternal() {
+ return true;
+ }
+
+ @Override
+ public Set<String> getTags() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public DebtRemediationFunction getRemediationFunction() {
+ return null;
+ }
+
+ @Override
+ public String getPluginKey() {
+ return null;
+ }
+
+ public static class Builder {
+ private RuleKey key;
+ private String name;
+ private String descriptionUrl;
+ private String severity;
+ private RuleType type;
+
+ public Builder setKey(RuleKey key) {
+ this.key = key;
+ return this;
+ }
+
+ public Builder setName(String name) {
+ this.name = StringUtils.trimToNull(name);
+ return this;
+ }
+
+ public Builder setDescriptionUrl(String descriptionUrl) {
+ this.descriptionUrl = StringUtils.trimToNull(descriptionUrl);
+ return this;
+ }
+
+ public Builder setSeverity(String severity) {
+ this.severity = StringUtils.trimToNull(severity);
+ return this;
+ }
+
+ public Builder setType(RuleType type) {
+ this.type = type;
+ return this;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public String descriptionUrl() {
+ return descriptionUrl;
+ }
+
+ public String severity() {
+ return severity;
+ }
+
+ public RuleType type() {
+ return type;
+ }
+
+ public NewExternalRule build() {
+ return new NewExternalRule(this);
+ }
+ }
+}
RuleStatus getStatus();
RuleType getType();
+
+ boolean isExternal();
/**
* Get all tags, whatever system or user tags.
private final DebtRemediationFunction remediationFunction;
private final RuleType type;
private final String pluginKey;
+ private final boolean external;
public RuleImpl(RuleDto dto) {
this.id = dto.getId();
this.remediationFunction = effectiveRemediationFunction(dto);
this.type = RuleType.valueOf(dto.getType());
this.pluginKey = dto.getPluginKey();
+ // TODO
+ this.external = false;
}
@Override
}
return null;
}
+
+ @Override
+ public boolean isExternal() {
+ return external;
+ }
}
package org.sonar.server.computation.task.projectanalysis.issue;
import java.util.Optional;
+import java.util.function.Supplier;
import org.sonar.api.rule.RuleKey;
/**
Optional<Rule> findByKey(RuleKey key);
Optional<Rule> findById(int id);
+
+ void insertNewExternalRuleIfAbsent(RuleKey ruleKey, Supplier<NewExternalRule> ruleSupplier);
}
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
+import java.util.function.Supplier;
import javax.annotation.CheckForNull;
import org.sonar.api.rule.RuleKey;
import org.sonar.core.util.stream.MoreCollectors;
private Map<RuleKey, Rule> rulesByKey;
@CheckForNull
private Map<Integer, Rule> rulesById;
+ @CheckForNull
+ private Map<RuleKey, NewExternalRule> newExternalRulesByKey;
private final DbClient dbClient;
private final AnalysisMetadataHolder analysisMetadataHolder;
this.analysisMetadataHolder = analysisMetadataHolder;
}
+ public void insertNewExternalRuleIfAbsent(RuleKey ruleKey, Supplier<NewExternalRule> ruleSupplier) {
+ if (!rulesByKey.containsKey(ruleKey)) {
+ newExternalRulesByKey.computeIfAbsent(ruleKey, s -> ruleSupplier.get());
+ }
+ }
+
@Override
public Rule getByKey(RuleKey key) {
verifyKeyArgument(key);
}
this.rulesByKey = rulesByKeyBuilder.build();
this.rulesById = rulesByIdBuilder.build();
+ this.newExternalRulesByKey = new LinkedHashMap<>();
}
}
if (issue.isNew()) {
// analyzer can provide some tags. They must be merged with rule tags
Rule rule = ruleRepository.getByKey(issue.ruleKey());
- issue.setTags(union(issue.tags(), rule.getTags()));
+ if (!rule.isExternal()) {
+ issue.setTags(union(issue.tags(), rule.getTags()));
+ }
}
}
}
public void onIssue(Component component, DefaultIssue issue) {
if (issue.type() == null) {
Rule rule = ruleRepository.getByKey(issue.ruleKey());
- issue.setType(rule.getType());
+ if (!rule.isExternal()) {
+ issue.setType(rule.getType());
+ }
}
}
}
private final SourceLinesRepository sourceLinesRepository;
private final CommonRuleEngine commonRuleEngine;
private final IssueFilter issueFilter;
+ private final RuleRepository ruleRepository;
public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader,
- SourceLinesRepository sourceLinesRepository, CommonRuleEngine commonRuleEngine, IssueFilter issueFilter) {
+ SourceLinesRepository sourceLinesRepository, CommonRuleEngine commonRuleEngine, IssueFilter issueFilter, RuleRepository ruleRepository) {
this.treeRootHolder = treeRootHolder;
this.reportReader = reportReader;
this.sourceLinesRepository = sourceLinesRepository;
this.commonRuleEngine = commonRuleEngine;
this.issueFilter = issueFilter;
+ this.ruleRepository = ruleRepository;
}
public Input<DefaultIssue> create(Component component) {
// as late as possible
while (reportIssues.hasNext()) {
ScannerReport.ExternalIssue reportExternalIssue = reportIssues.next();
- DefaultIssue issue = toIssue(getLineHashSequence(), reportExternalIssue);
- result.add(issue);
+ result.add(toExternalIssue(getLineHashSequence(), reportExternalIssue));
}
}
return issue;
}
- private DefaultIssue toIssue(LineHashSequence lineHashSeq, ScannerReport.ExternalIssue reportIssue) {
+ private DefaultIssue toExternalIssue(LineHashSequence lineHashSeq, ScannerReport.ExternalIssue reportIssue) {
DefaultIssue issue = new DefaultIssue();
init(issue);
- issue.setRuleKey(RuleKey.of(reportIssue.getRuleRepository(), reportIssue.getRuleKey()));
+
+ issue.setRuleKey(RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportIssue.getRuleRepository(), reportIssue.getRuleKey()));
if (reportIssue.hasTextRange()) {
int startLine = reportIssue.getTextRange().getStartLine();
issue.setLine(startLine);
}
issue.setLocations(dbLocationsBuilder.build());
issue.setType(toRuleType(reportIssue.getType()));
- issue.setDescriptionUrl(StringUtils.stripToNull(reportIssue.getDescriptionUrl()));
+
+ ruleRepository.insertNewExternalRuleIfAbsent(issue.getRuleKey(), () -> toExternalRule(reportIssue));
return issue;
}
+ private NewExternalRule toExternalRule(ScannerReport.ExternalIssue reportIssue) {
+ NewExternalRule.Builder builder = new NewExternalRule.Builder()
+ .setDescriptionUrl(StringUtils.stripToNull(reportIssue.getDescriptionUrl()))
+ .setType(toRuleType(reportIssue.getType()))
+ .setKey(RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportIssue.getRuleRepository(), reportIssue.getRuleKey()))
+ .setPluginKey(reportIssue.getRuleRepository())
+ .setName(reportIssue.getRuleName());
+
+ if (reportIssue.getSeverity() != Severity.UNSET_SEVERITY) {
+ builder.setSeverity(reportIssue.getSeverity().name());
+ }
+ return builder.build();
+ }
+
private RuleType toRuleType(IssueType type) {
switch (type) {
case BUG:
while (batchActiveRules.hasNext()) {
ScannerReport.ActiveRule scannerReportActiveRule = batchActiveRules.next();
Optional<Rule> rule = ruleRepository.findByKey(RuleKey.of(scannerReportActiveRule.getRuleRepository(), scannerReportActiveRule.getRuleKey()));
- if (rule.isPresent() && rule.get().getStatus() != RuleStatus.REMOVED) {
+ if (rule.isPresent() && rule.get().getStatus() != RuleStatus.REMOVED && !rule.get().isExternal()) {
ActiveRule activeRule = convert(scannerReportActiveRule, rule.get());
activeRules.add(activeRule);
}
private Set<String> tags = new HashSet<>();
private DebtRemediationFunction function;
private String pluginKey;
+ private boolean isExternal;
public DumbRule(RuleKey key) {
this.key = key;
return pluginKey;
}
+ @Override
+ public boolean isExternal() {
+ return isExternal;
+ }
+
public DumbRule setId(Integer id) {
this.id = id;
return this;
return this;
}
+ public DumbRule setIsExtenral(boolean isExternal) {
+ this.isExternal = isExternal;
+ return this;
+ }
+
}
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
public RuleRepositoryRule ruleRepositoryRule = new RuleRepositoryRule();
@Rule
public SourceLinesRepositoryRule fileSourceRepository = new SourceLinesRepositoryRule();
+ @Rule
+ public RuleRepositoryRule ruleRepository = new RuleRepositoryRule();
- @Mock
- private AnalysisMetadataHolder analysisMetadataHolder;
- @Mock
- private IssueFilter issueFilter;
- @Mock
- private MovedFilesRepository movedFilesRepository;
- @Mock
- private IssueLifecycle issueLifecycle;
- @Mock
- private IssueVisitor issueVisitor;
- @Mock
- private MergeBranchComponentUuids mergeBranchComponentsUuids;
- @Mock
- private ShortBranchIssueMerger issueStatusCopier;
- @Mock
- private MergeBranchComponentUuids mergeBranchComponentUuids;
+ private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
+ private IssueFilter issueFilter = mock(IssueFilter.class);
+ private MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
+ private IssueLifecycle issueLifecycle = mock(IssueLifecycle.class);
+ private IssueVisitor issueVisitor = mock(IssueVisitor.class);
+ private MergeBranchComponentUuids mergeBranchComponentsUuids = mock(MergeBranchComponentUuids.class);
+ private ShortBranchIssueMerger issueStatusCopier = mock(ShortBranchIssueMerger.class);
+ private MergeBranchComponentUuids mergeBranchComponentUuids = mock(MergeBranchComponentUuids.class);
ArgumentCaptor<DefaultIssue> defaultIssueCaptor;
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
IssueVisitors issueVisitors = new IssueVisitors(new IssueVisitor[] {issueVisitor});
defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
when(movedFilesRepository.getOriginalFile(any(Component.class))).thenReturn(Optional.absent());
- TrackerRawInputFactory rawInputFactory = new TrackerRawInputFactory(treeRootHolder, reportReader, fileSourceRepository, new CommonRuleEngineImpl(), issueFilter);
+ TrackerRawInputFactory rawInputFactory = new TrackerRawInputFactory(treeRootHolder, reportReader, fileSourceRepository, new CommonRuleEngineImpl(),
+ issueFilter, ruleRepository);
TrackerBaseInputFactory baseInputFactory = new TrackerBaseInputFactory(issuesLoader, dbTester.getDbClient(), movedFilesRepository);
TrackerMergeBranchInputFactory mergeInputFactory = new TrackerMergeBranchInputFactory(issuesLoader, mergeBranchComponentsUuids, dbTester.getDbClient());
tracker = new TrackerExecution(baseInputFactory, rawInputFactory, new Tracker<>());
--- /dev/null
+/*
+ * 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.server.computation.task.projectanalysis.issue;
+
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NewExternalRuleTest {
+ @org.junit.Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void should_build_new_external_rule() {
+ NewExternalRule.Builder builder = new NewExternalRule.Builder()
+ .setDescriptionUrl("url")
+ .setKey(RuleKey.of("repo", "rule"))
+ .setName("name")
+ .setSeverity("MAJOR")
+ .setType(RuleType.BUG);
+
+ assertThat(builder.descriptionUrl()).isEqualTo("url");
+ assertThat(builder.name()).isEqualTo("name");
+ assertThat(builder.severity()).isEqualTo("MAJOR");
+ assertThat(builder.type()).isEqualTo(RuleType.BUG);
+ assertThat(builder.descriptionUrl()).isEqualTo("url");
+
+ NewExternalRule rule = builder.build();
+
+ assertThat(rule.getDescriptionUrl()).isEqualTo("url");
+ assertThat(rule.getName()).isEqualTo("name");
+ assertThat(rule.getPluginKey()).isNull();
+ assertThat(rule.getSeverity()).isEqualTo("MAJOR");
+ assertThat(rule.getType()).isEqualTo(RuleType.BUG);
+ assertThat(rule.getDescriptionUrl()).isEqualTo("url");
+ }
+
+ @Test
+ public void fail_if_name_is_not_set() {
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("'name' not expected to be empty for an external rule");
+
+ new NewExternalRule.Builder()
+ .setDescriptionUrl("url")
+ .setKey(RuleKey.of("repo", "rule"))
+ .setSeverity("MAJOR")
+ .setType(RuleType.BUG)
+ .build();
+ }
+
+ @Test
+ public void fail_if_rule_key_is_not_set() {
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("'key' not expected to be null for an external rule");
+
+ new NewExternalRule.Builder()
+ .setDescriptionUrl("url")
+ .setName("name")
+ .setSeverity("MAJOR")
+ .setType(RuleType.BUG)
+ .build();
+ }
+}
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
+import java.util.function.Supplier;
import org.junit.rules.ExternalResource;
import org.sonar.api.rule.RuleKey;
private final Map<RuleKey, Rule> rulesByKey = new HashMap<>();
private final Map<Integer, Rule> rulesById = new HashMap<>();
+ private final Map<RuleKey, NewExternalRule> newExternalRulesById = new HashMap<>();
@Override
protected void after() {
return this;
}
+ @Override
+ public void insertNewExternalRuleIfAbsent(RuleKey ruleKey, Supplier<NewExternalRule> ruleSupplier) {
+ newExternalRulesById.computeIfAbsent(ruleKey, k -> ruleSupplier.get());
+ }
+
}
@Rule
public SourceLinesRepositoryRule fileSourceRepository = new SourceLinesRepositoryRule();
+
+ @Rule
+ public RuleRepositoryRule ruleRepository = new RuleRepositoryRule();
CommonRuleEngine commonRuleEngine = mock(CommonRuleEngine.class);
-
IssueFilter issueFilter = mock(IssueFilter.class);
-
- TrackerRawInputFactory underTest = new TrackerRawInputFactory(treeRootHolder, reportReader, fileSourceRepository, commonRuleEngine, issueFilter);
+ TrackerRawInputFactory underTest = new TrackerRawInputFactory(treeRootHolder, reportReader, fileSourceRepository, commonRuleEngine, issueFilter, ruleRepository);
@Test
public void load_source_hash_sequences() {