--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.rule.service;
+
+import java.util.List;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+
+public record RuleInformation(RuleDto ruleDto, List<RuleParamDto> params) {
+}
*/
package org.sonar.server.common.rule.service;
-import org.sonar.db.rule.RuleDto;
-
public class RuleService {
- RuleDto create(CreateRuleRequest request) {
+ public RuleInformation create(CreateRuleRequest request) {
return null;
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.text;
+
+public interface Macro {
+
+ String getRegex();
+
+ String getReplacement();
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.text;
+
+import java.util.List;
+import org.sonar.api.platform.Server;
+import org.sonar.api.server.ServerSide;
+
+@ServerSide
+public class MacroInterpreter {
+ private final List<Macro> macros;
+
+ public MacroInterpreter(Server server) {
+ this.macros = List.of(
+ new RuleMacro(server.getContextPath())
+ );
+ }
+
+ public String interpret(String text) {
+ String textReplaced = text;
+ for (Macro macro : macros) {
+ textReplaced = textReplaced.replaceAll(macro.getRegex(), macro.getReplacement());
+ }
+ return textReplaced;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.text;
+
+class RuleMacro implements Macro {
+
+ private static final String COLON = "%3A";
+ private final String contextPath;
+
+ RuleMacro(String contextPath) {
+ this.contextPath = contextPath;
+ }
+
+ /**
+ * First parameter is the repository, second one is the rule key. Exemple : {rule:java:ArchitecturalConstraint}
+ */
+ @Override
+ public String getRegex() {
+ return "\\{rule:([a-zA-Z0-9._-]++):([a-zA-Z0-9._-]++)\\}";
+ }
+
+ @Override
+ public String getReplacement() {
+ return "<a href='" + contextPath + "/coding_rules#rule_key=$1" + COLON + "$2'>$2</a>";
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.common.text;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.text;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.platform.Server;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MacroInterpreterTest {
+
+ String path = "http://sonar";
+ MacroInterpreter interpreter;
+
+ @Before
+ public void setUp() {
+ Server server = mock(Server.class);
+ when(server.getContextPath()).thenReturn(path);
+ interpreter = new MacroInterpreter(server);
+ }
+
+ @Test
+ public void should_do_nothing_if_no_macro_detected() {
+ String origin = "nothing to do";
+ String result = interpreter.interpret(origin);
+ assertThat(result).isEqualTo(origin);
+ }
+
+ @Test
+ public void should_replace_rule_macro() {
+ // key of repository and rule can contain alphanumeric latin characters, dashes, underscores and dots
+ String ruleKey = "Some_Repo-Key.1:Some_Rule-Key.1";
+ String origin = "See {rule:" + ruleKey + "} for detail.";
+ String result = interpreter.interpret(origin);
+ // colon should be escaped
+ assertThat(result).isEqualTo("See <a href='" + path + "/coding_rules#rule_key=Some_Repo-Key.1%3ASome_Rule-Key.1'>Some_Rule-Key.1</a> for detail.");
+ }
+
+}
*/
package org.sonar.server.v2.api.rule.controller;
+import org.sonar.server.common.rule.service.RuleInformation;
import org.sonar.server.common.rule.service.RuleService;
import org.sonar.server.user.UserSession;
import org.sonar.server.v2.api.rule.converter.RuleRestResponseGenerator;
@Override
public RuleRestResponse create(RuleCreateRestRequest request) {
- return null;
- }
+ RuleInformation ruleInformation = ruleService.create(null);
+ return ruleRestResponseGenerator.toRuleRestResponse(ruleInformation);
+ }
}
*/
package org.sonar.server.v2.api.rule.converter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import org.jetbrains.annotations.Nullable;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.rules.CleanCodeAttribute;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.issue.ImpactDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.markdown.Markdown;
+import org.sonar.server.common.rule.service.RuleInformation;
+import org.sonar.server.common.text.MacroInterpreter;
+import org.sonar.server.rule.RuleDescriptionFormatter;
+import org.sonar.server.v2.api.rule.enums.CleanCodeAttributeCategoryRestEnum;
+import org.sonar.server.v2.api.rule.enums.CleanCodeAttributeRestEnum;
+import org.sonar.server.v2.api.rule.enums.ImpactSeverityRestEnum;
+import org.sonar.server.v2.api.rule.enums.RuleStatusRestEnum;
+import org.sonar.server.v2.api.rule.enums.RuleTypeRestEnum;
+import org.sonar.server.v2.api.rule.enums.SoftwareQualityRestEnum;
+import org.sonar.server.v2.api.rule.response.RuleDescriptionSectionContextRestResponse;
+import org.sonar.server.v2.api.rule.response.RuleDescriptionSectionRestResponse;
+import org.sonar.server.v2.api.rule.response.RuleImpactRestResponse;
+import org.sonar.server.v2.api.rule.response.RuleParameterRestResponse;
+import org.sonar.server.v2.api.rule.response.RuleRestResponse;
+
+import static java.util.Optional.ofNullable;
+import static org.sonar.db.rule.RuleDto.Format.MARKDOWN;
+
public class RuleRestResponseGenerator {
+ private final Languages languages;
+ private final MacroInterpreter macroInterpreter;
+ private final RuleDescriptionFormatter ruleDescriptionFormatter;
+
+ public RuleRestResponseGenerator(Languages languages, MacroInterpreter macroInterpreter, RuleDescriptionFormatter ruleDescriptionFormatter) {
+
+ this.languages = languages;
+ this.macroInterpreter = macroInterpreter;
+ this.ruleDescriptionFormatter = ruleDescriptionFormatter;
+ }
+
+ public RuleRestResponse toRuleRestResponse(RuleInformation ruleInformation) {
+
+ RuleRestResponse.Builder builder = RuleRestResponse.Builder.builder();
+ RuleDto ruleDto = ruleInformation.ruleDto();
+ builder
+ .setId(ruleDto.getUuid())
+ .setKey(ruleDto.getKey().toString())
+ .setRepositoryKey(ruleDto.getRepositoryKey())
+ .setName(ruleDto.getName())
+ .setSeverity(ruleDto.getSeverityString())
+ .setType(RuleTypeRestEnum.from(RuleType.valueOf(ruleDto.getType())))
+ .setImpacts(toImpactRestResponse(ruleDto.getDefaultImpacts()))
+ .setCleanCodeAttribute(CleanCodeAttributeRestEnum.from(ruleDto.getCleanCodeAttribute()))
+ .setCleanCodeAttributeCategory(ofNullable(ruleDto.getCleanCodeAttribute())
+ .map(CleanCodeAttribute::getAttributeCategory)
+ .map(CleanCodeAttributeCategoryRestEnum::from)
+ .orElse(null))
+ .setStatus(RuleStatusRestEnum.from(ruleDto.getStatus()))
+ .setExternal(ruleDto.isExternal())
+ .setCreatedAt(toDateTime(ruleDto.getCreatedAt()))
+ .setGapDescription(ruleDto.getGapDescription())
+ .setHtmlNote(ofNullable(ruleDto.getNoteData()).map(n -> macroInterpreter.interpret(Markdown.convertToHtml(n))).orElse(null))
+ .setMarkdownNote(ruleDto.getNoteData())
+ .setEducationPrinciples(new ArrayList<>(ruleDto.getEducationPrinciples()))
+ .setTemplate(ruleDto.isTemplate())
+ .setTemplateId(ruleDto.getTemplateUuid())
+ .setTags(new ArrayList<>(ruleDto.getTags()))
+ .setSystemTags(new ArrayList<>(ruleDto.getSystemTags()))
+ .setLanguageKey(ruleDto.getLanguage())
+ .setLanguageName(getLanguageName(ruleDto.getLanguage()))
+ .setParameters(toRuleParameterResponse(ruleInformation.params()));
+
+ setDescriptionFields(builder, ruleDto);
+ setRemediationFunctionFields(builder, ruleDto);
+
+ if (ruleDto.isAdHoc()) {
+ ofNullable(ruleDto.getAdHocName()).ifPresent(builder::setName);
+ ofNullable(ruleDto.getAdHocDescription())
+ .map(this::toDescriptionSectionResponse)
+ .ifPresent(section -> builder.setDescriptionSections(List.of(section)));
+ ofNullable(ruleDto.getAdHocSeverity()).ifPresent(builder::setSeverity);
+ ofNullable(ruleDto.getAdHocType()).ifPresent(type -> builder.setType(RuleTypeRestEnum.from(RuleType.valueOf(type))));
+ }
+ return builder.build();
+ }
+
+ private static void setRemediationFunctionFields(RuleRestResponse.Builder builder, RuleDto ruleDto) {
+ ofNullable(debtRemediationFunction(ruleDto))
+ .ifPresent(function -> {
+ builder.setRemediationFunctionBaseEffort(function.baseEffort());
+ builder.setRemediationFunctionGapMultiplier(function.gapMultiplier());
+ ofNullable(function.type()).map(Enum::name).ifPresent(builder::setRemediationFunctionType);
+ });
+ }
+
+ private static List<RuleParameterRestResponse> toRuleParameterResponse(List<RuleParamDto> ruleParamDtos) {
+ return ruleParamDtos.stream()
+ .map(p -> new RuleParameterRestResponse(p.getName(), Markdown.convertToHtml(p.getDescription()), p.getDefaultValue(), p.getType()))
+ .toList();
+ }
+
+ @CheckForNull
+ private String getLanguageName(@Nullable String languageKey) {
+ if (languageKey == null) {
+ return null;
+ }
+ Language language = languages.get(languageKey);
+ return language == null ? languageKey : language.getName();
+ }
+
+ private void setDescriptionFields(RuleRestResponse.Builder builder, RuleDto ruleDto) {
+ builder.setDescriptionSections(ruleDto.getRuleDescriptionSectionDtos().stream()
+ .map(sectionDto -> toDescriptionSectionResponse(ruleDto, sectionDto))
+ .toList());
+
+ String htmlDescription = ruleDescriptionFormatter.getDescriptionAsHtml(ruleDto);
+ if (MARKDOWN.equals(ruleDto.getDescriptionFormat())) {
+ Optional.ofNullable(ruleDto.getDefaultRuleDescriptionSection())
+ .map(RuleDescriptionSectionDto::getContent)
+ .ifPresent(builder::setMarkdownDescription);
+ } else if (htmlDescription != null) {
+ builder.setMarkdownDescription(macroInterpreter.interpret(htmlDescription));
+ }
+ }
+
+ private RuleDescriptionSectionRestResponse toDescriptionSectionResponse(RuleDto ruleDto, RuleDescriptionSectionDto section) {
+ String htmlContent = ruleDescriptionFormatter.toHtml(ruleDto.getDescriptionFormat(), section);
+ String interpretedHtmlContent = macroInterpreter.interpret(htmlContent);
+ return new RuleDescriptionSectionRestResponse(section.getKey(), interpretedHtmlContent,
+ ofNullable(section.getContext())
+ .map(c -> new RuleDescriptionSectionContextRestResponse(c.getKey(), c.getDisplayName()))
+ .orElse(null));
+ }
+
+ private RuleDescriptionSectionRestResponse toDescriptionSectionResponse(String description) {
+ return new RuleDescriptionSectionRestResponse(RuleDescriptionSectionDto.DEFAULT_KEY, macroInterpreter.interpret(description), null);
+ }
+
+ private static List<RuleImpactRestResponse> toImpactRestResponse(Set<ImpactDto> defaultImpacts) {
+ return defaultImpacts.stream()
+ .map(i -> new RuleImpactRestResponse(SoftwareQualityRestEnum.from(i.getSoftwareQuality()), ImpactSeverityRestEnum.from(i.getSeverity())))
+ .toList();
+ }
+
+ @CheckForNull
+ private static DebtRemediationFunction defaultDebtRemediationFunction(final RuleDto ruleDto) {
+ final String function = ruleDto.getDefRemediationFunction();
+ if (function == null || function.isEmpty()) {
+ return null;
+ } else {
+ return new DefaultDebtRemediationFunction(
+ DebtRemediationFunction.Type.valueOf(function.toUpperCase(Locale.ENGLISH)),
+ ruleDto.getDefRemediationGapMultiplier(),
+ ruleDto.getDefRemediationBaseEffort());
+ }
+ }
+
+ @CheckForNull
+ private static DebtRemediationFunction debtRemediationFunction(RuleDto ruleDto) {
+ final String function = ruleDto.getRemediationFunction();
+ if (function == null || function.isEmpty()) {
+ return defaultDebtRemediationFunction(ruleDto);
+ } else {
+ return new DefaultDebtRemediationFunction(
+ DebtRemediationFunction.Type.valueOf(function.toUpperCase(Locale.ENGLISH)),
+ ruleDto.getRemediationGapMultiplier(),
+ ruleDto.getRemediationBaseEffort());
+ }
+ }
+
+ private static String toDateTime(@Nullable Long dateTimeMs) {
+ return Optional.ofNullable(dateTimeMs).map(DateUtils::formatDateTime).orElse(null);
+ }
+
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v2.api.rule.enums;
+
+import java.util.Arrays;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.rules.CleanCodeAttributeCategory;
+
+public enum CleanCodeAttributeCategoryRestEnum {
+ ADAPTABLE(CleanCodeAttributeCategory.ADAPTABLE),
+ CONSISTENT(CleanCodeAttributeCategory.CONSISTENT),
+ INTENTIONAL(CleanCodeAttributeCategory.INTENTIONAL),
+ RESPONSIBLE(CleanCodeAttributeCategory.RESPONSIBLE);
+
+ private final CleanCodeAttributeCategory cleanCodeAttributeCategory;
+
+ CleanCodeAttributeCategoryRestEnum(CleanCodeAttributeCategory cleanCodeAttributeCategory) {
+ this.cleanCodeAttributeCategory = cleanCodeAttributeCategory;
+ }
+
+ @CheckForNull
+ public static CleanCodeAttributeCategoryRestEnum from(@Nullable CleanCodeAttributeCategory cleanCodeAttributeCategory) {
+ if (cleanCodeAttributeCategory == null) {
+ return null;
+ }
+ return Arrays.stream(CleanCodeAttributeCategoryRestEnum.values())
+ .filter(cleanCodeAttributeCategoryRestEnum -> cleanCodeAttributeCategoryRestEnum.cleanCodeAttributeCategory.equals(cleanCodeAttributeCategory))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Unsupported clean code attribute category: " + cleanCodeAttributeCategory));
+ }
+
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v2.api.rule.enums;
+
+import java.util.Arrays;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.rules.CleanCodeAttribute;
+
+public enum CleanCodeAttributeRestEnum {
+ CONVENTIONAL(CleanCodeAttribute.CONVENTIONAL),
+ FORMATTED(CleanCodeAttribute.FORMATTED),
+ IDENTIFIABLE(CleanCodeAttribute.IDENTIFIABLE),
+
+ CLEAR(CleanCodeAttribute.CLEAR),
+ COMPLETE(CleanCodeAttribute.COMPLETE),
+ EFFICIENT(CleanCodeAttribute.EFFICIENT),
+ LOGICAL(CleanCodeAttribute.LOGICAL),
+
+ DISTINCT(CleanCodeAttribute.DISTINCT),
+ FOCUSED(CleanCodeAttribute.FOCUSED),
+ MODULAR(CleanCodeAttribute.MODULAR),
+ TESTED(CleanCodeAttribute.TESTED),
+
+ LAWFUL(CleanCodeAttribute.LAWFUL),
+ RESPECTFUL(CleanCodeAttribute.RESPECTFUL),
+ TRUSTWORTHY(CleanCodeAttribute.TRUSTWORTHY);
+
+ private final CleanCodeAttribute cleanCodeAttribute;
+
+ CleanCodeAttributeRestEnum(CleanCodeAttribute cleanCodeAttribute) {
+ this.cleanCodeAttribute = cleanCodeAttribute;
+ }
+
+ @CheckForNull
+ public static CleanCodeAttributeRestEnum from(@Nullable CleanCodeAttribute cleanCodeAttribute) {
+ if (cleanCodeAttribute == null) {
+ return null;
+ }
+ return Arrays.stream(CleanCodeAttributeRestEnum.values())
+ .filter(cleanCodeAttributeRestEnum -> cleanCodeAttributeRestEnum.cleanCodeAttribute.equals(cleanCodeAttribute))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Unsupported clean code attribute: " + cleanCodeAttribute));
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v2.api.rule.enums;
+
+import java.util.Arrays;
+import org.sonar.api.issue.impact.Severity;
+
+public enum ImpactSeverityRestEnum {
+ LOW(Severity.LOW),
+ MEDIUM(Severity.MEDIUM),
+ HIGH(Severity.HIGH);
+
+ private final Severity severity;
+
+ ImpactSeverityRestEnum(Severity severity) {
+
+ this.severity = severity;
+ }
+
+ public static ImpactSeverityRestEnum from(Severity severity) {
+ return Arrays.stream(ImpactSeverityRestEnum.values())
+ .filter(severityRestResponse -> severityRestResponse.severity.equals(severity))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Unsupported impact severity: " + severity));
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v2.api.rule.enums;
+
+import java.util.Arrays;
+import org.sonar.api.rule.RuleStatus;
+
+public enum RuleStatusRestEnum {
+ BETA(RuleStatus.BETA),
+ DEPRECATED(RuleStatus.DEPRECATED),
+ READY(RuleStatus.READY),
+ REMOVED(RuleStatus.REMOVED);
+
+ private final RuleStatus ruleStatus;
+
+ RuleStatusRestEnum(RuleStatus ruleStatus) {
+ this.ruleStatus = ruleStatus;
+ }
+
+ public static RuleStatusRestEnum from(RuleStatus ruleStatus) {
+ return Arrays.stream(RuleStatusRestEnum.values())
+ .filter(ruleStatusRestEnum -> ruleStatusRestEnum.ruleStatus.equals(ruleStatus))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Unsupported RuleStatus: " + ruleStatus));
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v2.api.rule.enums;
+
+import java.util.Arrays;
+import org.sonar.api.rules.RuleType;
+
+public enum RuleTypeRestEnum {
+ CODE_SMELL(RuleType.CODE_SMELL),
+ BUG(RuleType.BUG),
+ VULNERABILITY(RuleType.VULNERABILITY),
+ SECURITY_HOTSPOT(RuleType.SECURITY_HOTSPOT),
+ ;
+
+ private final RuleType ruleType;
+
+ RuleTypeRestEnum(RuleType ruleType) {
+ this.ruleType = ruleType;
+ }
+
+ public static RuleTypeRestEnum from(RuleType ruleType) {
+ return Arrays.stream(RuleTypeRestEnum.values())
+ .filter(ruleTypeRestEnum -> ruleTypeRestEnum.ruleType.equals(ruleType))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Unsupported RuleType: " + ruleType));
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v2.api.rule.enums;
+
+import java.util.Arrays;
+import org.sonar.api.issue.impact.SoftwareQuality;
+
+public enum SoftwareQualityRestEnum {
+ MAINTAINABILITY(SoftwareQuality.MAINTAINABILITY),
+ RELIABILITY(SoftwareQuality.RELIABILITY),
+ SECURITY(SoftwareQuality.SECURITY);
+
+
+ private final SoftwareQuality softwareQuality;
+
+ SoftwareQualityRestEnum(SoftwareQuality softwareQuality) {
+
+ this.softwareQuality = softwareQuality;
+ }
+
+ public static SoftwareQualityRestEnum from(SoftwareQuality softwareQuality) {
+ return Arrays.stream(SoftwareQualityRestEnum.values())
+ .filter(softwareQualityRest -> softwareQualityRest.softwareQuality.equals(softwareQuality))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Unsupported SoftwareQuality: " + softwareQuality));
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.v2.api.rule.enums;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v2.api.rule.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(accessMode = Schema.AccessMode.READ_ONLY)
+public record RuleDescriptionSectionContextRestResponse(
+ String key,
+ String displayName
+
+) {
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v2.api.rule.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import javax.annotation.Nullable;
+
+public record RuleDescriptionSectionRestResponse(
+ @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+ String key,
+ @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+ String content,
+ @Nullable
+ @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+ RuleDescriptionSectionContextRestResponse context
+
+) {
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v2.api.rule.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.sonar.server.v2.api.rule.enums.ImpactSeverityRestEnum;
+import org.sonar.server.v2.api.rule.enums.SoftwareQualityRestEnum;
+
+@Schema(accessMode = Schema.AccessMode.READ_ONLY)
+public record RuleImpactRestResponse(
+ SoftwareQualityRestEnum softwareQuality,
+ ImpactSeverityRestEnum severity
+) {
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v2.api.rule.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import javax.annotation.Nullable;
+
+public record RuleParameterRestResponse(
+
+ String key,
+ @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+ String htmlDescription,
+ @Nullable
+ String defaultValue,
+ @Schema(allowableValues = {
+ "STRING",
+ "TEXT",
+ "BOOLEAN",
+ "INTEGER",
+ "FLOAT"
+ }, accessMode = Schema.AccessMode.READ_ONLY)
+ String type
+) {
+}
*/
package org.sonar.server.v2.api.rule.response;
-public record RuleRestResponse() {
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.sonar.server.v2.api.rule.enums.CleanCodeAttributeCategoryRestEnum;
+import org.sonar.server.v2.api.rule.enums.CleanCodeAttributeRestEnum;
+import org.sonar.server.v2.api.rule.enums.RuleStatusRestEnum;
+import org.sonar.server.v2.api.rule.enums.RuleTypeRestEnum;
+
+@Schema(accessMode = Schema.AccessMode.READ_ONLY)
+public record RuleRestResponse(
+ String id,
+ String key,
+ String repositoryKey,
+ String name,
+ @Nullable
+ String severity,
+ RuleTypeRestEnum type,
+ List<RuleImpactRestResponse> impacts,
+ @Nullable
+ CleanCodeAttributeRestEnum cleanCodeAttribute,
+ @Nullable
+ CleanCodeAttributeCategoryRestEnum cleanCodeAttributeCategory,
+ @Nullable
+ RuleStatusRestEnum status,
+ boolean external,
+ @Nullable
+ String createdAt,
+ List<RuleDescriptionSectionRestResponse> descriptionSections,
+ @Schema(accessMode = Schema.AccessMode.READ_WRITE)
+ String markdownDescription,
+ @Nullable
+ String gapDescription,
+ @Nullable
+ String htmlNote,
+ @Nullable
+ String markdownNote,
+ List<String> educationPrinciples,
+ boolean template,
+ @Nullable
+ String templateId,
+ @Schema(accessMode = Schema.AccessMode.READ_WRITE)
+ List<String> tags,
+ List<String> systemTags,
+ @Nullable
+ String languageKey,
+ @Nullable
+ String languageName,
+ List<RuleParameterRestResponse> parameters,
+ String remediationFunctionType,
+ String remediationFunctionGapMultiplier,
+ String remediationFunctionBaseEffort
+) {
+
+
+ public static final class Builder {
+ private String id;
+ private String key;
+ private String repositoryKey;
+ private String name;
+ private String severity;
+ private RuleTypeRestEnum type;
+ private List<RuleImpactRestResponse> impacts;
+ private CleanCodeAttributeRestEnum cleanCodeAttribute;
+ private CleanCodeAttributeCategoryRestEnum cleanCodeAttributeCategory;
+ private RuleStatusRestEnum status;
+ private boolean external;
+ private String createdAt;
+ private List<RuleDescriptionSectionRestResponse> descriptionSections;
+ private String markdownDescription;
+ private String gapDescription;
+ private String htmlNote;
+ private String markdownNote;
+ private List<String> educationPrinciples;
+ private boolean template;
+ private String templateId;
+ private List<String> tags;
+ private List<String> systemTags;
+ private String languageKey;
+ private String languageName;
+ private List<RuleParameterRestResponse> parameters;
+ private String remediationFunctionType;
+ private String remediationFunctionGapMultiplier;
+ private String remediationFunctionBaseEffort;
+
+ private Builder() {
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public Builder setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder setKey(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public Builder setRepositoryKey(String repositoryKey) {
+ this.repositoryKey = repositoryKey;
+ return this;
+ }
+
+ public Builder setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder setSeverity(@Nullable String severity) {
+ this.severity = severity;
+ return this;
+ }
+
+ public Builder setType(RuleTypeRestEnum type) {
+ this.type = type;
+ return this;
+ }
+
+ public Builder setImpacts(List<RuleImpactRestResponse> impacts) {
+ this.impacts = impacts;
+ return this;
+ }
+
+ public Builder setCleanCodeAttribute(@Nullable CleanCodeAttributeRestEnum cleanCodeAttribute) {
+ this.cleanCodeAttribute = cleanCodeAttribute;
+ return this;
+ }
+
+ public Builder setCleanCodeAttributeCategory(@Nullable CleanCodeAttributeCategoryRestEnum cleanCodeAttributeCategory) {
+ this.cleanCodeAttributeCategory = cleanCodeAttributeCategory;
+ return this;
+ }
+
+ public Builder setStatus(RuleStatusRestEnum status) {
+ this.status = status;
+ return this;
+ }
+
+ public Builder setExternal(boolean external) {
+ this.external = external;
+ return this;
+ }
+
+ public Builder setCreatedAt(@Nullable String createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Builder setDescriptionSections(List<RuleDescriptionSectionRestResponse> descriptionSections) {
+ this.descriptionSections = descriptionSections;
+ return this;
+ }
+
+ public Builder setMarkdownDescription(String markdownDescription) {
+ this.markdownDescription = markdownDescription;
+ return this;
+ }
+
+ public Builder setGapDescription(@Nullable String gapDescription) {
+ this.gapDescription = gapDescription;
+ return this;
+ }
+
+ public Builder setHtmlNote(@Nullable String htmlNote) {
+ this.htmlNote = htmlNote;
+ return this;
+ }
+
+ public Builder setMarkdownNote(@Nullable String markdownNote) {
+ this.markdownNote = markdownNote;
+ return this;
+ }
+
+ public Builder setEducationPrinciples(List<String> educationPrinciples) {
+ this.educationPrinciples = educationPrinciples;
+ return this;
+ }
+
+ public Builder setTemplate(boolean template) {
+ this.template = template;
+ return this;
+ }
+
+ public Builder setTemplateId(@Nullable String templateId) {
+ this.templateId = templateId;
+ return this;
+ }
+
+ public Builder setTags(List<String> tags) {
+ this.tags = tags;
+ return this;
+ }
+
+ public Builder setSystemTags(List<String> systemTags) {
+ this.systemTags = systemTags;
+ return this;
+ }
+
+ public Builder setLanguageKey(@Nullable String languageKey) {
+ this.languageKey = languageKey;
+ return this;
+ }
+
+ public Builder setLanguageName(@Nullable String languageName) {
+ this.languageName = languageName;
+ return this;
+ }
+
+ public Builder setParameters(List<RuleParameterRestResponse> parameters) {
+ this.parameters = parameters;
+ return this;
+ }
+
+ public Builder setRemediationFunctionType(@Nullable String remediationFunctionType) {
+ this.remediationFunctionType = remediationFunctionType;
+ return this;
+ }
+
+ public Builder setRemediationFunctionGapMultiplier(@Nullable String remediationFunctionGapMultiplier) {
+ this.remediationFunctionGapMultiplier = remediationFunctionGapMultiplier;
+ return this;
+ }
+
+ public Builder setRemediationFunctionBaseEffort(@Nullable String remediationFunctionBaseEffort) {
+ this.remediationFunctionBaseEffort = remediationFunctionBaseEffort;
+ return this;
+ }
+
+ public RuleRestResponse build() {
+ return new RuleRestResponse(id, key, repositoryKey, name, severity, type, impacts, cleanCodeAttribute, cleanCodeAttributeCategory,
+ status, external, createdAt, descriptionSections, markdownDescription, gapDescription, htmlNote, markdownNote,
+ educationPrinciples, template, templateId, tags, systemTags, languageKey, languageName, parameters, remediationFunctionType,
+ remediationFunctionGapMultiplier, remediationFunctionBaseEffort);
+ }
+ }
}
package org.sonar.server.v2.config;
import javax.annotation.Nullable;
+import org.sonar.api.resources.Languages;
import org.sonar.db.DbClient;
import org.sonar.server.common.group.service.GroupMembershipService;
import org.sonar.server.common.group.service.GroupService;
import org.sonar.server.common.platform.LivenessChecker;
import org.sonar.server.common.platform.LivenessCheckerImpl;
import org.sonar.server.common.rule.service.RuleService;
+import org.sonar.server.common.text.MacroInterpreter;
import org.sonar.server.common.user.service.UserService;
import org.sonar.server.health.HealthChecker;
import org.sonar.server.platform.NodeInformation;
+import org.sonar.server.rule.RuleDescriptionFormatter;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.user.UserSession;
import org.sonar.server.v2.api.group.controller.DefaultGroupController;
}
@Bean
- public RuleRestResponseGenerator ruleRestResponseGenerator() {
- return new RuleRestResponseGenerator();
+ public RuleRestResponseGenerator ruleRestResponseGenerator(Languages languages, MacroInterpreter macroInterpreter, RuleDescriptionFormatter ruleDescriptionFormatter) {
+ return new RuleRestResponseGenerator(languages, macroInterpreter, ruleDescriptionFormatter);
}
@Bean
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.v2.api.ControllerTester;
import org.sonar.server.v2.api.rule.converter.RuleRestResponseGenerator;
+import org.sonar.server.v2.api.rule.response.RuleRestResponse;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.sonar.server.v2.WebApiEndpoints.RULES_ENDPOINT;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(MockitoJUnitRunner.class)
private final RuleService ruleService = mock();
+ private final RuleRestResponseGenerator ruleRestResponseGenerator = mock();
private final MockMvc mockMvc = ControllerTester
- .getMockMvc(new DefaultRuleController(userSession, ruleService, new RuleRestResponseGenerator()));
+ .getMockMvc(new DefaultRuleController(userSession, ruleService, ruleRestResponseGenerator));
@Test
.andExpectAll(
status().isOk());
}
+
+ @Test
+ public void create_shouldReturnExpectedBody() throws Exception {
+ when(ruleRestResponseGenerator.toRuleRestResponse(any())).thenReturn(RuleRestResponse.Builder.builder().setId("id").build());
+
+ mockMvc.perform(post(RULES_ENDPOINT).contentType(MediaType.APPLICATION_JSON_VALUE).content("{}"))
+ .andExpectAll(
+ status().isOk(),
+ content().json("{id: 'id'}"));
+ }
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.v2.api.rule.converter;
+
+import java.util.List;
+import java.util.Locale;
+import org.assertj.core.groups.Tuple;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.db.rule.RuleTesting;
+import org.sonar.server.common.rule.service.RuleInformation;
+import org.sonar.server.common.text.MacroInterpreter;
+import org.sonar.server.language.LanguageTesting;
+import org.sonar.server.rule.RuleDescriptionFormatter;
+import org.sonar.server.v2.api.rule.response.RuleRestResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RuleRestResponseGeneratorTest {
+ @Mock
+ private Languages languages;
+
+ @Mock
+ private MacroInterpreter macroInterpreter;
+
+ @Mock
+ RuleDescriptionFormatter ruleDescriptionFormatter;
+
+ @InjectMocks
+ private RuleRestResponseGenerator ruleRestResponseGenerator;
+
+
+ @Test
+ public void toRuleRestResponse_shouldReturnSameFieldForStandardMapping() {
+ when(macroInterpreter.interpret(Mockito.anyString())).thenAnswer(invocation -> "interpreted" + invocation.getArgument(0));
+ when(ruleDescriptionFormatter.toHtml(any(), any())).thenAnswer(invocation -> "html" + ((RuleDescriptionSectionDto) invocation.getArgument(1)).getContent());
+
+ RuleDto dto = RuleTesting.newRule();
+ when(languages.get(dto.getLanguage())).thenReturn(LanguageTesting.newLanguage(dto.getLanguage(), "languageName"));
+
+ RuleRestResponse ruleRestResponse = ruleRestResponseGenerator.toRuleRestResponse(new RuleInformation(dto, List.of()));
+ assertThat(ruleRestResponse.id()).isEqualTo(dto.getUuid());
+ assertThat(ruleRestResponse.key()).isEqualTo(dto.getKey().toString());
+ assertThat(ruleRestResponse.repositoryKey()).isEqualTo(dto.getRepositoryKey());
+ assertThat(ruleRestResponse.name()).isEqualTo(dto.getName());
+ assertThat(ruleRestResponse.descriptionSections()).extracting(s -> s.key(), s -> s.content(), s -> s.context())
+ .containsExactly(dto.getRuleDescriptionSectionDtos().stream().map(s -> tuple(s.getKey(), "interpreted" + "html" + s.getContent(), s.getContext())).toArray(Tuple[]::new));
+ assertThat(ruleRestResponse.severity()).isEqualTo(dto.getSeverityString());
+ assertThat(ruleRestResponse.type().name()).isEqualTo(RuleType.valueOf(dto.getType()).name());
+ assertThat(ruleRestResponse.impacts()).extracting(r -> r.severity().name(), r -> r.softwareQuality().name())
+ .containsExactly(dto.getDefaultImpacts().stream().map(e -> tuple(e.getSeverity().name(), e.getSoftwareQuality().name())).toArray(Tuple[]::new));
+ assertThat(ruleRestResponse.cleanCodeAttribute().name()).isEqualTo(dto.getCleanCodeAttribute().name());
+ assertThat(ruleRestResponse.cleanCodeAttributeCategory().name()).isEqualTo(dto.getCleanCodeAttribute().getAttributeCategory().name());
+ assertThat(ruleRestResponse.status().name()).isEqualTo(dto.getStatus().name());
+ assertThat(ruleRestResponse.external()).isEqualTo(dto.isExternal());
+ assertThat(ruleRestResponse.createdAt()).isEqualTo(DateUtils.formatDateTime(dto.getCreatedAt()));
+ assertThat(ruleRestResponse.gapDescription()).isEqualTo(dto.getGapDescription());
+ assertThat(ruleRestResponse.markdownNote()).isEqualTo(dto.getNoteData());
+ assertThat(ruleRestResponse.educationPrinciples()).containsExactlyElementsOf(dto.getEducationPrinciples());
+ assertThat(ruleRestResponse.template()).isEqualTo(dto.isTemplate());
+ assertThat(ruleRestResponse.templateId()).isEqualTo(dto.getTemplateUuid());
+ assertThat(ruleRestResponse.tags()).containsExactlyElementsOf(dto.getTags());
+ assertThat(ruleRestResponse.systemTags()).containsExactlyElementsOf(dto.getSystemTags());
+ assertThat(ruleRestResponse.languageKey()).isEqualTo(dto.getLanguage());
+ assertThat(ruleRestResponse.languageName()).isEqualTo("languageName");
+
+ DefaultDebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.valueOf(dto.getRemediationFunction().toUpperCase(Locale.ENGLISH)),
+ dto.getRemediationGapMultiplier(),
+ dto.getRemediationBaseEffort());
+ assertThat(ruleRestResponse.remediationFunctionBaseEffort()).isEqualTo(function.baseEffort());
+ assertThat(ruleRestResponse.remediationFunctionGapMultiplier()).isEqualTo(function.gapMultiplier());
+ assertThat(ruleRestResponse.remediationFunctionType()).isEqualTo(dto.getRemediationFunction());
+ }
+
+ @Test
+ public void toRuleRestResponse_shouldReturnNullFields_whenRuleIsEmpty() {
+ RuleDto dto = new RuleDto().setRuleKey("key").setRepositoryKey("repoKey").setStatus(RuleStatus.READY).setType(RuleType.BUG.getDbConstant());
+ RuleRestResponse ruleRestResponse = ruleRestResponseGenerator.toRuleRestResponse(new RuleInformation(dto, List.of()));
+ assertThat(ruleRestResponse.cleanCodeAttribute()).isNull();
+ assertThat(ruleRestResponse.cleanCodeAttributeCategory()).isNull();
+ assertThat(ruleRestResponse.htmlNote()).isNull();
+ }
+
+ @Test
+ public void toRuleRestResponse_shouldReturnParameters_whenParametersAreProvided() {
+ RuleDto dto = RuleTesting.newRule();
+ RuleParamDto ruleParamDto1 = RuleTesting.newRuleParam(dto);
+ RuleParamDto ruleParamDto2 = RuleTesting.newRuleParam(dto);
+ RuleRestResponse ruleRestResponse = ruleRestResponseGenerator.toRuleRestResponse(new RuleInformation(dto, List.of(ruleParamDto1, ruleParamDto2)));
+
+ assertThat(ruleRestResponse.parameters()).extracting(p -> p.key(), p -> p.htmlDescription(), p -> p.defaultValue())
+ .containsExactlyInAnyOrder(tuple(ruleParamDto1.getName(), ruleParamDto1.getDescription(), ruleParamDto1.getDefaultValue()),
+ tuple(ruleParamDto2.getName(), ruleParamDto2.getDescription(), ruleParamDto2.getDefaultValue()));
+ }
+
+ @Test
+ public void toRuleRestResponse_shouldReturnAdhocInformation_whenRuleIsAdhoc() {
+ when(macroInterpreter.interpret(Mockito.anyString())).thenAnswer(invocation -> "interpreted" + invocation.getArgument(0));
+ RuleDto dto = RuleTesting.newRule();
+ dto.setIsAdHoc(true);
+ dto.setAdHocName("adhocName");
+ dto.setAdHocDescription("adhocDescription");
+ dto.setAdHocSeverity(Severity.INFO);
+ dto.setAdHocType(RuleType.BUG.getDbConstant());
+
+ RuleRestResponse ruleRestResponse = ruleRestResponseGenerator.toRuleRestResponse(new RuleInformation(dto, List.of()));
+ assertThat(ruleRestResponse.name()).isEqualTo(dto.getAdHocName());
+ assertThat(ruleRestResponse.descriptionSections()).extracting(r -> r.key(), r -> r.content(), r -> r.context())
+ .containsExactly(tuple("default", "interpreted" + dto.getAdHocDescription(), null));
+ assertThat(ruleRestResponse.severity()).isEqualTo(dto.getAdHocSeverity());
+ assertThat(ruleRestResponse.type().name()).isEqualTo(RuleType.valueOf(dto.getAdHocType()).name());
+ }
+
+ @Test
+ public void toRuleRestResponse_shouldReturnRemediationFunctions() {
+ when(macroInterpreter.interpret(Mockito.anyString())).thenAnswer(invocation -> "interpreted" + invocation.getArgument(0));
+ RuleDto dto = RuleTesting.newRule();
+ dto.setIsAdHoc(true);
+ dto.setAdHocName("adhocName");
+ dto.setAdHocDescription("adhocDescription");
+ dto.setAdHocSeverity(Severity.INFO);
+ dto.setAdHocType(RuleType.BUG.getDbConstant());
+
+ RuleRestResponse ruleRestResponse = ruleRestResponseGenerator.toRuleRestResponse(new RuleInformation(dto, List.of()));
+ assertThat(ruleRestResponse.name()).isEqualTo(dto.getAdHocName());
+ assertThat(ruleRestResponse.descriptionSections()).extracting(r -> r.key(), r -> r.content(), r -> r.context())
+ .containsExactly(tuple("default", "interpreted" + dto.getAdHocDescription(), null));
+ assertThat(ruleRestResponse.severity()).isEqualTo(dto.getAdHocSeverity());
+ assertThat(ruleRestResponse.type().name()).isEqualTo(RuleType.valueOf(dto.getAdHocType()).name());
+ }
+
+}
import org.sonar.core.util.UuidFactory;
import org.sonar.db.DbTester;
import org.sonar.db.rule.RuleDto;
+import org.sonar.server.common.text.MacroInterpreter;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.rule.RuleDescriptionFormatter;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.text.MacroInterpreter;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleTesting;
+import org.sonar.server.common.text.MacroInterpreter;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.language.LanguageTesting;
import org.sonar.server.rule.RuleDescriptionFormatter;
import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.text.MacroInterpreter;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.Rules;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleParamDto;
import org.sonar.db.user.UserDto;
+import org.sonar.server.common.text.MacroInterpreter;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.language.LanguageTesting;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.text.MacroInterpreter;
import org.sonar.server.util.IntegerTypeValidation;
import org.sonar.server.util.StringTypeValidation;
import org.sonar.server.util.TypeValidations;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleParamDto;
import org.sonar.db.user.UserDto;
+import org.sonar.server.common.text.MacroInterpreter;
import org.sonar.server.rule.RuleDescriptionFormatter;
import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.text.MacroInterpreter;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Rules;
import org.sonar.db.rule.RuleDescriptionSectionDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.user.UserDto;
+import org.sonar.server.common.text.MacroInterpreter;
import org.sonar.server.es.EsClient;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.rule.RuleUpdater;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.text.MacroInterpreter;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsAction;
import org.sonar.server.ws.WsActionTester;
import org.sonar.db.rule.RuleParamDto;
import org.sonar.db.user.UserDto;
import org.sonar.markdown.Markdown;
+import org.sonar.server.common.text.MacroInterpreter;
import org.sonar.server.rule.RuleDescriptionFormatter;
import org.sonar.server.rule.ws.RulesResponseFormatter.SearchResult;
-import org.sonar.server.text.MacroInterpreter;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Common.RuleScope;
import org.sonarqube.ws.Rules;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.text;
-
-public interface Macro {
-
- String getRegex();
-
- String getReplacement();
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.text;
-
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.platform.Server;
-
-import java.util.List;
-
-@ServerSide
-public class MacroInterpreter {
- private final List<Macro> macros;
-
- public MacroInterpreter(Server server) {
- this.macros = List.of(
- new RuleMacro(server.getContextPath())
- );
- }
-
- public String interpret(String text) {
- String textReplaced = text;
- for (Macro macro : macros) {
- textReplaced = textReplaced.replaceAll(macro.getRegex(), macro.getReplacement());
- }
- return textReplaced;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.text;
-
-class RuleMacro implements Macro {
-
- private static final String COLON = "%3A";
- private final String contextPath;
-
- RuleMacro(String contextPath) {
- this.contextPath = contextPath;
- }
-
- /**
- * First parameter is the repository, second one is the rule key. Exemple : {rule:java:ArchitecturalConstraint}
- */
- @Override
- public String getRegex() {
- return "\\{rule:([a-zA-Z0-9._-]++):([a-zA-Z0-9._-]++)\\}";
- }
-
- @Override
- public String getReplacement() {
- return "<a href='" + contextPath + "/coding_rules#rule_key=$1" + COLON + "$2'>$2</a>";
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.text;
-
-import javax.annotation.ParametersAreNonnullByDefault;
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.text;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.platform.Server;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class MacroInterpreterTest {
-
- String path = "http://sonar";
- MacroInterpreter interpreter;
-
- @Before
- public void setUp() {
- Server server = mock(Server.class);
- when(server.getContextPath()).thenReturn(path);
- interpreter = new MacroInterpreter(server);
- }
-
- @Test
- public void should_do_nothing_if_no_macro_detected() {
- String origin = "nothing to do";
- String result = interpreter.interpret(origin);
- assertThat(result).isEqualTo(origin);
- }
-
- @Test
- public void should_replace_rule_macro() {
- // key of repository and rule can contain alphanumeric latin characters, dashes, underscores and dots
- String ruleKey = "Some_Repo-Key.1:Some_Rule-Key.1";
- String origin = "See {rule:" + ruleKey + "} for detail.";
- String result = interpreter.interpret(origin);
- // colon should be escaped
- assertThat(result).isEqualTo("See <a href='" + path + "/coding_rules#rule_key=Some_Repo-Key.1%3ASome_Rule-Key.1'>Some_Rule-Key.1</a> for detail.");
- }
-
-}
import org.sonar.server.common.group.service.GroupMembershipService;
import org.sonar.server.common.group.service.GroupService;
import org.sonar.server.common.rule.service.RuleService;
+import org.sonar.server.common.text.MacroInterpreter;
import org.sonar.server.component.ComponentCleanerService;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.component.ComponentService;
import org.sonar.server.telemetry.TelemetryDaemon;
import org.sonar.server.telemetry.TelemetryDataJsonWriter;
import org.sonar.server.telemetry.TelemetryDataLoaderImpl;
-import org.sonar.server.text.MacroInterpreter;
import org.sonar.server.ui.PageRepository;
import org.sonar.server.ui.WebAnalyticsLoaderImpl;
import org.sonar.server.ui.ws.NavigationWsModule;