@@ -54,6 +54,7 @@ import org.sonar.xoo.rule.OneCodeSmellIssuePerTestLineSensor; | |||
import org.sonar.xoo.rule.OneDayDebtPerFileSensor; | |||
import org.sonar.xoo.rule.OneExternalIssueOnProjectSensor; | |||
import org.sonar.xoo.rule.OneExternalIssuePerLineSensor; | |||
import org.sonar.xoo.rule.OneExternalIssuePerLineWithoutMessageSensor; | |||
import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor; | |||
import org.sonar.xoo.rule.OneIssuePerDirectorySensor; | |||
import org.sonar.xoo.rule.OneIssuePerFileSensor; | |||
@@ -147,6 +148,7 @@ public class XooPlugin implements Plugin { | |||
OneIssuePerUnknownFileSensor.class, | |||
OneExternalIssuePerLineSensor.class, | |||
OneExternalIssuePerLineWithoutMessageSensor.class, | |||
OneExternalIssueOnProjectSensor.class, | |||
OnePredefinedRuleExternalIssuePerLineSensor.class, | |||
OnePredefinedAndAdHocRuleExternalIssuePerLineSensor.class, |
@@ -0,0 +1,86 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.xoo.rule; | |||
import org.sonar.api.batch.fs.FilePredicates; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.batch.fs.InputFile; | |||
import org.sonar.api.batch.rule.Severity; | |||
import org.sonar.api.batch.sensor.Sensor; | |||
import org.sonar.api.batch.sensor.SensorContext; | |||
import org.sonar.api.batch.sensor.SensorDescriptor; | |||
import org.sonar.api.batch.sensor.issue.NewExternalIssue; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.xoo.Xoo; | |||
public class OneExternalIssuePerLineWithoutMessageSensor implements Sensor { | |||
public static final String RULE_ID = "oneExternalIssuePerLineWithoutMessage"; | |||
public static final String ENGINE_ID = "XooEngine"; | |||
public static final String SEVERITY = "MAJOR"; | |||
public static final Long EFFORT = 10L; | |||
public static final RuleType TYPE = RuleType.BUG; | |||
public static final String ACTIVATE = "sonar.oneExternalIssuePerLineWithoutMessage.activate"; | |||
public static final String REGISTER_AD_HOC_RULE = "sonar.oneExternalIssuePerLineWithoutMessage.adhocRule"; | |||
private static final String NAME = "One External Issue Per Line Without Message"; | |||
@Override | |||
public void describe(SensorDescriptor descriptor) { | |||
descriptor | |||
.name(NAME) | |||
.onlyOnLanguages(Xoo.KEY) | |||
.onlyWhenConfiguration(c -> c.getBoolean(ACTIVATE).orElse(false)); | |||
} | |||
@Override | |||
public void execute(SensorContext context) { | |||
FileSystem fs = context.fileSystem(); | |||
FilePredicates p = fs.predicates(); | |||
for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(Xoo.KEY), p.hasType(InputFile.Type.MAIN)))) { | |||
createIssues(file, context); | |||
} | |||
if (context.config().getBoolean(REGISTER_AD_HOC_RULE).orElse(false)) { | |||
context.newAdHocRule() | |||
.engineId(ENGINE_ID) | |||
.ruleId(RULE_ID) | |||
.name("An ad hoc rule") | |||
.description("blah blah") | |||
.severity(Severity.BLOCKER) | |||
.type(RuleType.BUG) | |||
.save(); | |||
} | |||
} | |||
private static void createIssues(InputFile file, SensorContext context) { | |||
for (int line = 1; line <= file.lines(); line++) { | |||
NewExternalIssue newIssue = context.newExternalIssue(); | |||
newIssue | |||
.engineId(ENGINE_ID) | |||
.ruleId(RULE_ID) | |||
.at(newIssue.newLocation() | |||
.on(file) | |||
.at(file.selectLine(line)) | |||
.message("")) | |||
.severity(Severity.valueOf(SEVERITY)) | |||
.remediationEffortMinutes(EFFORT) | |||
.type(TYPE) | |||
.save(); | |||
} | |||
} | |||
} |
@@ -74,6 +74,9 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> { | |||
if (this == o) { | |||
return true; | |||
} | |||
if (o == null || getClass() != o.getClass()) { | |||
return false; | |||
} | |||
LineAndLineHashKey that = (LineAndLineHashKey) o; | |||
// start with most discriminant field | |||
return Objects.equals(line, that.line) && lineHash.equals(that.lineHash) && ruleKey.equals(that.ruleKey); | |||
@@ -108,7 +111,7 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> { | |||
} | |||
LineAndLineHashAndMessage that = (LineAndLineHashAndMessage) o; | |||
// start with most discriminant field | |||
return Objects.equals(line, that.line) && lineHash.equals(that.lineHash) && message.equals(that.message) && ruleKey.equals(that.ruleKey); | |||
return Objects.equals(line, that.line) && lineHash.equals(that.lineHash) && Objects.equals(message, that.message) && ruleKey.equals(that.ruleKey); | |||
} | |||
@Override | |||
@@ -138,7 +141,7 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> { | |||
} | |||
LineHashAndMessageKey that = (LineHashAndMessageKey) o; | |||
// start with most discriminant field | |||
return lineHash.equals(that.lineHash) && message.equals(that.message) && ruleKey.equals(that.ruleKey); | |||
return lineHash.equals(that.lineHash) && Objects.equals(message, that.message) && ruleKey.equals(that.ruleKey); | |||
} | |||
@Override | |||
@@ -168,7 +171,7 @@ public class AbstractTracker<RAW extends Trackable, BASE extends Trackable> { | |||
} | |||
LineAndMessageKey that = (LineAndMessageKey) o; | |||
// start with most discriminant field | |||
return Objects.equals(line, that.line) && message.equals(that.message) && ruleKey.equals(that.ruleKey); | |||
return Objects.equals(line, that.line) && Objects.equals(message, that.message) && ruleKey.equals(that.ruleKey); | |||
} | |||
@Override |
@@ -0,0 +1,131 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.core.issue.tracking; | |||
import java.util.function.Function; | |||
import org.junit.Test; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.core.issue.tracking.AbstractTracker.LineAndLineHashAndMessage; | |||
import org.sonar.core.issue.tracking.AbstractTracker.LineAndLineHashKey; | |||
import org.sonar.core.issue.tracking.AbstractTracker.LineAndMessageKey; | |||
import org.sonar.core.issue.tracking.AbstractTracker.LineHashAndMessageKey; | |||
import org.sonar.core.issue.tracking.AbstractTracker.LineHashKey; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class AbstractTrackerTest { | |||
private final Trackable base = trackable(RuleKey.of("r1", "r1"), 0, "m1", "hash1"); | |||
private final Trackable same = trackable(RuleKey.of("r1", "r1"), 0, "m1", "hash1"); | |||
private final Trackable diffRule = trackable(RuleKey.of("r1", "r2"), 0, "m1", "hash1"); | |||
private final Trackable diffMessage = trackable(RuleKey.of("r1", "r1"), 0, null, "hash1"); | |||
private final Trackable diffLineHash = trackable(RuleKey.of("r1", "r1"), 0, "m1", null); | |||
private final Trackable diffLine = trackable(RuleKey.of("r1", "r1"), null, "m1", "hash1"); | |||
@Test | |||
public void lineAndLineHashKey() { | |||
Comparator comparator = new Comparator(LineAndLineHashKey::new); | |||
comparator.sameEqualsAndHashcode(base, same); | |||
comparator.sameEqualsAndHashcode(base, diffMessage); | |||
comparator.differentEquals(base, diffRule); | |||
comparator.differentEquals(base, diffLineHash); | |||
comparator.differentEquals(base, diffLine); | |||
} | |||
@Test | |||
public void lineAndLineHashAndMessage() { | |||
Comparator comparator = new Comparator(LineAndLineHashAndMessage::new); | |||
comparator.sameEqualsAndHashcode(base, same); | |||
comparator.differentEquals(base, diffMessage); | |||
comparator.differentEquals(base, diffRule); | |||
comparator.differentEquals(base, diffLineHash); | |||
comparator.differentEquals(base, diffLine); | |||
} | |||
@Test | |||
public void lineHashAndMessageKey() { | |||
Comparator comparator = new Comparator(LineHashAndMessageKey::new); | |||
comparator.sameEqualsAndHashcode(base, same); | |||
comparator.sameEqualsAndHashcode(base, diffLine); | |||
comparator.differentEquals(base, diffMessage); | |||
comparator.differentEquals(base, diffRule); | |||
comparator.differentEquals(base, diffLineHash); | |||
} | |||
@Test | |||
public void lineAndMessageKey() { | |||
Comparator comparator = new Comparator(LineAndMessageKey::new); | |||
comparator.sameEqualsAndHashcode(base, same); | |||
comparator.sameEqualsAndHashcode(base, diffLineHash); | |||
comparator.differentEquals(base, diffMessage); | |||
comparator.differentEquals(base, diffRule); | |||
comparator.differentEquals(base, diffLine); | |||
} | |||
@Test | |||
public void lineHashKey() { | |||
Comparator comparator = new Comparator(LineHashKey::new); | |||
comparator.sameEqualsAndHashcode(base, same); | |||
comparator.sameEqualsAndHashcode(base, diffLine); | |||
comparator.sameEqualsAndHashcode(base, diffMessage); | |||
comparator.differentEquals(base, diffRule); | |||
comparator.differentEquals(base, diffLineHash); | |||
} | |||
private static Trackable trackable(RuleKey ruleKey, Integer line, String message, String lineHash) { | |||
Trackable trackable = mock(Trackable.class); | |||
when(trackable.getRuleKey()).thenReturn(ruleKey); | |||
when(trackable.getLine()).thenReturn(line); | |||
when(trackable.getMessage()).thenReturn(message); | |||
when(trackable.getLineHash()).thenReturn(lineHash); | |||
return trackable; | |||
} | |||
private static class Comparator { | |||
private final Function<Trackable, AbstractTracker.SearchKey> searchKeyFactory; | |||
private Comparator(Function<Trackable, AbstractTracker.SearchKey> searchKeyFactory) { | |||
this.searchKeyFactory = searchKeyFactory; | |||
} | |||
private void sameEqualsAndHashcode(Trackable t1, Trackable t2) { | |||
AbstractTracker.SearchKey k1 = searchKeyFactory.apply(t1); | |||
AbstractTracker.SearchKey k2 = searchKeyFactory.apply(t2); | |||
assertThat(k1).isEqualTo(k1); | |||
assertThat(k1).isEqualTo(k2); | |||
assertThat(k2).isEqualTo(k1); | |||
assertThat(k1).hasSameHashCodeAs(k1); | |||
assertThat(k1).hasSameHashCodeAs(k2); | |||
assertThat(k2).hasSameHashCodeAs(k1); | |||
} | |||
private void differentEquals(Trackable t1, Trackable t2) { | |||
AbstractTracker.SearchKey k1 = searchKeyFactory.apply(t1); | |||
AbstractTracker.SearchKey k2 = searchKeyFactory.apply(t2); | |||
assertThat(k1).isNotEqualTo(k2); | |||
assertThat(k2).isNotEqualTo(k1); | |||
assertThat(k1).isNotEqualTo(new Object()); | |||
assertThat(k1).isNotEqualTo(null); | |||
} | |||
} | |||
} |
@@ -23,6 +23,7 @@ import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Date; | |||
import java.util.List; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.codec.digest.DigestUtils; | |||
import org.junit.Rule; | |||
@@ -185,6 +186,19 @@ public class TrackerTest { | |||
assertThat(tracking.getUnmatchedBases()).containsOnly(base); | |||
} | |||
// SONAR-15091 | |||
@Test | |||
public void do_not_fail_if_message_is_null() { | |||
FakeInput baseInput = new FakeInput("H1", "H2"); | |||
Issue base = baseInput.createIssueOnLine(1, RULE_UNUSED_LOCAL_VARIABLE, null); | |||
FakeInput rawInput = new FakeInput("H1", "H2"); | |||
Issue raw = rawInput.createIssueOnLine(1, RULE_UNUSED_LOCAL_VARIABLE, null); | |||
Tracking<Issue, Issue> tracking = tracker.trackNonClosed(rawInput, baseInput); | |||
assertThat(tracking.baseFor(raw)).isNotNull(); | |||
} | |||
@Test | |||
public void do_not_fail_if_raw_line_does_not_exist() { | |||
FakeInput baseInput = new FakeInput(); | |||
@@ -456,7 +470,7 @@ public class TrackerTest { | |||
private final String status; | |||
private final Date updateDate; | |||
Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message, String status, Date updateDate) { | |||
Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, @Nullable String message, String status, Date updateDate) { | |||
this.line = line; | |||
this.lineHash = lineHash; | |||
this.ruleKey = ruleKey; | |||
@@ -470,6 +484,7 @@ public class TrackerTest { | |||
return line; | |||
} | |||
@CheckForNull | |||
@Override | |||
public String getMessage() { | |||
return message; | |||
@@ -521,7 +536,7 @@ public class TrackerTest { | |||
/** | |||
* No line (line 0) | |||
*/ | |||
Issue createIssue(RuleKey ruleKey, String message) { | |||
Issue createIssue(RuleKey ruleKey, @Nullable String message) { | |||
Issue issue = new Issue(null, "", ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN, new Date()); | |||
issues.add(issue); | |||
return issue; |