import org.sonar.xoo.rule.OneDayDebtPerFileSensor; | import org.sonar.xoo.rule.OneDayDebtPerFileSensor; | ||||
import org.sonar.xoo.rule.OneExternalIssueOnProjectSensor; | import org.sonar.xoo.rule.OneExternalIssueOnProjectSensor; | ||||
import org.sonar.xoo.rule.OneExternalIssuePerLineSensor; | import org.sonar.xoo.rule.OneExternalIssuePerLineSensor; | ||||
import org.sonar.xoo.rule.OneExternalIssuePerLineWithoutMessageSensor; | |||||
import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor; | import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor; | ||||
import org.sonar.xoo.rule.OneIssuePerDirectorySensor; | import org.sonar.xoo.rule.OneIssuePerDirectorySensor; | ||||
import org.sonar.xoo.rule.OneIssuePerFileSensor; | import org.sonar.xoo.rule.OneIssuePerFileSensor; | ||||
OneIssuePerUnknownFileSensor.class, | OneIssuePerUnknownFileSensor.class, | ||||
OneExternalIssuePerLineSensor.class, | OneExternalIssuePerLineSensor.class, | ||||
OneExternalIssuePerLineWithoutMessageSensor.class, | |||||
OneExternalIssueOnProjectSensor.class, | OneExternalIssueOnProjectSensor.class, | ||||
OnePredefinedRuleExternalIssuePerLineSensor.class, | OnePredefinedRuleExternalIssuePerLineSensor.class, | ||||
OnePredefinedAndAdHocRuleExternalIssuePerLineSensor.class, | OnePredefinedAndAdHocRuleExternalIssuePerLineSensor.class, |
/* | |||||
* 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(); | |||||
} | |||||
} | |||||
} |
if (this == o) { | if (this == o) { | ||||
return true; | return true; | ||||
} | } | ||||
if (o == null || getClass() != o.getClass()) { | |||||
return false; | |||||
} | |||||
LineAndLineHashKey that = (LineAndLineHashKey) o; | LineAndLineHashKey that = (LineAndLineHashKey) o; | ||||
// start with most discriminant field | // start with most discriminant field | ||||
return Objects.equals(line, that.line) && lineHash.equals(that.lineHash) && ruleKey.equals(that.ruleKey); | return Objects.equals(line, that.line) && lineHash.equals(that.lineHash) && ruleKey.equals(that.ruleKey); | ||||
} | } | ||||
LineAndLineHashAndMessage that = (LineAndLineHashAndMessage) o; | LineAndLineHashAndMessage that = (LineAndLineHashAndMessage) o; | ||||
// start with most discriminant field | // 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 | @Override | ||||
} | } | ||||
LineHashAndMessageKey that = (LineHashAndMessageKey) o; | LineHashAndMessageKey that = (LineHashAndMessageKey) o; | ||||
// start with most discriminant field | // 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 | @Override | ||||
} | } | ||||
LineAndMessageKey that = (LineAndMessageKey) o; | LineAndMessageKey that = (LineAndMessageKey) o; | ||||
// start with most discriminant field | // 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 | @Override |
/* | |||||
* 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); | |||||
} | |||||
} | |||||
} |
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Date; | import java.util.Date; | ||||
import java.util.List; | import java.util.List; | ||||
import javax.annotation.CheckForNull; | |||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.apache.commons.codec.digest.DigestUtils; | import org.apache.commons.codec.digest.DigestUtils; | ||||
import org.junit.Rule; | import org.junit.Rule; | ||||
assertThat(tracking.getUnmatchedBases()).containsOnly(base); | 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 | @Test | ||||
public void do_not_fail_if_raw_line_does_not_exist() { | public void do_not_fail_if_raw_line_does_not_exist() { | ||||
FakeInput baseInput = new FakeInput(); | FakeInput baseInput = new FakeInput(); | ||||
private final String status; | private final String status; | ||||
private final Date updateDate; | 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.line = line; | ||||
this.lineHash = lineHash; | this.lineHash = lineHash; | ||||
this.ruleKey = ruleKey; | this.ruleKey = ruleKey; | ||||
return line; | return line; | ||||
} | } | ||||
@CheckForNull | |||||
@Override | @Override | ||||
public String getMessage() { | public String getMessage() { | ||||
return message; | return message; | ||||
/** | /** | ||||
* No line (line 0) | * 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()); | Issue issue = new Issue(null, "", ruleKey, message, org.sonar.api.issue.Issue.STATUS_OPEN, new Date()); | ||||
issues.add(issue); | issues.add(issue); | ||||
return issue; | return issue; |