aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-plugin-api-impl
diff options
context:
space:
mode:
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>2022-11-23 15:57:02 +0100
committersonartech <sonartech@sonarsource.com>2022-12-01 20:03:11 +0000
commit66b8bff0dcc778eb93da92cd408214bbfca82c2a (patch)
treee60388bdf0bdbc60434d00eae81b8e6dffe39fad /sonar-plugin-api-impl
parent24d4519ea1b8a4426dd90e0bb80b2b09bd7a1c12 (diff)
downloadsonarqube-66b8bff0dcc778eb93da92cd408214bbfca82c2a.tar.gz
sonarqube-66b8bff0dcc778eb93da92cd408214bbfca82c2a.zip
SONAR-17592 Persist Message formatting from plugin api
Diffstat (limited to 'sonar-plugin-api-impl')
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java24
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java18
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java44
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultMessageFormatting.java71
-rw-r--r--sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java25
-rw-r--r--sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultMessageFormattingTest.java65
6 files changed, 237 insertions, 10 deletions
diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java
index cc77d90a02f..186ad5080b8 100644
--- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java
+++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java
@@ -25,6 +25,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.internal.DefaultInputDir;
@@ -34,8 +35,10 @@ import org.sonar.api.batch.sensor.internal.DefaultStorable;
import org.sonar.api.batch.sensor.internal.SensorStorage;
import org.sonar.api.batch.sensor.issue.Issue.Flow;
import org.sonar.api.batch.sensor.issue.IssueLocation;
+import org.sonar.api.batch.sensor.issue.MessageFormatting;
import org.sonar.api.batch.sensor.issue.NewIssue.FlowType;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
+import org.sonar.api.batch.sensor.issue.NewMessageFormatting;
import org.sonar.api.utils.PathUtils;
import static org.sonar.api.utils.Preconditions.checkArgument;
@@ -107,14 +110,31 @@ public abstract class AbstractDefaultIssue<T extends AbstractDefaultIssue> exten
DefaultIssueLocation fixedLocation = new DefaultIssueLocation();
fixedLocation.on(project);
StringBuilder fullMessage = new StringBuilder();
+ String prefixMessage;
if (path != null && !path.isEmpty()) {
- fullMessage.append("[").append(path).append("] ");
+ prefixMessage = "[" + path + "] ";
+ } else {
+ prefixMessage = "";
}
+
+ fullMessage.append(prefixMessage);
fullMessage.append(location.message());
- fixedLocation.message(fullMessage.toString());
+
+ List<NewMessageFormatting> paddedFormattings = location.messageFormattings().stream()
+ .map(m -> padMessageFormatting(m, prefixMessage.length()))
+ .collect(Collectors.toList());
+
+ fixedLocation.message(fullMessage.toString(), paddedFormattings);
+
return fixedLocation;
} else {
return location;
}
}
+
+ private static NewMessageFormatting padMessageFormatting(MessageFormatting messageFormatting, int length) {
+ return new DefaultMessageFormatting().type(messageFormatting.type())
+ .start(messageFormatting.start() + length)
+ .end(messageFormatting.end() + length);
+ }
}
diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java
index 4f2598d6683..ed55c0d09af 100644
--- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java
+++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java
@@ -19,6 +19,7 @@
*/
package org.sonar.api.batch.sensor.issue.internal;
+import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.api.batch.fs.internal.DefaultInputProject;
@@ -27,6 +28,8 @@ import org.sonar.api.batch.sensor.internal.SensorStorage;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.issue.IssueLocation;
import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.batch.sensor.issue.fix.NewQuickFix;
+import org.sonar.api.batch.sensor.issue.fix.QuickFix;
import org.sonar.api.rule.RuleKey;
import static java.lang.String.format;
@@ -78,6 +81,16 @@ public class DefaultIssue extends AbstractDefaultIssue<DefaultIssue> implements
}
@Override
+ public NewQuickFix newQuickFix() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public NewIssue addQuickFix(NewQuickFix newQuickFix) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public DefaultIssue setRuleDescriptionContextKey(@Nullable String ruleDescriptionContextKey) {
this.ruleDescriptionContextKey = ruleDescriptionContextKey;
return this;
@@ -94,6 +107,11 @@ public class DefaultIssue extends AbstractDefaultIssue<DefaultIssue> implements
}
@Override
+ public List<QuickFix> quickFixes() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public Severity overriddenSeverity() {
return this.overriddenSeverity;
}
diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java
index 19160b6757b..89f1c9550f3 100644
--- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java
+++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java
@@ -19,12 +19,16 @@
*/
package org.sonar.api.batch.sensor.issue.internal;
+import java.util.ArrayList;
+import java.util.List;
import javax.annotation.Nullable;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.TextRange;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.issue.IssueLocation;
+import org.sonar.api.batch.sensor.issue.MessageFormatting;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.issue.NewMessageFormatting;
import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang.StringUtils.abbreviate;
@@ -37,6 +41,7 @@ public class DefaultIssueLocation implements NewIssueLocation, IssueLocation {
private InputComponent component;
private TextRange textRange;
private String message;
+ private final List<MessageFormatting> messageFormattings = new ArrayList<>();
@Override
public DefaultIssueLocation on(InputComponent component) {
@@ -58,12 +63,40 @@ public class DefaultIssueLocation implements NewIssueLocation, IssueLocation {
@Override
public DefaultIssueLocation message(String message) {
+ validateMessage(message);
+ this.message = abbreviate(trim(message), MESSAGE_MAX_SIZE);
+ return this;
+ }
+
+ @Override
+ public NewIssueLocation message(String message, List<NewMessageFormatting> newMessageFormattings) {
+ validateMessage(message);
+ validateFormattings(newMessageFormattings, message);
+ this.message = abbreviate(trim(message), MESSAGE_MAX_SIZE);
+
+ for (NewMessageFormatting newMessageFormatting : newMessageFormattings) {
+ messageFormattings.add((MessageFormatting) newMessageFormatting);
+ }
+ return this;
+ }
+
+ private static void validateFormattings(List<NewMessageFormatting> newMessageFormattings, String message) {
+ checkArgument(newMessageFormattings != null, "messageFormattings can't be null");
+ newMessageFormattings.stream()
+ .map(DefaultMessageFormatting.class::cast)
+ .forEach(e -> e.validate(message));
+ }
+
+ private void validateMessage(String message) {
requireNonNull(message, "Message can't be null");
if (message.contains("\u0000")) {
throw new IllegalArgumentException(unsupportedCharacterError(message, component));
}
- this.message = abbreviate(trim(message), MESSAGE_MAX_SIZE);
- return this;
+ }
+
+ @Override
+ public NewMessageFormatting newMessageFormatting() {
+ return new DefaultMessageFormatting();
}
private static String unsupportedCharacterError(String message, @Nullable InputComponent component) {
@@ -89,4 +122,9 @@ public class DefaultIssueLocation implements NewIssueLocation, IssueLocation {
return this.message;
}
+ @Override
+ public List<MessageFormatting> messageFormattings() {
+ return this.messageFormattings;
+ }
+
}
diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultMessageFormatting.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultMessageFormatting.java
new file mode 100644
index 00000000000..cf58777088e
--- /dev/null
+++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultMessageFormatting.java
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.issue.internal;
+
+import org.sonar.api.batch.sensor.issue.MessageFormatting;
+import org.sonar.api.batch.sensor.issue.NewMessageFormatting;
+
+import static org.sonar.api.utils.Preconditions.checkArgument;
+
+public class DefaultMessageFormatting implements MessageFormatting, NewMessageFormatting {
+ private int start;
+ private int end;
+ private Type type;
+
+ @Override
+ public int start() {
+ return start;
+ }
+
+ @Override
+ public int end() {
+ return end;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public DefaultMessageFormatting start(int start) {
+ this.start = start;
+ return this;
+ }
+
+ @Override
+ public DefaultMessageFormatting end(int end) {
+ this.end = end;
+ return this;
+ }
+
+ @Override
+ public DefaultMessageFormatting type(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ public void validate(String message) {
+ checkArgument(this.type() != null, "Message formatting type can't be null");
+ checkArgument(this.start() >= 0, "Message formatting start must be greater or equals to 0");
+ checkArgument(this.end() <= message.length(), "Message formatting end must be lesser or equal than message size");
+ checkArgument(this.end() > this.start(), "Message formatting end must be greater than start");
+ }
+}
diff --git a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java
index 1f874295e48..6e40c170065 100644
--- a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java
+++ b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueTest.java
@@ -37,10 +37,10 @@ import org.sonar.api.batch.fs.internal.DefaultTextRange;
import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
import org.sonar.api.batch.rule.Severity;
import org.sonar.api.batch.sensor.internal.SensorStorage;
-import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.issue.Issue.Flow;
-import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.batch.sensor.issue.MessageFormatting;
import org.sonar.api.batch.sensor.issue.NewIssue.FlowType;
+import org.sonar.api.batch.sensor.issue.fix.NewQuickFix;
import org.sonar.api.rule.RuleKey;
import static org.assertj.core.api.Assertions.assertThat;
@@ -60,6 +60,8 @@ public class DefaultIssueTest {
.build();
private DefaultInputProject project;
+ private final NewQuickFix quickFix = mock(NewQuickFix.class);
+
@Before
public void prepare() throws IOException {
project = new DefaultInputProject(ProjectDefinition.create()
@@ -164,16 +166,19 @@ public class DefaultIssueTest {
DefaultIssue issue = new DefaultIssue(project, storage)
.at(new DefaultIssueLocation()
.on(subModule)
- .message("Wrong way!"))
+ .message("Wrong way! with code snippet", List.of(new DefaultMessageFormatting().start(16).end(27).type(MessageFormatting.Type.CODE))))
.forRule(RULE_KEY)
.overrideSeverity(Severity.BLOCKER);
assertThat(issue.primaryLocation().inputComponent()).isEqualTo(project);
assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "rule"));
assertThat(issue.primaryLocation().textRange()).isNull();
- assertThat(issue.primaryLocation().message()).isEqualTo("[bar] Wrong way!");
+ assertThat(issue.primaryLocation().message()).isEqualTo("[bar] Wrong way! with code snippet");
assertThat(issue.overriddenSeverity()).isEqualTo(Severity.BLOCKER);
-
+ assertThat(issue.primaryLocation().messageFormattings().get(0)).extracting(MessageFormatting::start,
+ MessageFormatting::end, MessageFormatting::type)
+ .as("Formatting ranges are padded with the new message")
+ .containsExactly(22, 33, MessageFormatting.Type.CODE);
issue.save();
verify(storage).store(issue);
@@ -227,4 +232,14 @@ public class DefaultIssueTest {
assertThat(issue.isQuickFixAvailable()).isTrue();
}
+ @Test
+ public void quickfix_not_supported_for_now() {
+ DefaultIssue issue = new DefaultIssue(project);
+ assertThatThrownBy(() -> issue.addQuickFix(quickFix))
+ .isInstanceOf(UnsupportedOperationException.class);
+ assertThatThrownBy(issue::newQuickFix)
+ .isInstanceOf(UnsupportedOperationException.class);
+ assertThatThrownBy(issue::quickFixes)
+ .isInstanceOf(UnsupportedOperationException.class);
+ }
}
diff --git a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultMessageFormattingTest.java b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultMessageFormattingTest.java
new file mode 100644
index 00000000000..55a67ddd485
--- /dev/null
+++ b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultMessageFormattingTest.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.issue.internal;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.sonar.api.batch.sensor.issue.MessageFormatting;
+
+public class DefaultMessageFormattingTest {
+
+ @Test
+ public void negative_start_should_throw_exception() {
+ DefaultMessageFormatting format = new DefaultMessageFormatting().start(-1).end(1).type(MessageFormatting.Type.CODE);
+ Assertions.assertThatThrownBy(() -> format.validate("message"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Message formatting start must be greater or equals to 0");
+ }
+
+ @Test
+ public void missing_type_should_throw_exception() {
+ DefaultMessageFormatting format = new DefaultMessageFormatting().start(0).end(1);
+ Assertions.assertThatThrownBy(() -> format.validate("message"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Message formatting type can't be null");
+ }
+
+ @Test
+ public void end_lesser_than_start_should_throw_exception() {
+ DefaultMessageFormatting format = new DefaultMessageFormatting().start(3).end(2).type(MessageFormatting.Type.CODE);
+ Assertions.assertThatThrownBy(() -> format.validate("message"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Message formatting end must be greater than start");
+ }
+
+ @Test
+ public void end_greater_or_equals_to_message_size_throw_exception() {
+ DefaultMessageFormatting format = new DefaultMessageFormatting().start(0).end(8).type(MessageFormatting.Type.CODE);
+ Assertions.assertThatThrownBy(() -> format.validate("message"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Message formatting end must be lesser or equal than message size");
+ }
+
+ @Test
+ public void full_range_on_message_should_work() {
+ DefaultMessageFormatting format = new DefaultMessageFormatting().start(0).end(6).type(MessageFormatting.Type.CODE);
+ Assertions.assertThatCode(() -> format.validate("message")).doesNotThrowAnyException();
+ }
+}