group=org.sonarsource.sonarqube | group=org.sonarsource.sonarqube | ||||
version=10.2 | version=10.2 | ||||
pluginApiVersion=10.0.0.790 | |||||
pluginApiVersion=10.1.0.809 | |||||
description=Open source platform for continuous inspection of code quality | description=Open source platform for continuous inspection of code quality | ||||
projectTitle=SonarQube | projectTitle=SonarQube | ||||
org.gradle.jvmargs=-Xmx2048m | org.gradle.jvmargs=-Xmx2048m |
private static void insertRuleDefaultImpacts(RuleDto ruleDto, RuleMapper mapper) { | private static void insertRuleDefaultImpacts(RuleDto ruleDto, RuleMapper mapper) { | ||||
ruleDto.getDefaultImpacts() | ruleDto.getDefaultImpacts() | ||||
.forEach(section -> mapper.insertRuleDefaultImpact(ruleDto.getUuid(), section)); | |||||
.forEach(impact -> mapper.insertRuleDefaultImpact(ruleDto.getUuid(), impact)); | |||||
} | } | ||||
public void scrollIndexingRuleExtensionsByIds(DbSession dbSession, Collection<String> ruleExtensionIds, Consumer<RuleExtensionForIndexingDto> consumer) { | public void scrollIndexingRuleExtensionsByIds(DbSession dbSession, Collection<String> ruleExtensionIds, Consumer<RuleExtensionForIndexingDto> consumer) { |
import org.sonar.api.rule.RuleStatus; | import org.sonar.api.rule.RuleStatus; | ||||
import org.sonar.api.rules.CleanCodeAttribute; | import org.sonar.api.rules.CleanCodeAttribute; | ||||
import org.sonar.api.rules.RuleType; | import org.sonar.api.rules.RuleType; | ||||
import org.sonar.api.server.debt.DebtRemediationFunction; | |||||
import org.sonar.api.server.rule.RulesDefinition; | |||||
import org.sonar.db.issue.ImpactDto; | import org.sonar.db.issue.ImpactDto; | ||||
import static com.google.common.base.Preconditions.checkArgument; | import static com.google.common.base.Preconditions.checkArgument; | ||||
import static java.util.Arrays.asList; | import static java.util.Arrays.asList; | ||||
import static java.util.Collections.emptySet; | import static java.util.Collections.emptySet; | ||||
import static java.util.Optional.ofNullable; | import static java.util.Optional.ofNullable; | ||||
import static org.apache.commons.lang.StringUtils.isNotEmpty; | |||||
import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY; | import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY; | ||||
public class RuleDto { | public class RuleDto { | ||||
return type; | return type; | ||||
} | } | ||||
public RuleType getEnumType() { | |||||
return RuleType.valueOf(type); | |||||
} | |||||
public RuleDto setType(int type) { | public RuleDto setType(int type) { | ||||
this.type = type; | this.type = type; | ||||
return this; | return this; | ||||
return strings == null || strings.isEmpty() ? null : String.join(",", strings); | return strings == null || strings.isEmpty() ? null : String.join(",", strings); | ||||
} | } | ||||
public static RuleDto from(RulesDefinition.Rule ruleDef, Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos, String uuid, | |||||
long now) { | |||||
RuleDto ruleDto = new RuleDto() | |||||
.setUuid(uuid) | |||||
.setRuleKey(RuleKey.of(ruleDef.repository().key(), ruleDef.key())) | |||||
.setPluginKey(ruleDef.pluginKey()) | |||||
.setIsTemplate(ruleDef.template()) | |||||
.setConfigKey(ruleDef.internalKey()) | |||||
.setLanguage(ruleDef.repository().language()) | |||||
.setName(ruleDef.name()) | |||||
.setSeverity(ruleDef.severity()) | |||||
.setStatus(ruleDef.status()) | |||||
.setGapDescription(ruleDef.gapDescription()) | |||||
.setSystemTags(ruleDef.tags()) | |||||
.setSecurityStandards(ruleDef.securityStandards()) | |||||
.setType(RuleType.valueOf(ruleDef.type().name())) | |||||
.setScope(Scope.valueOf(ruleDef.scope().name())) | |||||
.setIsExternal(ruleDef.repository().isExternal()) | |||||
.setIsAdHoc(false) | |||||
.setCreatedAt(now) | |||||
.setUpdatedAt(now) | |||||
.setCleanCodeAttribute(ruleDef.cleanCodeAttribute() != null ? ruleDef.cleanCodeAttribute() : CleanCodeAttribute.defaultCleanCodeAttribute()) | |||||
.setEducationPrinciples(ruleDef.educationPrincipleKeys()); | |||||
if (isNotEmpty(ruleDef.htmlDescription())) { | |||||
ruleDto.setDescriptionFormat(Format.HTML); | |||||
} else if (isNotEmpty(ruleDef.markdownDescription())) { | |||||
ruleDto.setDescriptionFormat(Format.MARKDOWN); | |||||
} | |||||
ruleDescriptionSectionDtos.forEach(ruleDto::addRuleDescriptionSectionDto); | |||||
DebtRemediationFunction debtRemediationFunction = ruleDef.debtRemediationFunction(); | |||||
if (debtRemediationFunction != null) { | |||||
ruleDto.setDefRemediationFunction(debtRemediationFunction.type().name()); | |||||
ruleDto.setDefRemediationGapMultiplier(debtRemediationFunction.gapMultiplier()); | |||||
ruleDto.setDefRemediationBaseEffort(debtRemediationFunction.baseEffort()); | |||||
ruleDto.setGapDescription(ruleDef.gapDescription()); | |||||
} | |||||
return ruleDto; | |||||
} | |||||
@Override | @Override | ||||
public boolean equals(Object obj) { | public boolean equals(Object obj) { | ||||
if (!(obj instanceof RuleDto)) { | if (!(obj instanceof RuleDto)) { |
.hasMessage(String.format(ERROR_MESSAGE_SECTION_ALREADY_EXISTS, SECTION_KEY, contextKey)); | .hasMessage(String.format(ERROR_MESSAGE_SECTION_ALREADY_EXISTS, SECTION_KEY, contextKey)); | ||||
} | } | ||||
@Test | |||||
public void from_whenRuleDefinitionDoesntHaveCleanCodeAttribute_shouldAlwaysSetCleanCodeAttribute() { | |||||
RulesDefinition.Rule ruleDef = getDefaultRule(); | |||||
RuleDto newRuleDto = RuleDto.from(ruleDef, Set.of(), "uuid", Long.MAX_VALUE); | |||||
assertThat(newRuleDto.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL); | |||||
} | |||||
@Test | |||||
public void from_whenRuleDefinitionDoesHaveCleanCodeAttribute_shouldReturnThisAttribute() { | |||||
RulesDefinition.Rule ruleDef = getDefaultRule(CleanCodeAttribute.TESTED); | |||||
RuleDto newRuleDto = RuleDto.from(ruleDef, Set.of(), "uuid", Long.MAX_VALUE); | |||||
assertThat(newRuleDto.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.TESTED); | |||||
} | |||||
@Test | @Test | ||||
public void addDefaultImpact_whenSoftwareQualityAlreadyDefined_shouldThrowISE() { | public void addDefaultImpact_whenSoftwareQualityAlreadyDefined_shouldThrowISE() { | ||||
RuleDto dto = new RuleDto(); | RuleDto dto = new RuleDto(); | ||||
.containsExactlyInAnyOrder( | .containsExactlyInAnyOrder( | ||||
tuple(SoftwareQuality.MAINTAINABILITY, Severity.HIGH), | tuple(SoftwareQuality.MAINTAINABILITY, Severity.HIGH), | ||||
tuple(SoftwareQuality.SECURITY, Severity.LOW)); | tuple(SoftwareQuality.SECURITY, Severity.LOW)); | ||||
} | |||||
@Test | |||||
public void getEnumType_shouldReturnCorrectValue() { | |||||
RuleDto ruleDto = new RuleDto(); | |||||
ruleDto.setType(RuleType.CODE_SMELL); | |||||
RuleType enumType = ruleDto.getEnumType(); | |||||
assertThat(enumType).isEqualTo(RuleType.CODE_SMELL); | |||||
} | } | ||||
@NotNull | @NotNull | ||||
.setSoftwareQuality(softwareQuality) | .setSoftwareQuality(softwareQuality) | ||||
.setSeverity(severity); | .setSeverity(severity); | ||||
} | } | ||||
private static RulesDefinition.Rule getDefaultRule(@Nullable CleanCodeAttribute attribute) { | |||||
RulesDefinition.Rule ruleDef = mock(RulesDefinition.Rule.class); | |||||
RulesDefinition.Repository repository = mock(RulesDefinition.Repository.class); | |||||
when(ruleDef.repository()).thenReturn(repository); | |||||
when(ruleDef.key()).thenReturn("key"); | |||||
when(repository.key()).thenReturn("repoKey"); | |||||
when(ruleDef.type()).thenReturn(RuleType.CODE_SMELL); | |||||
when(ruleDef.scope()).thenReturn(RuleScope.TEST); | |||||
when(ruleDef.cleanCodeAttribute()).thenReturn(attribute); | |||||
return ruleDef; | |||||
} | |||||
private static RulesDefinition.Rule getDefaultRule() { | |||||
return getDefaultRule(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.rule.registration; | |||||
import java.util.stream.Collectors; | |||||
import org.sonar.api.rule.RuleKey; | |||||
import org.sonar.api.rules.CleanCodeAttribute; | |||||
import org.sonar.api.rules.RuleType; | |||||
import org.sonar.api.server.debt.DebtRemediationFunction; | |||||
import org.sonar.api.server.rule.RulesDefinition; | |||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.core.util.UuidFactory; | |||||
import org.sonar.db.DbClient; | |||||
import org.sonar.db.DbSession; | |||||
import org.sonar.db.issue.ImpactDto; | |||||
import org.sonar.db.rule.RuleDto; | |||||
import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver; | |||||
import static org.apache.commons.lang.StringUtils.isNotEmpty; | |||||
public class NewRuleCreator { | |||||
private final DbClient dbClient; | |||||
private final RuleDescriptionSectionsGeneratorResolver ruleDescriptionSectionsGeneratorResolver; | |||||
private final UuidFactory uuidFactory; | |||||
private final System2 system2; | |||||
public NewRuleCreator(DbClient dbClient, RuleDescriptionSectionsGeneratorResolver ruleDescriptionSectionsGeneratorResolver, UuidFactory uuidFactory, System2 system2) { | |||||
this.dbClient = dbClient; | |||||
this.ruleDescriptionSectionsGeneratorResolver = ruleDescriptionSectionsGeneratorResolver; | |||||
this.uuidFactory = uuidFactory; | |||||
this.system2 = system2; | |||||
} | |||||
RuleDto createNewRule(RulesRegistrationContext context, RulesDefinition.Rule ruleDef, DbSession session) { | |||||
RuleDto newRule = createRuleWithSimpleFields(ruleDef, uuidFactory.create(), system2.now()); | |||||
newRule.replaceAllDefaultImpacts(ruleDef.defaultImpacts().entrySet() | |||||
.stream() | |||||
.map(e -> new ImpactDto().setUuid(uuidFactory.create()).setSoftwareQuality(e.getKey()).setSeverity(e.getValue())) | |||||
.collect(Collectors.toSet())); | |||||
ruleDescriptionSectionsGeneratorResolver.generateFor(ruleDef).forEach(newRule::addRuleDescriptionSectionDto); | |||||
dbClient.ruleDao().insert(session, newRule); | |||||
context.created(newRule); | |||||
return newRule; | |||||
} | |||||
RuleDto createRuleWithSimpleFields(RulesDefinition.Rule ruleDef, String uuid, long now) { | |||||
RuleDto ruleDto = new RuleDto() | |||||
.setUuid(uuid) | |||||
.setRuleKey(RuleKey.of(ruleDef.repository().key(), ruleDef.key())) | |||||
.setPluginKey(ruleDef.pluginKey()) | |||||
.setIsTemplate(ruleDef.template()) | |||||
.setConfigKey(ruleDef.internalKey()) | |||||
.setLanguage(ruleDef.repository().language()) | |||||
.setName(ruleDef.name()) | |||||
.setSeverity(ruleDef.severity()) | |||||
.setStatus(ruleDef.status()) | |||||
.setGapDescription(ruleDef.gapDescription()) | |||||
.setSystemTags(ruleDef.tags()) | |||||
.setSecurityStandards(ruleDef.securityStandards()) | |||||
.setType(RuleType.valueOf(ruleDef.type().name())) | |||||
.setScope(RuleDto.Scope.valueOf(ruleDef.scope().name())) | |||||
.setIsExternal(ruleDef.repository().isExternal()) | |||||
.setIsAdHoc(false) | |||||
.setCreatedAt(now) | |||||
.setUpdatedAt(now) | |||||
.setCleanCodeAttribute(ruleDef.cleanCodeAttribute() != null ? ruleDef.cleanCodeAttribute() : CleanCodeAttribute.defaultCleanCodeAttribute()) | |||||
.setEducationPrinciples(ruleDef.educationPrincipleKeys()); | |||||
if (isNotEmpty(ruleDef.htmlDescription())) { | |||||
ruleDto.setDescriptionFormat(RuleDto.Format.HTML); | |||||
} else if (isNotEmpty(ruleDef.markdownDescription())) { | |||||
ruleDto.setDescriptionFormat(RuleDto.Format.MARKDOWN); | |||||
} | |||||
DebtRemediationFunction debtRemediationFunction = ruleDef.debtRemediationFunction(); | |||||
if (debtRemediationFunction != null) { | |||||
ruleDto.setDefRemediationFunction(debtRemediationFunction.type().name()); | |||||
ruleDto.setDefRemediationGapMultiplier(debtRemediationFunction.gapMultiplier()); | |||||
ruleDto.setDefRemediationBaseEffort(debtRemediationFunction.baseEffort()); | |||||
ruleDto.setGapDescription(ruleDef.gapDescription()); | |||||
} | |||||
return ruleDto; | |||||
} | |||||
} |
import java.util.Set; | import java.util.Set; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import java.util.stream.Stream; | import java.util.stream.Stream; | ||||
import javax.annotation.Nonnull; | |||||
import org.sonar.api.Startable; | import org.sonar.api.Startable; | ||||
import org.sonar.api.resources.Languages; | import org.sonar.api.resources.Languages; | ||||
import org.sonar.api.rule.RuleKey; | import org.sonar.api.rule.RuleKey; | ||||
import org.sonar.api.utils.log.Logger; | import org.sonar.api.utils.log.Logger; | ||||
import org.sonar.api.utils.log.Loggers; | import org.sonar.api.utils.log.Loggers; | ||||
import org.sonar.api.utils.log.Profiler; | import org.sonar.api.utils.log.Profiler; | ||||
import org.sonar.core.util.UuidFactory; | |||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import org.sonar.db.DbSession; | import org.sonar.db.DbSession; | ||||
import org.sonar.db.rule.RuleDto; | import org.sonar.db.rule.RuleDto; | ||||
import org.sonar.server.qualityprofile.QProfileRules; | import org.sonar.server.qualityprofile.QProfileRules; | ||||
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; | import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; | ||||
import org.sonar.server.rule.RuleDefinitionsLoader; | import org.sonar.server.rule.RuleDefinitionsLoader; | ||||
import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver; | |||||
import org.sonar.server.rule.WebServerRuleFinder; | import org.sonar.server.rule.WebServerRuleFinder; | ||||
import org.sonar.server.rule.index.RuleIndexer; | import org.sonar.server.rule.index.RuleIndexer; | ||||
private final Languages languages; | private final Languages languages; | ||||
private final System2 system2; | private final System2 system2; | ||||
private final WebServerRuleFinder webServerRuleFinder; | private final WebServerRuleFinder webServerRuleFinder; | ||||
private final UuidFactory uuidFactory; | |||||
private final MetadataIndex metadataIndex; | private final MetadataIndex metadataIndex; | ||||
private final RuleDescriptionSectionsGeneratorResolver ruleDescriptionSectionsGeneratorResolver; | |||||
private final RulesKeyVerifier rulesKeyVerifier; | private final RulesKeyVerifier rulesKeyVerifier; | ||||
private final StartupRuleUpdater startupRuleUpdater; | private final StartupRuleUpdater startupRuleUpdater; | ||||
private final NewRuleCreator newRuleCreator; | |||||
public RulesRegistrant(RuleDefinitionsLoader defLoader, QProfileRules qProfileRules, DbClient dbClient, RuleIndexer ruleIndexer, | public RulesRegistrant(RuleDefinitionsLoader defLoader, QProfileRules qProfileRules, DbClient dbClient, RuleIndexer ruleIndexer, | ||||
ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2, | |||||
WebServerRuleFinder webServerRuleFinder, UuidFactory uuidFactory, MetadataIndex metadataIndex, | |||||
RuleDescriptionSectionsGeneratorResolver ruleDescriptionSectionsGeneratorResolver, | |||||
RulesKeyVerifier rulesKeyVerifier, StartupRuleUpdater startupRuleUpdater) { | |||||
ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2, WebServerRuleFinder webServerRuleFinder, | |||||
MetadataIndex metadataIndex, RulesKeyVerifier rulesKeyVerifier, StartupRuleUpdater startupRuleUpdater, | |||||
NewRuleCreator newRuleCreator) { | |||||
this.defLoader = defLoader; | this.defLoader = defLoader; | ||||
this.qProfileRules = qProfileRules; | this.qProfileRules = qProfileRules; | ||||
this.dbClient = dbClient; | this.dbClient = dbClient; | ||||
this.languages = languages; | this.languages = languages; | ||||
this.system2 = system2; | this.system2 = system2; | ||||
this.webServerRuleFinder = webServerRuleFinder; | this.webServerRuleFinder = webServerRuleFinder; | ||||
this.uuidFactory = uuidFactory; | |||||
this.metadataIndex = metadataIndex; | this.metadataIndex = metadataIndex; | ||||
this.ruleDescriptionSectionsGeneratorResolver = ruleDescriptionSectionsGeneratorResolver; | |||||
this.rulesKeyVerifier = rulesKeyVerifier; | this.rulesKeyVerifier = rulesKeyVerifier; | ||||
this.startupRuleUpdater = startupRuleUpdater; | this.startupRuleUpdater = startupRuleUpdater; | ||||
this.newRuleCreator = newRuleCreator; | |||||
} | } | ||||
@Override | @Override | ||||
for (RulesDefinition.Rule ruleDef : ruleDefs) { | for (RulesDefinition.Rule ruleDef : ruleDefs) { | ||||
RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key()); | RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key()); | ||||
RuleDto ruleDto = findOrCreateRuleDto(context, session, ruleDef); | |||||
RuleDto ruleDto = context.getDbRuleFor(ruleDef).orElseGet(() -> newRuleCreator.createNewRule(context, ruleDef, session)); | |||||
dtos.put(ruleDef, ruleDto); | dtos.put(ruleDef, ruleDto); | ||||
// we must detect renaming __before__ we modify the DTO | // we must detect renaming __before__ we modify the DTO | ||||
ruleDto.setRuleKey(ruleKey); | ruleDto.setRuleKey(ruleKey); | ||||
} | } | ||||
if (startupRuleUpdater.findChangesAndUpdateRule(ruleDef, ruleDto)) { | |||||
if (!context.isCreated(ruleDto) && startupRuleUpdater.findChangesAndUpdateRule(ruleDef, ruleDto)) { | |||||
context.updated(ruleDto); | context.updated(ruleDto); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
@Nonnull | |||||
private RuleDto findOrCreateRuleDto(RulesRegistrationContext context, DbSession session, RulesDefinition.Rule ruleDef) { | |||||
return context.getDbRuleFor(ruleDef) | |||||
.orElseGet(() -> { | |||||
RuleDto newRule = RuleDto.from(ruleDef, ruleDescriptionSectionsGeneratorResolver.generateFor(ruleDef), uuidFactory.create(), system2.now()); | |||||
dbClient.ruleDao().insert(session, newRule); | |||||
context.created(newRule); | |||||
return newRule; | |||||
}); | |||||
} | |||||
private void processRemainingDbRules(RulesRegistrationContext recorder, DbSession dbSession) { | private void processRemainingDbRules(RulesRegistrationContext recorder, DbSession dbSession) { | ||||
// custom rules check status of template, so they must be processed at the end | // custom rules check status of template, so they must be processed at the end | ||||
List<RuleDto> customRules = new ArrayList<>(); | List<RuleDto> customRules = new ArrayList<>(); | ||||
private static Set<String> getExistingAndRenamedRepositories(RulesRegistrationContext recorder, Collection<RulesDefinition.Repository> context) { | private static Set<String> getExistingAndRenamedRepositories(RulesRegistrationContext recorder, Collection<RulesDefinition.Repository> context) { | ||||
return Stream.concat( | return Stream.concat( | ||||
context.stream().map(RulesDefinition.ExtendedRepository::key), | |||||
recorder.getRenamed().map(Map.Entry::getValue).map(RuleKey::repository)) | |||||
context.stream().map(RulesDefinition.ExtendedRepository::key), | |||||
recorder.getRenamed().map(Map.Entry::getValue).map(RuleKey::repository)) | |||||
.collect(Collectors.toSet()); | .collect(Collectors.toSet()); | ||||
} | } | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Objects; | import java.util.Objects; | ||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.stream.Collectors; | |||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.apache.commons.lang.StringUtils; | import org.apache.commons.lang.StringUtils; | ||||
import org.sonar.api.issue.impact.Severity; | |||||
import org.sonar.api.issue.impact.SoftwareQuality; | |||||
import org.sonar.api.rule.RuleStatus; | import org.sonar.api.rule.RuleStatus; | ||||
import org.sonar.api.rules.CleanCodeAttribute; | import org.sonar.api.rules.CleanCodeAttribute; | ||||
import org.sonar.api.rules.RuleType; | import org.sonar.api.rules.RuleType; | ||||
import org.sonar.core.util.UuidFactory; | import org.sonar.core.util.UuidFactory; | ||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import org.sonar.db.DbSession; | import org.sonar.db.DbSession; | ||||
import org.sonar.db.issue.ImpactDto; | |||||
import org.sonar.db.qualityprofile.ActiveRuleDto; | import org.sonar.db.qualityprofile.ActiveRuleDto; | ||||
import org.sonar.db.qualityprofile.ActiveRuleParamDto; | import org.sonar.db.qualityprofile.ActiveRuleParamDto; | ||||
import org.sonar.db.rule.DeprecatedRuleKeyDto; | import org.sonar.db.rule.DeprecatedRuleKeyDto; | ||||
changed = true; | changed = true; | ||||
} | } | ||||
changed |= mergeCleanCodeAttribute(def, dto); | changed |= mergeCleanCodeAttribute(def, dto); | ||||
changed |= mergeImpacts(def, dto); | |||||
changed |= mergeImpacts(def, dto, uuidFactory); | |||||
if (dto.isAdHoc()) { | if (dto.isAdHoc()) { | ||||
dto.setIsAdHoc(false); | dto.setIsAdHoc(false); | ||||
changed = true; | changed = true; | ||||
return changed; | return changed; | ||||
} | } | ||||
private static boolean mergeImpacts(RulesDefinition.Rule def, RuleDto dto) { | |||||
boolean changed = false; | |||||
// TODO when DTOs for impacts are ready | |||||
return changed; | |||||
} | |||||
private static boolean mergeCleanCodeAttribute(RulesDefinition.Rule def, RuleDto dto) { | private static boolean mergeCleanCodeAttribute(RulesDefinition.Rule def, RuleDto dto) { | ||||
boolean changed = false; | boolean changed = false; | ||||
if (!Objects.equals(dto.getCleanCodeAttribute(), def.cleanCodeAttribute()) && (def.cleanCodeAttribute() != null)) { | if (!Objects.equals(dto.getCleanCodeAttribute(), def.cleanCodeAttribute()) && (def.cleanCodeAttribute() != null)) { | ||||
return changed; | return changed; | ||||
} | } | ||||
boolean mergeImpacts(RulesDefinition.Rule def, RuleDto dto, UuidFactory uuidFactory) { | |||||
if (dto.getEnumType() == RuleType.SECURITY_HOTSPOT) { | |||||
return false; | |||||
} | |||||
Map<SoftwareQuality, Severity> impactsFromPlugin = def.defaultImpacts(); | |||||
Map<SoftwareQuality, Severity> impactsFromDb = dto.getDefaultImpacts().stream().collect(Collectors.toMap(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)); | |||||
if (impactsFromPlugin.isEmpty()) { | |||||
throw new IllegalStateException("There should be at least one impact defined for the rule " + def.key()); | |||||
} | |||||
if (!Objects.equals(impactsFromDb, impactsFromPlugin)) { | |||||
dto.replaceAllDefaultImpacts(impactsFromPlugin.entrySet() | |||||
.stream() | |||||
.map(e -> new ImpactDto().setUuid(uuidFactory.create()).setSoftwareQuality(e.getKey()).setSeverity(e.getValue())) | |||||
.collect(Collectors.toSet())); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
private static boolean mergeEducationPrinciples(RulesDefinition.Rule ruleDef, RuleDto dto) { | private static boolean mergeEducationPrinciples(RulesDefinition.Rule ruleDef, RuleDto dto) { | ||||
boolean changed = false; | boolean changed = false; | ||||
if (dto.getEducationPrinciples().size() != ruleDef.educationPrincipleKeys().size() || | if (dto.getEducationPrinciples().size() != ruleDef.educationPrincipleKeys().size() || |
/* | |||||
* 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.rule.registration; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import org.jetbrains.annotations.Nullable; | |||||
import org.junit.Before; | |||||
import org.junit.Test; | |||||
import org.sonar.api.issue.impact.Severity; | |||||
import org.sonar.api.issue.impact.SoftwareQuality; | |||||
import org.sonar.api.rule.RuleScope; | |||||
import org.sonar.api.rules.CleanCodeAttribute; | |||||
import org.sonar.api.rules.RuleType; | |||||
import org.sonar.api.server.rule.RulesDefinition; | |||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.core.util.UuidFactory; | |||||
import org.sonar.db.DbClient; | |||||
import org.sonar.db.issue.ImpactDto; | |||||
import org.sonar.db.rule.RuleDto; | |||||
import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
import static org.assertj.core.groups.Tuple.tuple; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.when; | |||||
import static org.sonar.api.rule.Severity.MAJOR; | |||||
public class NewRuleCreatorTest { | |||||
private final DbClient dbClient = mock(); | |||||
private final RuleDescriptionSectionsGeneratorResolver ruleDescriptionSectionsGeneratorResolver = mock(); | |||||
private final UuidFactory uuidFactory = mock(); | |||||
private final System2 system2 = mock(); | |||||
private final RulesRegistrationContext context = mock(); | |||||
private final NewRuleCreator underTest = new NewRuleCreator(dbClient, ruleDescriptionSectionsGeneratorResolver, uuidFactory, system2); | |||||
@Before | |||||
public void before() { | |||||
when(dbClient.ruleDao()).thenReturn(mock()); | |||||
} | |||||
@Test | |||||
public void from_whenRuleDefinitionDoesntHaveCleanCodeAttribute_shouldAlwaysSetCleanCodeAttribute() { | |||||
RulesDefinition.Rule ruleDef = getDefaultRule(); | |||||
RuleDto newRuleDto = underTest.createNewRule(context, ruleDef, mock()); | |||||
assertThat(newRuleDto.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL); | |||||
} | |||||
@Test | |||||
public void from_whenRuleDefinitionDoesHaveCleanCodeAttribute_shouldReturnThisAttribute() { | |||||
RulesDefinition.Rule ruleDef = getDefaultRule(CleanCodeAttribute.TESTED); | |||||
RuleDto newRuleDto = underTest.createNewRule(context, ruleDef, mock()); | |||||
assertThat(newRuleDto.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.TESTED); | |||||
} | |||||
@Test | |||||
public void createNewRule_whenImpactDefined_shouldReturnThisImpact() { | |||||
RulesDefinition.Rule ruleDef = getDefaultRule(); | |||||
Map<SoftwareQuality, Severity> singleImpact = new HashMap<>(); | |||||
singleImpact.put(SoftwareQuality.RELIABILITY, Severity.LOW); | |||||
when(ruleDef.defaultImpacts()).thenReturn(singleImpact); | |||||
RuleDto newRuleDto = underTest.createNewRule(context, ruleDef, mock()); | |||||
assertThat(newRuleDto.getDefaultImpacts()).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity) | |||||
.containsOnly(tuple(SoftwareQuality.RELIABILITY, Severity.LOW)); | |||||
} | |||||
private static RulesDefinition.Rule getDefaultRule(@Nullable CleanCodeAttribute attribute) { | |||||
RulesDefinition.Rule ruleDef = mock(RulesDefinition.Rule.class); | |||||
RulesDefinition.Repository repository = mock(RulesDefinition.Repository.class); | |||||
when(ruleDef.repository()).thenReturn(repository); | |||||
when(ruleDef.key()).thenReturn("key"); | |||||
when(repository.key()).thenReturn("repoKey"); | |||||
when(ruleDef.type()).thenReturn(RuleType.CODE_SMELL); | |||||
when(ruleDef.scope()).thenReturn(RuleScope.TEST); | |||||
when(ruleDef.cleanCodeAttribute()).thenReturn(attribute); | |||||
when(ruleDef.severity()).thenReturn(MAJOR); | |||||
when(ruleDef.defaultImpacts()).thenReturn(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.LOW)); | |||||
return ruleDef; | |||||
} | |||||
private static RulesDefinition.Rule getDefaultRule() { | |||||
return getDefaultRule(null); | |||||
} | |||||
} |
import org.junit.Test; | import org.junit.Test; | ||||
import org.junit.runner.RunWith; | import org.junit.runner.RunWith; | ||||
import org.sonar.api.impl.utils.TestSystem2; | import org.sonar.api.impl.utils.TestSystem2; | ||||
import org.sonar.api.issue.impact.Severity; | |||||
import org.sonar.api.issue.impact.SoftwareQuality; | |||||
import org.sonar.api.resources.Language; | import org.sonar.api.resources.Language; | ||||
import org.sonar.api.resources.Languages; | import org.sonar.api.resources.Languages; | ||||
import org.sonar.api.rule.RuleKey; | import org.sonar.api.rule.RuleKey; | ||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import org.sonar.db.DbSession; | import org.sonar.db.DbSession; | ||||
import org.sonar.db.DbTester; | import org.sonar.db.DbTester; | ||||
import org.sonar.db.issue.ImpactDto; | |||||
import org.sonar.db.qualityprofile.ActiveRuleDto; | import org.sonar.db.qualityprofile.ActiveRuleDto; | ||||
import org.sonar.db.qualityprofile.QProfileChangeDto; | import org.sonar.db.qualityprofile.QProfileChangeDto; | ||||
import org.sonar.db.qualityprofile.QProfileChangeQuery; | import org.sonar.db.qualityprofile.QProfileChangeQuery; | ||||
import static org.sonar.api.rule.RuleStatus.READY; | import static org.sonar.api.rule.RuleStatus.READY; | ||||
import static org.sonar.api.rule.RuleStatus.REMOVED; | import static org.sonar.api.rule.RuleStatus.REMOVED; | ||||
import static org.sonar.api.rule.Severity.BLOCKER; | import static org.sonar.api.rule.Severity.BLOCKER; | ||||
import static org.sonar.api.rule.Severity.CRITICAL; | |||||
import static org.sonar.api.rule.Severity.INFO; | import static org.sonar.api.rule.Severity.INFO; | ||||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY; | import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY; | ||||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; | import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; | ||||
private final RulesKeyVerifier rulesKeyVerifier = new RulesKeyVerifier(); | private final RulesKeyVerifier rulesKeyVerifier = new RulesKeyVerifier(); | ||||
private final StartupRuleUpdater startupRuleUpdater = new StartupRuleUpdater(dbClient, system, uuidFactory, resolver); | private final StartupRuleUpdater startupRuleUpdater = new StartupRuleUpdater(dbClient, system, uuidFactory, resolver); | ||||
private final NewRuleCreator newRuleCreator = new NewRuleCreator(dbClient, resolver, uuidFactory, system); | |||||
@Before | @Before | ||||
public void before() { | public void before() { | ||||
// verify db | // verify db | ||||
assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3); | assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(3); | ||||
RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1); | RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1); | ||||
verifyRule(rule1); | |||||
verifyRule(rule1, RuleType.CODE_SMELL, BLOCKER); | |||||
assertThat(rule1.isExternal()).isFalse(); | assertThat(rule1.isExternal()).isFalse(); | ||||
assertThat(rule1.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL); | assertThat(rule1.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL); | ||||
assertThat(rule1.getDefaultImpacts()).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity).containsOnly(tuple(SoftwareQuality.RELIABILITY, Severity.HIGH)); | |||||
assertThat(rule1.getDefRemediationFunction()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name()); | assertThat(rule1.getDefRemediationFunction()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET.name()); | ||||
assertThat(rule1.getDefRemediationGapMultiplier()).isEqualTo("5d"); | assertThat(rule1.getDefRemediationGapMultiplier()).isEqualTo("5d"); | ||||
assertThat(rule1.getDefRemediationBaseEffort()).isEqualTo("10h"); | assertThat(rule1.getDefRemediationBaseEffort()).isEqualTo("10h"); | ||||
// verify index | // verify index | ||||
RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2); | RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY2); | ||||
assertThat(rule2.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.EFFICIENT); | assertThat(rule2.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.EFFICIENT); | ||||
assertThat(rule2.getDefaultImpacts()).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity).containsOnly(tuple(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM)); | |||||
assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid(), hotspotRule.getUuid()); | assertThat(ruleIndex.search(new RuleQuery(), new SearchOptions()).getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid(), hotspotRule.getUuid()); | ||||
verifyIndicesMarkedAsInitialized(); | verifyIndicesMarkedAsInitialized(); | ||||
// verify db | // verify db | ||||
assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(2); | assertThat(dbClient.ruleDao().selectAll(db.getSession())).hasSize(2); | ||||
RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_RULE_KEY1); | RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_RULE_KEY1); | ||||
verifyRule(rule1); | |||||
verifyRule(rule1, RuleType.CODE_SMELL, BLOCKER); | |||||
assertThat(rule1.isExternal()).isTrue(); | assertThat(rule1.isExternal()).isTrue(); | ||||
assertThat(rule1.getDefRemediationFunction()).isNull(); | assertThat(rule1.getDefRemediationFunction()).isNull(); | ||||
assertThat(rule1.getDefRemediationGapMultiplier()).isNull(); | assertThat(rule1.getDefRemediationGapMultiplier()).isNull(); | ||||
verifyHotspot(hotspotRule); | verifyHotspot(hotspotRule); | ||||
} | } | ||||
private void verifyRule(RuleDto rule) { | |||||
private void verifyRule(RuleDto rule, RuleType type, String expectedSeverity) { | |||||
assertThat(rule.getName()).isEqualTo("One"); | assertThat(rule.getName()).isEqualTo("One"); | ||||
assertThat(rule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description of One"); | assertThat(rule.getDefaultRuleDescriptionSection().getContent()).isEqualTo("Description of One"); | ||||
assertThat(rule.getSeverityString()).isEqualTo(BLOCKER); | |||||
assertThat(rule.getSeverityString()).isEqualTo(expectedSeverity); | |||||
assertThat(rule.getTags()).isEmpty(); | assertThat(rule.getTags()).isEmpty(); | ||||
assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2", "tag3"); | assertThat(rule.getSystemTags()).containsOnly("tag1", "tag2", "tag3"); | ||||
assertThat(rule.getConfigKey()).isEqualTo("config1"); | assertThat(rule.getConfigKey()).isEqualTo("config1"); | ||||
assertThat(rule.getCreatedAt()).isEqualTo(DATE1.getTime()); | assertThat(rule.getCreatedAt()).isEqualTo(DATE1.getTime()); | ||||
assertThat(rule.getScope()).isEqualTo(Scope.ALL); | assertThat(rule.getScope()).isEqualTo(Scope.ALL); | ||||
assertThat(rule.getUpdatedAt()).isEqualTo(DATE1.getTime()); | assertThat(rule.getUpdatedAt()).isEqualTo(DATE1.getTime()); | ||||
assertThat(rule.getType()).isEqualTo(RuleType.CODE_SMELL.getDbConstant()); | |||||
assertThat(rule.getType()).isEqualTo(type.getDbConstant()); | |||||
assertThat(rule.getPluginKey()).isEqualTo(FAKE_PLUGIN_KEY); | assertThat(rule.getPluginKey()).isEqualTo(FAKE_PLUGIN_KEY); | ||||
assertThat(rule.isAdHoc()).isFalse(); | assertThat(rule.isAdHoc()).isFalse(); | ||||
assertThat(rule.getEducationPrinciples()).containsOnly("concept1", "concept2", "concept3"); | assertThat(rule.getEducationPrinciples()).containsOnly("concept1", "concept2", "concept3"); | ||||
.setRuleKey("rule1") | .setRuleKey("rule1") | ||||
.setRepositoryKey("findbugs") | .setRepositoryKey("findbugs") | ||||
.setName("Rule One") | .setName("Rule One") | ||||
.setType(RuleType.CODE_SMELL) | |||||
.setScope(Scope.ALL) | .setScope(Scope.ALL) | ||||
.addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule one description")) | .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule one description")) | ||||
.setDescriptionFormat(RuleDto.Format.HTML) | .setDescriptionFormat(RuleDto.Format.HTML) | ||||
when(languages.get(any())).thenReturn(mock(Language.class)); | when(languages.get(any())).thenReturn(mock(Language.class)); | ||||
reset(webServerRuleFinder); | reset(webServerRuleFinder); | ||||
RulesRegistrant task = new RulesRegistrant(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, languages, system, webServerRuleFinder, uuidFactory, metadataIndex, | |||||
resolver, rulesKeyVerifier, startupRuleUpdater); | |||||
RulesRegistrant task = new RulesRegistrant(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, languages, system, webServerRuleFinder, metadataIndex, | |||||
rulesKeyVerifier, startupRuleUpdater, newRuleCreator); | |||||
task.start(); | task.start(); | ||||
// Execute a commit to refresh session state as the task is using its own session | // Execute a commit to refresh session state as the task is using its own session | ||||
db.getSession().commit(); | db.getSession().commit(); | ||||
.setType(RuleType.CODE_SMELL) | .setType(RuleType.CODE_SMELL) | ||||
.setStatus(RuleStatus.BETA) | .setStatus(RuleStatus.BETA) | ||||
.setGapDescription("java.S115.effortToFix") | .setGapDescription("java.S115.effortToFix") | ||||
.addEducationPrincipleKeys("concept1", "concept2", "concept3"); | |||||
.addEducationPrincipleKeys("concept1", "concept2", "concept3") | |||||
.addDefaultImpact(SoftwareQuality.RELIABILITY, Severity.HIGH); | |||||
rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("5d", "10h")); | rule1.setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("5d", "10h")); | ||||
rule1.createParam("param1").setDescription("parameter one").setDefaultValue("default1"); | rule1.createParam("param1").setDescription("parameter one").setDefaultValue("default1"); |
import org.sonar.server.rule.AdvancedRuleDescriptionSectionsGenerator; | import org.sonar.server.rule.AdvancedRuleDescriptionSectionsGenerator; | ||||
import org.sonar.server.rule.LegacyHotspotRuleDescriptionSectionsGenerator; | import org.sonar.server.rule.LegacyHotspotRuleDescriptionSectionsGenerator; | ||||
import org.sonar.server.rule.LegacyIssueRuleDescriptionSectionsGenerator; | import org.sonar.server.rule.LegacyIssueRuleDescriptionSectionsGenerator; | ||||
import org.sonar.server.rule.registration.NewRuleCreator; | |||||
import org.sonar.server.rule.registration.RulesKeyVerifier; | import org.sonar.server.rule.registration.RulesKeyVerifier; | ||||
import org.sonar.server.rule.registration.RulesRegistrant; | import org.sonar.server.rule.registration.RulesRegistrant; | ||||
import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver; | import org.sonar.server.rule.RuleDescriptionSectionsGeneratorResolver; | ||||
LegacyHotspotRuleDescriptionSectionsGenerator.class, | LegacyHotspotRuleDescriptionSectionsGenerator.class, | ||||
LegacyIssueRuleDescriptionSectionsGenerator.class, | LegacyIssueRuleDescriptionSectionsGenerator.class, | ||||
RulesRegistrant.class, | RulesRegistrant.class, | ||||
NewRuleCreator.class, | |||||
RulesKeyVerifier.class, | RulesKeyVerifier.class, | ||||
StartupRuleUpdater.class, | StartupRuleUpdater.class, | ||||
BuiltInQProfileLoader.class); | BuiltInQProfileLoader.class); |