import java.util.stream.Collectors;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactory;
}
}
- if (!Objects.equals(ruleDtoToUpdate.getCleanCodeAttribute(), adHoc.getCleanCodeAttribute())) {
- ruleDtoToUpdate.setCleanCodeAttribute(adHoc.getCleanCodeAttribute());
+ CleanCodeAttribute cleanCodeAttribute = adHoc.getCleanCodeAttribute();
+ if (!Objects.equals(ruleDtoToUpdate.getCleanCodeAttribute(), cleanCodeAttribute)) {
+ ruleDtoToUpdate.setCleanCodeAttribute(cleanCodeAttribute);
changed = true;
}
private final String severity;
private final RuleType ruleType;
private final boolean hasDetails;
-
- private final CleanCodeAttribute cleanCodeAttribute;
+ private CleanCodeAttribute cleanCodeAttribute = null;
private final Map<SoftwareQuality, Severity> defaultImpacts = new EnumMap<>(SoftwareQuality.class);
this.name = ruleFromScannerReport.getName();
this.description = trimToNull(ruleFromScannerReport.getDescription());
this.hasDetails = true;
- this.cleanCodeAttribute = mapCleanCodeAttribute(trimToNull(ruleFromScannerReport.getCleanCodeAttribute()));
this.ruleType = determineType(ruleFromScannerReport);
this.severity = determineSeverity(ruleFromScannerReport);
- this.defaultImpacts.putAll(determineImpacts(ruleFromScannerReport));
+ if (!ScannerReport.IssueType.SECURITY_HOTSPOT.equals(ruleFromScannerReport.getType())) {
+ this.cleanCodeAttribute = mapCleanCodeAttribute(trimToNull(ruleFromScannerReport.getCleanCodeAttribute()));
+ this.defaultImpacts.putAll(determineImpacts(ruleFromScannerReport));
+ }
+ }
+ public NewAdHocRule(ScannerReport.ExternalIssue fromIssue) {
+ Preconditions.checkArgument(isNotBlank(fromIssue.getEngineId()), "'engine id' not expected to be null for an ad hoc rule");
+ Preconditions.checkArgument(isNotBlank(fromIssue.getRuleId()), "'rule id' not expected to be null for an ad hoc rule");
+ this.key = RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + fromIssue.getEngineId(), fromIssue.getRuleId());
+ this.engineId = fromIssue.getEngineId();
+ this.ruleId = fromIssue.getRuleId();
+ this.name = null;
+ this.description = null;
+ this.severity = null;
+ this.ruleType = null;
+ this.hasDetails = false;
+ if (!ScannerReport.IssueType.SECURITY_HOTSPOT.equals(fromIssue.getType())) {
+ this.cleanCodeAttribute = CleanCodeAttribute.defaultCleanCodeAttribute();
+ this.defaultImpacts.put(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM);
+ }
}
private Map<SoftwareQuality, Severity> determineImpacts(ScannerReport.AdHocRule ruleFromScannerReport) {
return SoftwareQuality.valueOf(softwareQuality);
}
- public NewAdHocRule(ScannerReport.ExternalIssue fromIssue) {
- Preconditions.checkArgument(isNotBlank(fromIssue.getEngineId()), "'engine id' not expected to be null for an ad hoc rule");
- Preconditions.checkArgument(isNotBlank(fromIssue.getRuleId()), "'rule id' not expected to be null for an ad hoc rule");
- this.key = RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + fromIssue.getEngineId(), fromIssue.getRuleId());
- this.engineId = fromIssue.getEngineId();
- this.ruleId = fromIssue.getRuleId();
- this.name = null;
- this.description = null;
- this.severity = null;
- this.ruleType = null;
- this.hasDetails = false;
- this.cleanCodeAttribute = CleanCodeAttribute.defaultCleanCodeAttribute();
- this.defaultImpacts.put(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM);
- }
-
public RuleKey getKey() {
return key;
}
return hasDetails;
}
+ @CheckForNull
public CleanCodeAttribute getCleanCodeAttribute() {
return cleanCodeAttribute;
}
Map<SoftwareQuality, Severity> getDefaultImpacts();
+ @CheckForNull
CleanCodeAttribute cleanCodeAttribute();
}
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.ce.task.projectanalysis.component.Component;
issue.getRuleDescriptionContextKey().ifPresent(event::setRuleDescriptionContextKey);
Rule rule = ruleRepository.getByKey(issue.getRuleKey());
- event.setCleanCodeAttribute(rule.cleanCodeAttribute().name());
- event.setCleanCodeAttributeCategory(rule.cleanCodeAttribute().getAttributeCategory().name());
+ CleanCodeAttribute cleanCodeAttribute = requireNonNull(rule.cleanCodeAttribute());
+ event.setCleanCodeAttribute(cleanCodeAttribute.name());
+ event.setCleanCodeAttributeCategory(cleanCodeAttribute.getAttributeCategory().name());
event.setImpacts(computeEffectiveImpacts(rule.getDefaultImpacts(), issue.impacts()));
return event;
}
.hasMessage("'issue type' not expected to be null for an ad hoc rule, or impacts should be provided instead");
}
+ @Test
+ public void constructor_whenRuleHotspot_shouldNotPopulateImpactsNorAttribute() {
+ NewAdHocRule adHocRule1 = new NewAdHocRule(ScannerReport.ExternalIssue.newBuilder()
+ .setEngineId("eslint")
+ .setType(ScannerReport.IssueType.SECURITY_HOTSPOT)
+ .setSeverity(Constants.Severity.BLOCKER)
+ .setRuleId("no-cond-assign").build());
+ assertThat(adHocRule1.getDefaultImpacts()).isEmpty();
+ assertThat(adHocRule1.getCleanCodeAttribute()).isNull();
+ }
+
+ @Test
+ public void constructor_whenIssueHotspot_shouldNotPopulateImpactsNorAttribute() {
+ NewAdHocRule adHocRule1 = new NewAdHocRule(ScannerReport.ExternalIssue.newBuilder()
+ .setType(ScannerReport.IssueType.SECURITY_HOTSPOT)
+ .setSeverity(Constants.Severity.BLOCKER)
+ .setEngineId("eslint")
+ .setRuleId("no-cond-assign").build());
+ assertThat(adHocRule1.getDefaultImpacts()).isEmpty();
+ assertThat(adHocRule1.getCleanCodeAttribute()).isNull();
+ }
+
}
return this;
}
+ @CheckForNull
public CleanCodeAttribute getCleanCodeAttribute() {
return cleanCodeAttribute;
}
return this;
}
+ @CheckForNull
public CleanCodeAttribute getCleanCodeAttribute() {
return cleanCodeAttribute;
}
- public RuleDto setCleanCodeAttribute(CleanCodeAttribute cleanCodeAttribute) {
+ public RuleDto setCleanCodeAttribute(@Nullable CleanCodeAttribute cleanCodeAttribute) {
this.cleanCodeAttribute = cleanCodeAttribute;
return this;
}
this.type = type;
}
+ @CheckForNull
public String getCleanCodeAttributeCategory() {
return cleanCodeAttributeCategory;
}
.add(10_2_032, "Increase size of 'ce_queue.main_is_last_key' from 55 to 80 characters", IncreaseMainIsLastKeyInCeActivity.class)
.add(10_2_033, "Add column 'clean_code_attribute' in 'rules' table", AddCleanCodeAttributeInRules.class)
.add(10_2_034, "Populate 'clean_code_attribute' column in 'rules' table", PopulateCleanCodeAttributeColumnInRules.class)
- //TODO SONAR-20073
- //.add(10_2_035, "Make 'clean_code_attribute' column not nullable in 'rules' table", MakeCleanCodeAttributeColumnNotNullableInRules.class);
.add(10_2_036, "Create 'rules_default_impacts' table", CreateRulesDefaultImpactsTable.class)
.add(10_2_037, "Create unique constraint index on 'rules_default_impacts' table", CreateUniqueConstraintOnRulesDefaultImpacts.class)
import org.sonar.server.platform.db.migration.step.DataChange;
import org.sonar.server.platform.db.migration.step.MassUpdate;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
+
public class PopulateCleanCodeAttributeColumnInRules extends DataChange {
private static final String SELECT_QUERY = """
SELECT uuid, clean_code_attribute
FROM rules
- WHERE clean_code_attribute is null
- """;
+ WHERE clean_code_attribute is null and (rule_type <> %1$s or ad_hoc_type <> %1$s)
+ """.formatted(SECURITY_HOTSPOT.getDbConstant());
private static final String UPDATE_QUERY = """
UPDATE rules
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.rules.CleanCodeAttribute;
+import org.sonar.api.rules.RuleType;
import org.sonar.db.CoreDbTester;
import static org.assertj.core.api.Assertions.assertThat;
.extracting(stringObjectMap -> stringObjectMap.get("CLEAN_CODE_ATTRIBUTE")).containsExactly(CleanCodeAttribute.FOCUSED.name());
}
- private void insertRule(String uuid, @Nullable CleanCodeAttribute cleanCodeAttribute) {
+ @Test
+ public void execute_whenRuleIsHotspot_shouldNotUpdate() throws SQLException {
+ insertRule("1", RuleType.SECURITY_HOTSPOT, null, null);
+ underTest.execute();
+ assertThat(db.select("select UUID, CLEAN_CODE_ATTRIBUTE from rules"))
+ .extracting(stringObjectMap -> stringObjectMap.get("CLEAN_CODE_ATTRIBUTE")).containsOnlyNulls();
+ }
+
+ @Test
+ public void execute_whenAdhocRuleIsHotspot_shouldNotUpdate() throws SQLException {
+ insertRule("1", null, RuleType.SECURITY_HOTSPOT, null);
+ underTest.execute();
+ assertThat(db.select("select UUID, CLEAN_CODE_ATTRIBUTE from rules"))
+ .extracting(stringObjectMap -> stringObjectMap.get("CLEAN_CODE_ATTRIBUTE")).containsOnlyNulls();
+ }
+
+
+ private void insertRule(String uuid, @Nullable RuleType ruleType, @Nullable RuleType adhocRuleType, @Nullable CleanCodeAttribute cleanCodeAttribute) {
db.executeInsert(TABLE_NAME,
"UUID", uuid,
"PLUGIN_RULE_KEY", "key",
"SCOPE", "1",
"CLEAN_CODE_ATTRIBUTE", cleanCodeAttribute != null ? cleanCodeAttribute.name() : null,
"IS_TEMPLATE", false,
+ "RULE_TYPE", ruleType != null ? ruleType.getDbConstant() : null,
+ "AD_HOC_TYPE", adhocRuleType != null ? adhocRuleType.getDbConstant() : null,
"IS_AD_HOC", false,
"IS_EXTERNAL", false);
}
+
+ private void insertRule(String uuid, @Nullable CleanCodeAttribute cleanCodeAttribute) {
+ insertRule(uuid, RuleType.CODE_SMELL, null, cleanCodeAttribute);
+ }
}
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);
}
RuleDto createRuleWithSimpleFields(RulesDefinition.Rule ruleDef, String uuid, long now) {
- CleanCodeAttribute cleanCodeAttribute = ruleDef.cleanCodeAttribute();
RuleDto ruleDto = new RuleDto()
.setUuid(uuid)
.setRuleKey(RuleKey.of(ruleDef.repository().key(), ruleDef.key()))
.setIsAdHoc(false)
.setCreatedAt(now)
.setUpdatedAt(now)
- .setCleanCodeAttribute(cleanCodeAttribute != null ? cleanCodeAttribute : CleanCodeAttribute.defaultCleanCodeAttribute())
.setEducationPrinciples(ruleDef.educationPrincipleKeys());
+ if (!RuleType.SECURITY_HOTSPOT.equals(ruleDef.type())) {
+ CleanCodeAttribute cleanCodeAttribute = ruleDef.cleanCodeAttribute();
+ ruleDto.setCleanCodeAttribute(cleanCodeAttribute != null ? cleanCodeAttribute : CleanCodeAttribute.defaultCleanCodeAttribute());
+ ruleDto.replaceAllDefaultImpacts(ruleDef.defaultImpacts().entrySet()
+ .stream()
+ .map(e -> new ImpactDto().setUuid(uuidFactory.create()).setSoftwareQuality(e.getKey()).setSeverity(e.getValue()))
+ .collect(Collectors.toSet()));
+ }
+
if (isNotEmpty(ruleDef.htmlDescription())) {
ruleDto.setDescriptionFormat(RuleDto.Format.HTML);
} else if (isNotEmpty(ruleDef.markdownDescription())) {
}
private static boolean mergeCleanCodeAttribute(RulesDefinition.Rule def, RuleDto dto) {
+ if (dto.getEnumType() == RuleType.SECURITY_HOTSPOT) {
+ return false;
+ }
boolean changed = false;
CleanCodeAttribute defCleanCodeAttribute = def.cleanCodeAttribute();
if (!Objects.equals(dto.getCleanCodeAttribute(), defCleanCodeAttribute) && (defCleanCodeAttribute != null)) {
@Test
public void from_whenRuleDefinitionDoesHaveCleanCodeAttribute_shouldReturnThisAttribute() {
- RulesDefinition.Rule ruleDef = getDefaultRule(CleanCodeAttribute.TESTED);
+ RulesDefinition.Rule ruleDef = getDefaultRule(CleanCodeAttribute.TESTED, RuleType.CODE_SMELL);
RuleDto newRuleDto = underTest.createNewRule(context, ruleDef, mock());
assertThat(newRuleDto.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.TESTED);
}
+ @Test
+ public void createNewRule_whenRuleDefinitionDoesHaveCleanCodeAttributeAndIsSecurityHotspot_shouldReturnNull() {
+ RulesDefinition.Rule ruleDef = getDefaultRule(CleanCodeAttribute.TESTED, RuleType.SECURITY_HOTSPOT);
+
+ RuleDto newRuleDto = underTest.createNewRule(context, ruleDef, mock());
+ assertThat(newRuleDto.getCleanCodeAttribute()).isNull();
+ assertThat(newRuleDto.getDefaultImpacts()).isEmpty();
+ }
+
@Test
public void createNewRule_whenImpactDefined_shouldReturnThisImpact() {
RulesDefinition.Rule ruleDef = getDefaultRule();
.containsOnly(tuple(SoftwareQuality.RELIABILITY, Severity.LOW));
}
- private static RulesDefinition.Rule getDefaultRule(@Nullable CleanCodeAttribute attribute) {
+ private static RulesDefinition.Rule getDefaultRule(@Nullable CleanCodeAttribute attribute, RuleType ruleType) {
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.type()).thenReturn(ruleType);
when(ruleDef.scope()).thenReturn(RuleScope.TEST);
when(ruleDef.cleanCodeAttribute()).thenReturn(attribute);
when(ruleDef.severity()).thenReturn(MAJOR);
}
private static RulesDefinition.Rule getDefaultRule() {
- return getDefaultRule(null);
+ return getDefaultRule(null, RuleType.CODE_SMELL);
}
}
assertThat(hotspotRule.getUpdatedAt()).isEqualTo(RulesRegistrantIT.DATE1.getTime());
assertThat(hotspotRule.getType()).isEqualTo(RuleType.SECURITY_HOTSPOT.getDbConstant());
assertThat(hotspotRule.getSecurityStandards()).containsExactly("cwe:1", "cwe:123", "cwe:863", "owaspTop10-2021:a1", "owaspTop10-2021:a3");
+ assertThat(hotspotRule.getDefaultImpacts()).isEmpty();
+ assertThat(hotspotRule.getCleanCodeAttribute()).isNull();
}
@Test
issueBuilder.setType(Common.RuleType.forNumber(dto.getType()));
CleanCodeAttribute cleanCodeAttribute = dto.getCleanCodeAttribute();
- String cleanCodeAttributeString = cleanCodeAttribute != null ? cleanCodeAttribute.name() : null;
- String cleanCodeAttributeCategoryString = cleanCodeAttribute != null ? cleanCodeAttribute.getAttributeCategory().name() : null;
- if (cleanCodeAttributeString != null) {
- issueBuilder.setCleanCodeAttribute(Common.CleanCodeAttribute.valueOf(cleanCodeAttributeString));
- issueBuilder.setCleanCodeAttributeCategory(Common.CleanCodeAttributeCategory.valueOf(cleanCodeAttributeCategoryString));
+ if (cleanCodeAttribute != null) {
+ issueBuilder.setCleanCodeAttribute(Common.CleanCodeAttribute.valueOf(cleanCodeAttribute.name()));
+ issueBuilder.setCleanCodeAttributeCategory(Common.CleanCodeAttributeCategory.valueOf(cleanCodeAttribute.getAttributeCategory().name()));
}
issueBuilder.addAllImpacts(dto.getEffectiveImpacts().entrySet()
.stream()
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
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.debt.internal.DefaultDebtRemediationFunction;
}
private static void setCleanCodeAttributes(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
- if (shouldReturnField(fieldsToReturn, FIELD_CLEAN_CODE_ATTRIBUTE) && ruleDto.getType() != RuleType.SECURITY_HOTSPOT.getDbConstant()) {
- ruleResponse.setCleanCodeAttribute(Common.CleanCodeAttribute.valueOf(ruleDto.getCleanCodeAttribute().name()));
- ruleResponse.setCleanCodeAttributeCategory(Common.CleanCodeAttributeCategory.valueOf(ruleDto.getCleanCodeAttribute().getAttributeCategory().name()));
+ CleanCodeAttribute cleanCodeAttribute = ruleDto.getCleanCodeAttribute();
+ if (shouldReturnField(fieldsToReturn, FIELD_CLEAN_CODE_ATTRIBUTE) && cleanCodeAttribute != null) {
+ ruleResponse.setCleanCodeAttribute(Common.CleanCodeAttribute.valueOf(cleanCodeAttribute.name()));
+ ruleResponse.setCleanCodeAttributeCategory(Common.CleanCodeAttributeCategory.valueOf(cleanCodeAttribute.getAttributeCategory().name()));
}
}
private static void setGapDescription(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
String gapDescription = ruleDto.getGapDescription();
if (shouldReturnField(fieldsToReturn, FIELD_GAP_DESCRIPTION)
- && gapDescription != null) {
+ && gapDescription != null) {
ruleResponse.setGapDescription(gapDescription);
}
}
/**
* This was done to preserve backward compatibility with SonarLint until they stop using htmlDesc field in api/rules/[show|search] endpoints, see SONAR-16635
+ *
* @deprecated the method should be removed once SonarLint supports rules.descriptionSections fields, I.E in 10.x and DB is cleaned up of non-necessary default sections.
*/
@Deprecated(since = "9.6", forRemoval = true)