From 66b8bff0dcc778eb93da92cd408214bbfca82c2a Mon Sep 17 00:00:00 2001 From: Léo Geoffroy Date: Wed, 23 Nov 2022 15:57:02 +0100 Subject: SONAR-17592 Persist Message formatting from plugin api --- .../issue/internal/AbstractDefaultIssue.java | 24 +++++++- .../batch/sensor/issue/internal/DefaultIssue.java | 18 ++++++ .../issue/internal/DefaultIssueLocation.java | 44 +++++++++++++- .../issue/internal/DefaultMessageFormatting.java | 71 ++++++++++++++++++++++ .../sensor/issue/internal/DefaultIssueTest.java | 25 ++++++-- .../internal/DefaultMessageFormattingTest.java | 65 ++++++++++++++++++++ 6 files changed, 237 insertions(+), 10 deletions(-) create mode 100644 sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultMessageFormatting.java create mode 100644 sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultMessageFormattingTest.java (limited to 'sonar-plugin-api-impl') 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 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 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; @@ -77,6 +80,16 @@ public class DefaultIssue extends AbstractDefaultIssue implements return this; } + @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; @@ -93,6 +106,11 @@ public class DefaultIssue extends AbstractDefaultIssue implements return Optional.ofNullable(ruleDescriptionContextKey); } + @Override + public List 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 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 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 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 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(); + } +} -- cgit v1.2.3