From 66567684bced17f962139109fb21dd575e4e4ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Mon, 6 Jul 2026 16:37:24 +0200 Subject: [PATCH] Extend the ProfileRegistrar interface to register rules for specific quality profiles --- .../verifier/TestProfileRegistrarContext.java | 10 +++++++--- .../TestProfileRegistrarContextTest.java | 5 ++++- .../plugins/java/api/ProfileRegistrar.java | 20 +++++++++++++++---- .../java/BuiltInJavaQualityProfile.java | 13 ++++++++++-- .../plugins/java/JavaAgenticAIProfile.java | 5 +++-- .../plugins/java/JavaSonarWayProfile.java | 3 ++- .../java/JavaAgenticWayProfileTest.java | 17 +++++++++++++--- .../plugins/java/JavaSonarWayProfileTest.java | 9 +++++++-- 8 files changed, 64 insertions(+), 18 deletions(-) diff --git a/java-checks-testkit/src/main/java/org/sonar/java/checks/verifier/TestProfileRegistrarContext.java b/java-checks-testkit/src/main/java/org/sonar/java/checks/verifier/TestProfileRegistrarContext.java index 94d759270c5..ff7c4d184cd 100644 --- a/java-checks-testkit/src/main/java/org/sonar/java/checks/verifier/TestProfileRegistrarContext.java +++ b/java-checks-testkit/src/main/java/org/sonar/java/checks/verifier/TestProfileRegistrarContext.java @@ -17,18 +17,22 @@ package org.sonar.java.checks.verifier; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.sonar.api.rule.RuleKey; import org.sonar.plugins.java.api.ProfileRegistrar; public class TestProfileRegistrarContext implements ProfileRegistrar.RegistrarContext { - public final Set defaultQualityProfileRules = new HashSet<>(); + public final Map> rulesByQualityProfile = new HashMap<>(); @Override - public void registerDefaultQualityProfileRules(Collection ruleKeys) { - defaultQualityProfileRules.addAll(ruleKeys); + public void registerRules(String qualityProfileName, Collection ruleKeys) { + rulesByQualityProfile + .computeIfAbsent(qualityProfileName, k -> new HashSet<>()) + .addAll(ruleKeys); } } diff --git a/java-checks-testkit/src/test/java/org/sonar/java/checks/verifier/TestProfileRegistrarContextTest.java b/java-checks-testkit/src/test/java/org/sonar/java/checks/verifier/TestProfileRegistrarContextTest.java index 81dce5fe289..7635f674305 100644 --- a/java-checks-testkit/src/test/java/org/sonar/java/checks/verifier/TestProfileRegistrarContextTest.java +++ b/java-checks-testkit/src/test/java/org/sonar/java/checks/verifier/TestProfileRegistrarContextTest.java @@ -28,7 +28,10 @@ class TestProfileRegistrarContextTest { void store_registration() { TestProfileRegistrarContext context = new TestProfileRegistrarContext(); context.registerDefaultQualityProfileRules(List.of(RuleKey.of("java", "S1234"))); - assertThat(context.defaultQualityProfileRules).containsExactly(RuleKey.of("java", "S1234")); + context.registerRules("Sonar way", List.of(RuleKey.of("java", "S1235"))); + context.registerRules("Sonar agentic AI", List.of(RuleKey.of("java", "S4321"))); + assertThat(context.rulesByQualityProfile.get("Sonar way")).containsOnly(RuleKey.of("java", "S1234"), RuleKey.of("java", "S1235")); + assertThat(context.rulesByQualityProfile.get("Sonar agentic AI")).containsExactly(RuleKey.of("java", "S4321")); } } diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/ProfileRegistrar.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/ProfileRegistrar.java index 38bde1ef70a..1885668fad1 100644 --- a/java-frontend/src/main/java/org/sonar/plugins/java/api/ProfileRegistrar.java +++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/ProfileRegistrar.java @@ -23,12 +23,13 @@ import org.sonarsource.api.sonarlint.SonarLintSide; /** - * This class can be extended to provide additional rule keys in the builtin default quality profile. + * This class can be extended to provide additional rule keys in builtin quality profiles. * *
  *   {@code
  *     public void register(RegistrarContext registrarContext) {
  *       registrarContext.registerDefaultQualityProfileRules(ruleKeys);
+ *       registrarContext.registerRules("Sonar agentic AI", ruleKeys);
  *     }
  *   }
  * 
@@ -46,16 +47,27 @@ public interface ProfileRegistrar { /** - * This method is called on server side and during an analysis to modify the builtin default quality profile for java. + * This method is called on server side and during an analysis to modify builtin quality profiles for Java. */ void register(RegistrarContext registrarContext); interface RegistrarContext { /** - * Registers additional rules into the "Sonar Way" default quality profile for the language "java". + * Registers additional rules into the "Sonar way" default quality profile for the language "java". */ - void registerDefaultQualityProfileRules(Collection ruleKeys); + default void registerDefaultQualityProfileRules(Collection ruleKeys) { + registerRules("Sonar way", ruleKeys); + } + + /** + * Registers additional rules into a builtin quality profile for the language "java". + * + *

+ * The profile name is expected to match the profile string coming from RSPEC metadata ({@code defaultQualityProfiles}). + *

+ */ + void registerRules(String qualityProfileName, Collection ruleKeys); } diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/BuiltInJavaQualityProfile.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/BuiltInJavaQualityProfile.java index e0562093565..ca1392a05c9 100644 --- a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/BuiltInJavaQualityProfile.java +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/BuiltInJavaQualityProfile.java @@ -45,7 +45,10 @@ abstract class BuiltInJavaQualityProfile implements BuiltInQualityProfilesDefini static final String GET_REPOSITORY_KEY = "getRepositoryKey"; static final String SECURITY_REPOSITORY_KEY = "javasecurity"; static final String DBD_REPOSITORY_KEY = "javabugs"; + static final String SONAR_WAY_PROFILE = "Sonar way"; + static final String SONAR_AGENTIC_PROFILE = "Sonar agentic AI"; + private static final Set BUILTIN_PROFILES = Set.of(SONAR_WAY_PROFILE, SONAR_AGENTIC_PROFILE); protected final ProfileRegistrar[] profileRegistrars; @@ -83,11 +86,17 @@ public void define(Context context) { profile.done(); } - static Set registerRulesFromJson(String pathToJsonProfile, @Nullable ProfileRegistrar[] profileRegistrars) { + Set registerRulesFromJson(String pathToJsonProfile, @Nullable ProfileRegistrar[] profileRegistrars) { Set ruleKeys = new HashSet<>(loadRuleKeys(pathToJsonProfile)); if (profileRegistrars != null) { for (ProfileRegistrar profileRegistrar : profileRegistrars) { - profileRegistrar.register(ruleKeys::addAll); + profileRegistrar.register((qualityProfileName, registrarRuleKeys) -> { + if (qualityProfileName.equals(getProfileName())) { + ruleKeys.addAll(registrarRuleKeys); + } else if (!BUILTIN_PROFILES.contains(qualityProfileName)) { + LOG.debug("No builtin quality profile called '{}' was found: skipping rules registration.", qualityProfileName); + } + }); } } diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticAIProfile.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticAIProfile.java index c188adbb760..214b7569ccc 100644 --- a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticAIProfile.java +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticAIProfile.java @@ -22,7 +22,8 @@ @SonarLintSide public class JavaAgenticAIProfile extends BuiltInJavaQualityProfile { - static final String PROFILE_NAME = "Sonar agentic AI"; + static final String PROFILE_NAME = SONAR_AGENTIC_PROFILE; + static final String SONAR_AGENTIC_PATH = "/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json"; public JavaAgenticAIProfile() { this(null); @@ -39,7 +40,7 @@ String getProfileName() { @Override String getPathToJsonProfile() { - return "/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json"; + return SONAR_AGENTIC_PATH; } @Override diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaSonarWayProfile.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaSonarWayProfile.java index ed3bb39b48d..eb8295d4cfa 100644 --- a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaSonarWayProfile.java +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaSonarWayProfile.java @@ -27,6 +27,7 @@ */ @SonarLintSide public class JavaSonarWayProfile extends BuiltInJavaQualityProfile { + static final String PROFILE_NAME = SONAR_WAY_PROFILE; static final String SONAR_WAY_PATH = "/org/sonar/l10n/java/rules/java/Sonar_way_profile.json"; /** @@ -42,7 +43,7 @@ public JavaSonarWayProfile(@Nullable ProfileRegistrar[] profileRegistrars) { @Override String getProfileName() { - return "Sonar way"; + return PROFILE_NAME; } @Override diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java index 5d255874ee4..ac5781b6f94 100644 --- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java +++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java @@ -32,7 +32,9 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.sonar.api.rule.RuleKey; import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; +import org.sonar.plugins.java.api.ProfileRegistrar; import static org.assertj.core.api.Assertions.assertThat; @@ -42,7 +44,12 @@ class JavaAgenticWayProfileTest { @Test void profile_is_registered_as_expected() { - JavaAgenticAIProfile profile = new JavaAgenticAIProfile(); + ProfileRegistrar agenticCustomRules = registrarContext -> + registrarContext.registerRules("Sonar agentic AI", List.of(RuleKey.of("java", "S1107"))); + ProfileRegistrar sonarWayCustomRules = registrarContext -> + registrarContext.registerDefaultQualityProfileRules(List.of(RuleKey.of("java", "S1108"))); + + JavaAgenticAIProfile profile = new JavaAgenticAIProfile(new ProfileRegistrar[]{agenticCustomRules, sonarWayCustomRules}); BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); profile.define(context); @@ -53,7 +60,7 @@ void profile_is_registered_as_expected() { BuiltInQualityProfilesDefinition.BuiltInQualityProfile actualProfile = profilesPerLanguages.get("java").get("Sonar agentic AI"); assertThat(actualProfile.isDefault()).isFalse(); assertThat(actualProfile.rules()) - .hasSize(465) + .hasSize(466) .extracting(BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey) .doesNotContainAnyElementsOf(List.of( "S101", @@ -113,6 +120,7 @@ void profile_is_registered_as_expected() { "S6242", "S6244", "S6246", + "S1108", "S6548", "S6804", "S6813", @@ -125,7 +133,10 @@ void profile_is_registered_as_expected() { "S8491", "S8692", "S8714" - )); + )) + .contains( + "S1107" + ); } diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSonarWayProfileTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSonarWayProfileTest.java index 7d88b9d52af..ab27c2e2606 100644 --- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSonarWayProfileTest.java +++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaSonarWayProfileTest.java @@ -50,10 +50,14 @@ void should_create_sonar_way_profile() { RuleKey.of("javasecurity", "S6287"))); ProfileRegistrar barCustomRules = registrarContext -> registrarContext.registerDefaultQualityProfileRules(List.of( RuleKey.of("javabugs", "S6466"))); + ProfileRegistrar bazCustomRules = registrarContext -> + registrarContext.registerRules("Sonar agentic AI", List.of(RuleKey.of("java", "S1107"))); JavaSonarWayProfile profileDef = new JavaSonarWayProfile(new ProfileRegistrar[] { fooCustomRules, - barCustomRules}); + barCustomRules, + bazCustomRules + }); BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); profileDef.define(context); BuiltInQualityProfilesDefinition.BuiltInQualityProfile profile = context.profile("java", "Sonar way"); @@ -72,7 +76,8 @@ void should_create_sonar_way_profile() { .doesNotContain(RuleKey.of("java", "S00116")) .contains(RuleKey.of("java", "S116")) .doesNotContain(RuleKey.of("java", "S6549")) - .doesNotContain(RuleKey.of("javasecurity", "S116")) + .doesNotContain(RuleKey.of("java", "S1107")) + .doesNotContain(RuleKey.of("javabugs", "S116")) .contains(RuleKey.of("javasecurity", "S6549")) .contains(RuleKey.of("javasecurity", "S6287")) .contains(RuleKey.of("javabugs", "S6466"));