Browse Source

SONAR-20021 adding default impacts when registering a rule

tags/10.2.0.77647
lukasz-jarocki-sonarsource 9 months ago
parent
commit
f4684e8e35

+ 1
- 1
gradle.properties View File

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

+ 1
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java View File



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) {

+ 4
- 46
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java View File

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)) {

+ 9
- 33
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDtoTest.java View File

.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);
}
} }

+ 106
- 0
server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/NewRuleCreator.java View File

/*
* 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;
}
}

+ 9
- 26
server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/RulesRegistrant.java View File

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());
} }



+ 28
- 7
server/sonar-webserver-core/src/main/java/org/sonar/server/rule/registration/StartupRuleUpdater.java View File

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() ||

+ 110
- 0
server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/NewRuleCreatorTest.java View File

/*
* 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);
}
}

+ 17
- 8
server/sonar-webserver-core/src/test/java/org/sonar/server/rule/registration/RulesRegistrantIT.java View File

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");

+ 2
- 0
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java View File

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);

Loading…
Cancel
Save