From 56ad648d5618740004098af4d5dd519bd1f534af Mon Sep 17 00:00:00 2001 From: Dorian Burihabwa Date: Fri, 20 Mar 2026 11:57:56 +0100 Subject: [PATCH 1/8] SONARJAVA-6205 Add an agentic focused quality profile for Java --- .../plugins/java/JavaAgenticWayProfile.java | 52 ++ .../org/sonar/plugins/java/JavaPlugin.java | 1 + .../plugins/java/JavaSonarWayProfile.java | 16 +- .../plugins/java/QualityProfileUtils.java | 52 ++ .../java/rules/java/Agentic_way_profile.json | 473 ++++++++++++++++++ .../java/JavaAgenticWayProfileTest.java | 107 ++++ .../sonar/plugins/java/JavaPluginTest.java | 4 +- 7 files changed, 692 insertions(+), 13 deletions(-) create mode 100644 sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java create mode 100644 sonar-java-plugin/src/main/java/org/sonar/plugins/java/QualityProfileUtils.java create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Agentic_way_profile.json create mode 100644 sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java new file mode 100644 index 00000000000..67efdb9e5bc --- /dev/null +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java @@ -0,0 +1,52 @@ +/* + * SonarQube Java + * Copyright (C) 2012-2025 SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.plugins.java; + +import java.util.Set; +import javax.annotation.Nullable; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; +import org.sonar.plugins.java.api.ProfileRegistrar; +import org.sonarsource.api.sonarlint.SonarLintSide; + +@SonarLintSide +public class JavaAgenticWayProfile implements BuiltInQualityProfilesDefinition { + private final ProfileRegistrar[] profileRegistrars; + + /** + * Constructor used by Pico container (SC) when no ProfileRegistrar are available + */ + public JavaAgenticWayProfile() { + this(null); + } + + public JavaAgenticWayProfile(@Nullable ProfileRegistrar[] profileRegistrars) { + this.profileRegistrars = profileRegistrars; + } + + @Override + public void define(Context context) { + NewBuiltInQualityProfile agenticWay = context.createBuiltInQualityProfile("AI Quality Profile", Java.KEY); + Set ruleKeys = QualityProfileUtils.registerRulesFromJson( + "/org/sonar/l10n/java/rules/java/Agentic_way_profile.json", + this.profileRegistrars + ); + + ruleKeys.forEach(ruleKey -> agenticWay.activateRule(ruleKey.repository(), ruleKey.rule())); + agenticWay.done(); + } +} diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaPlugin.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaPlugin.java index c1f64a5613c..7cd9a3788cc 100644 --- a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaPlugin.java +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaPlugin.java @@ -62,6 +62,7 @@ public void define(Context context) { list.addAll(SurefireExtensions.getExtensions()); list.add(DroppedPropertiesSensor.class); list.add(JavaSonarWayProfile.class); + list.add(JavaAgenticWayProfile.class); list.add(ClasspathForMain.class); ExternalReportExtensions.define(context); 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 c2b47e553de..4f183d4ba4f 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 @@ -26,10 +26,8 @@ import org.slf4j.LoggerFactory; import org.sonar.api.rule.RuleKey; import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.java.GeneratedCheckList; import org.sonar.java.annotations.VisibleForTesting; import org.sonar.plugins.java.api.ProfileRegistrar; -import org.sonarsource.analyzer.commons.BuiltInQualityProfileJsonLoader; import org.sonarsource.api.sonarlint.SonarLintSide; /** @@ -66,12 +64,10 @@ public JavaSonarWayProfile(@Nullable ProfileRegistrar[] profileRegistrars) { @Override public void define(Context context) { NewBuiltInQualityProfile sonarWay = context.createBuiltInQualityProfile("Sonar way", Java.KEY); - Set ruleKeys = new HashSet<>(sonarJavaSonarWayRuleKeys()); - if (profileRegistrars != null) { - for (ProfileRegistrar profileRegistrar : profileRegistrars) { - profileRegistrar.register(ruleKeys::addAll); - } - } + Set ruleKeys = QualityProfileUtils.registerRulesFromJson( + SONAR_WAY_PATH, + this.profileRegistrars + ); // Former activation mechanism, it should be removed once sonar-security and sonar-dataflow-bug-detection // support the new mechanism: @@ -89,9 +85,7 @@ public void define(Context context) { } static Set sonarJavaSonarWayRuleKeys() { - return BuiltInQualityProfileJsonLoader.loadActiveKeysFromJsonProfile(SONAR_WAY_PATH).stream() - .map(rule -> RuleKey.of(GeneratedCheckList.REPOSITORY_KEY, rule)) - .collect(Collectors.toSet()); + return QualityProfileUtils.loadRuleKeys(SONAR_WAY_PATH); } @VisibleForTesting diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/QualityProfileUtils.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/QualityProfileUtils.java new file mode 100644 index 00000000000..ac768d9eded --- /dev/null +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/QualityProfileUtils.java @@ -0,0 +1,52 @@ +/* + * SonarQube Java + * Copyright (C) 2012-2025 SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.plugins.java; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.sonar.api.rule.RuleKey; +import org.sonar.java.GeneratedCheckList; +import org.sonar.plugins.java.api.ProfileRegistrar; +import org.sonarsource.analyzer.commons.BuiltInQualityProfileJsonLoader; + +class QualityProfileUtils { + private QualityProfileUtils() { + /* This utility class should not be instantiated */ + } + + static Set registerRulesFromJson( + String pathToJsonProfile, + @Nullable ProfileRegistrar[] profileRegistrars) { + + Set ruleKeys = new HashSet<>(loadRuleKeys(pathToJsonProfile)); + if (profileRegistrars != null) { + for (ProfileRegistrar profileRegistrar : profileRegistrars) { + profileRegistrar.register(ruleKeys::addAll); + } + } + + return ruleKeys; + } + + static Set loadRuleKeys(final String pathToJsonProfile) { + return BuiltInQualityProfileJsonLoader.loadActiveKeysFromJsonProfile(pathToJsonProfile).stream() + .map(rule -> RuleKey.of(GeneratedCheckList.REPOSITORY_KEY, rule)) + .collect(Collectors.toSet()); + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Agentic_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Agentic_way_profile.json new file mode 100644 index 00000000000..a13126eaaa7 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Agentic_way_profile.json @@ -0,0 +1,473 @@ +{ + "name": "AI Quality Profile", + "ruleKeys": [ + "S100", + "S106", + "S107", + "S108", + "S112", + "S119", + "S120", + "S125", + "S127", + "S128", + "S131", + "S135", + "S899", + "S1065", + "S1068", + "S1111", + "S1113", + "S1116", + "S1117", + "S1118", + "S1119", + "S1121", + "S1123", + "S1125", + "S1126", + "S1128", + "S1130", + "S1133", + "S1134", + "S1135", + "S1141", + "S1143", + "S1144", + "S1149", + "S1150", + "S1153", + "S1155", + "S1157", + "S1158", + "S1161", + "S1163", + "S1165", + "S1168", + "S1170", + "S1171", + "S1172", + "S1174", + "S1175", + "S1181", + "S1182", + "S1185", + "S1186", + "S1190", + "S1191", + "S1192", + "S1193", + "S1199", + "S1201", + "S1206", + "S1210", + "S1214", + "S1215", + "S1217", + "S1219", + "S1220", + "S1221", + "S1223", + "S1226", + "S1264", + "S1301", + "S1313", + "S1317", + "S1319", + "S1444", + "S1450", + "S1452", + "S1479", + "S1481", + "S1488", + "S1596", + "S1598", + "S1602", + "S1604", + "S1607", + "S1612", + "S1640", + "S1643", + "S1656", + "S1700", + "S1751", + "S1764", + "S1844", + "S1845", + "S1849", + "S1854", + "S1858", + "S1860", + "S1862", + "S1871", + "S1872", + "S1874", + "S1905", + "S1940", + "S1948", + "S1989", + "S1994", + "S2053", + "S2055", + "S2060", + "S2061", + "S2062", + "S2065", + "S2066", + "S2068", + "S2077", + "S2092", + "S2093", + "S2094", + "S2097", + "S2109", + "S2110", + "S2111", + "S2112", + "S2114", + "S2115", + "S2116", + "S2118", + "S2119", + "S2121", + "S2122", + "S2123", + "S2127", + "S2129", + "S2130", + "S2133", + "S2134", + "S2139", + "S2140", + "S2142", + "S2147", + "S2151", + "S2153", + "S2154", + "S2157", + "S2159", + "S2160", + "S2166", + "S2167", + "S2168", + "S2175", + "S2176", + "S2177", + "S2178", + "S2183", + "S2184", + "S2185", + "S2186", + "S2187", + "S2188", + "S2200", + "S2201", + "S2204", + "S2209", + "S2225", + "S2226", + "S2229", + "S2230", + "S2232", + "S2234", + "S2235", + "S2236", + "S2245", + "S2251", + "S2252", + "S2254", + "S2257", + "S2272", + "S2273", + "S2274", + "S2275", + "S2276", + "S2293", + "S2326", + "S2386", + "S2387", + "S2388", + "S2390", + "S2437", + "S2438", + "S2440", + "S2441", + "S2442", + "S2445", + "S2446", + "S2447", + "S2479", + "S2612", + "S2629", + "S2638", + "S2639", + "S2674", + "S2675", + "S2676", + "S2677", + "S2681", + "S2692", + "S2695", + "S2696", + "S2699", + "S2718", + "S2737", + "S2757", + "S2761", + "S2786", + "S2789", + "S2864", + "S2885", + "S2886", + "S2924", + "S2925", + "S2970", + "S2975", + "S3008", + "S3010", + "S3011", + "S3012", + "S3014", + "S3020", + "S3024", + "S3033", + "S3034", + "S3038", + "S3039", + "S3042", + "S3046", + "S3051", + "S3063", + "S3064", + "S3066", + "S3067", + "S3077", + "S3078", + "S3252", + "S3305", + "S3329", + "S3330", + "S3346", + "S3358", + "S3398", + "S3400", + "S3415", + "S3416", + "S3436", + "S3457", + "S3551", + "S3577", + "S3599", + "S3626", + "S3631", + "S3740", + "S3751", + "S3752", + "S3753", + "S3776", + "S3864", + "S3878", + "S3923", + "S3972", + "S3973", + "S3981", + "S3984", + "S3985", + "S3986", + "S4030", + "S4032", + "S4034", + "S4036", + "S4042", + "S4065", + "S4087", + "S4143", + "S4144", + "S4201", + "S4274", + "S4275", + "S4276", + "S4347", + "S4348", + "S4349", + "S4351", + "S4423", + "S4425", + "S4426", + "S4433", + "S4434", + "S4454", + "S4488", + "S4502", + "S4507", + "S4512", + "S4517", + "S4524", + "S4544", + "S4601", + "S4602", + "S4635", + "S4682", + "S4684", + "S4738", + "S4790", + "S4830", + "S4970", + "S4973", + "S5042", + "S5122", + "S5164", + "S5247", + "S5301", + "S5320", + "S5322", + "S5324", + "S5332", + "S5344", + "S5361", + "S5443", + "S5445", + "S5527", + "S5542", + "S5547", + "S5659", + "S5679", + "S5689", + "S5693", + "S5738", + "S5776", + "S5777", + "S5778", + "S5779", + "S5783", + "S5785", + "S5790", + "S5803", + "S5804", + "S5808", + "S5810", + "S5826", + "S5831", + "S5833", + "S5838", + "S5842", + "S5843", + "S5845", + "S5846", + "S5850", + "S5852", + "S5855", + "S5856", + "S5863", + "S5866", + "S5868", + "S5876", + "S5917", + "S5960", + "S5967", + "S5969", + "S5994", + "S5996", + "S5998", + "S6001", + "S6002", + "S6068", + "S6070", + "S6103", + "S6104", + "S6126", + "S6201", + "S6202", + "S6203", + "S6204", + "S6205", + "S6206", + "S6207", + "S6208", + "S6209", + "S6216", + "S6218", + "S6243", + "S6262", + "S6263", + "S6288", + "S6293", + "S6301", + "S6326", + "S6331", + "S6353", + "S6355", + "S6362", + "S6363", + "S6395", + "S6396", + "S6397", + "S6418", + "S6432", + "S6437", + "S6485", + "S6539", + "S6541", + "S6548", + "S6806", + "S6809", + "S6810", + "S6814", + "S6816", + "S6817", + "S6818", + "S6831", + "S6838", + "S6856", + "S6857", + "S6862", + "S6863", + "S6876", + "S6877", + "S6878", + "S6880", + "S6881", + "S6885", + "S6889", + "S6901", + "S6905", + "S6906", + "S6909", + "S6913", + "S6915", + "S6916", + "S7158", + "S7177", + "S7178", + "S7179", + "S7180", + "S7183", + "S7184", + "S7185", + "S7186", + "S7190", + "S7409", + "S7435", + "S7466", + "S7467", + "S7474", + "S7475", + "S7476", + "S7477", + "S7478", + "S7479", + "S7481", + "S7482", + "S7629", + "S8346", + "S8432", + "S8433", + "S8444", + "S8445", + "S8446", + "S8447", + "S8450", + "S8465", + "S8469" + ] +} 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 new file mode 100644 index 00000000000..1de94c21e9a --- /dev/null +++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java @@ -0,0 +1,107 @@ +/* + * SonarQube Java + * Copyright (C) 2012-2025 SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.plugins.java; + + +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +class JavaAgenticWayProfileTest { + + @Test + void profile_is_registered_as_expected() { + JavaAgenticWayProfile profile = new JavaAgenticWayProfile(); + BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); + profile.define(context); + + Map> profilesPerLanguages = context.profilesByLanguageAndName(); + assertThat(profilesPerLanguages).containsOnlyKeys("java"); + assertThat(profilesPerLanguages.get("java")).containsOnlyKeys("AI Quality Profile"); + + BuiltInQualityProfilesDefinition.BuiltInQualityProfile actualProfile = profilesPerLanguages.get("java").get("AI Quality Profile"); + assertThat(actualProfile.isDefault()).isFalse(); + assertThat(actualProfile.rules()) + .hasSize(468) + .extracting(BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey) + .doesNotContainAnyElementsOf(List.of( + "S101", + "S110", + "S114", + "S115", + "S116", + "S117", + "S1066", + "S1075", + "S1104", + "S1110", + "S1124", + "S1195", + "S1197", + "S1611", + "S1659", + "S1710", + "S4719", + "S4838", + "S4925", + "S4929", + "S4968", + "S4977", + "S5261", + "S5329", + "S5411", + "S5413", + "S5663", + "S5664", + "S5665", + "S5669", + "S5786", + "S5841", + "S5853", + "S5854", + "S5857", + "S5860", + "S5869", + "S5958", + "S5961", + "S5973", + "S5976", + "S5993", + "S6019", + "S6035", + "S6213", + "S6217", + "S6219", + "S6241", + "S6242", + "S6244", + "S6246", + "S6804", + "S6813", + "S6829", + "S6830", + "S6832", + "S6833", + "S6837", + "S6912", + "S8491" + )); + } +} diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaPluginTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaPluginTest.java index eb700626422..06031c47465 100644 --- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaPluginTest.java +++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaPluginTest.java @@ -51,7 +51,7 @@ void sonarqube_9_9_extensions() { Plugin.Context context = new Plugin.Context(sqCommunity); javaPlugin.define(context); assertThat(context.getExtensions()) - .hasSize(36) + .hasSize(37) .doesNotContain(Jasper.class); } @@ -61,7 +61,7 @@ void sonarqube_9_9_commercial_extensions() { Plugin.Context context = new Plugin.Context(sqEnterprise); javaPlugin.define(context); assertThat(context.getExtensions()) - .hasSize(37) + .hasSize(38) .contains(Jasper.class); } From 6bc7087116fed7c40f124ee0edebe884033790ae Mon Sep 17 00:00:00 2001 From: Dorian Burihabwa Date: Mon, 23 Mar 2026 17:56:41 +0100 Subject: [PATCH 2/8] SONARJAVA-6205 Add test script to generate profile from CSV file --- pom.xml | 5 + sonar-java-plugin/pom.xml | 5 + .../java/JavaAgenticWayProfileTest.java | 91 +++++++++++++++++++ 3 files changed, 101 insertions(+) diff --git a/pom.xml b/pom.xml index 3fd1a140b57..fd0fa8f0522 100644 --- a/pom.xml +++ b/pom.xml @@ -280,6 +280,11 @@ commons-lang3 3.20.0 + + org.apache.commons + commons-csv + 1.14.1 + org.springframework spring-expression diff --git a/sonar-java-plugin/pom.xml b/sonar-java-plugin/pom.xml index e8818a56508..7640a1028ab 100644 --- a/sonar-java-plugin/pom.xml +++ b/sonar-java-plugin/pom.xml @@ -137,6 +137,11 @@ ${project.version} test + + org.apache.commons + commons-csv + test + 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 1de94c21e9a..b698eab4f2c 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 @@ -17,8 +17,19 @@ package org.sonar.plugins.java; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.commons.csv.CSVFormat; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; @@ -26,6 +37,8 @@ class JavaAgenticWayProfileTest { + private static final Path RULE_DESCRIPTION_DIRECTORY = Path.of("sonar-java-plugin", "src", "main", "resources", "org", "sonar", "l10n", "java", "rules", "java"); + @Test void profile_is_registered_as_expected() { JavaAgenticWayProfile profile = new JavaAgenticWayProfile(); @@ -104,4 +117,82 @@ void profile_is_registered_as_expected() { "S8491" )); } + + @Disabled("This method should be used as a utility method to generate a quality profile from a CSV file") + void generate_ai_quality_profile() throws IOException { + generate( + Path.of("Path", "to", "your", "input.csv"), + RULE_DESCRIPTION_DIRECTORY.resolve("Agentic_way_profile.json"), + "AI Quality Profile" + ); + } + + public static Path generate(Path input, Path output, String profileName) throws IOException { + CSVFormat csvFormat = CSVFormat.DEFAULT.builder() + .setHeader() + .setSkipHeaderRecord(true) + .get(); + + Set keysOfImplementedRules = getImplementedRuleKeys(); + + String ruleKeys; + try (FileReader in = new FileReader(input.toFile(), StandardCharsets.UTF_8)) { + ruleKeys = csvFormat.parse(in).stream() + // Filter rules that have not been classified as AI and Sonar Way + .filter(ruleRecord -> "AI and Sonar way".equalsIgnoreCase(ruleRecord.get("classification_status"))) + // Recover keys + .map(ruleRecord -> ruleRecord.get("ruleid")) + // Filter out keys that do not have a sonar-java implementation + .filter(keysOfImplementedRules::contains) + // Sort keys + .sorted(JavaAgenticWayProfileTest::compareRuleKeys) + // Surround with double quotes for output in JSON document + .map(" \"%s\""::formatted) + // Comma and line separate rule keys + .collect(Collectors.joining(",%s".formatted(System.lineSeparator()))); + } + try ( + FileWriter out = new FileWriter(output.toFile(), StandardCharsets.UTF_8); + BufferedWriter writer = new BufferedWriter(out) + ) { + writer.write("{"); + writer.newLine(); + writer.write(" \"name\": \"%s\",".formatted(profileName)); + writer.newLine(); + writer.write(" \"ruleKeys\": ["); + writer.newLine(); + writer.write(ruleKeys); + writer.newLine(); + writer.write(" ]"); + writer.newLine(); + writer.write("}"); + } + return output; + + } + + private static Set getImplementedRuleKeys() throws IOException { + if (!Files.isDirectory(RULE_DESCRIPTION_DIRECTORY)) { + throw new IllegalStateException("This should not happen!"); + } + return Files.list(RULE_DESCRIPTION_DIRECTORY) + .filter(path -> !path.endsWith("_profile.json")) + .map(path -> { + String fileName = path.getFileName().toString(); + return fileName.substring(0, fileName.lastIndexOf('.')); + }) + .collect(Collectors.toSet()); + } + + private static Integer getSortingKey(String ruleKey) { + try { + return Integer.parseInt(ruleKey.substring(1)); + } catch (NumberFormatException ignored) { + return Integer.MIN_VALUE; + } + } + + private static int compareRuleKeys(String first, String second) { + return getSortingKey(first) - getSortingKey(second); + } } From 696b0e74ca439a13c4a4f0bd8f39692455ddbf84 Mon Sep 17 00:00:00 2001 From: Dorian Burihabwa Date: Mon, 23 Mar 2026 17:57:45 +0100 Subject: [PATCH 3/8] SONARJAVA-6205 Remove S100 from AI quality profile --- .../org/sonar/l10n/java/rules/java/Agentic_way_profile.json | 3 +-- .../java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Agentic_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Agentic_way_profile.json index a13126eaaa7..3163088ae65 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Agentic_way_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Agentic_way_profile.json @@ -1,7 +1,6 @@ { "name": "AI Quality Profile", "ruleKeys": [ - "S100", "S106", "S107", "S108", @@ -470,4 +469,4 @@ "S8465", "S8469" ] -} +} \ No newline at end of file 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 b698eab4f2c..582d05a2d6f 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 @@ -52,7 +52,7 @@ void profile_is_registered_as_expected() { BuiltInQualityProfilesDefinition.BuiltInQualityProfile actualProfile = profilesPerLanguages.get("java").get("AI Quality Profile"); assertThat(actualProfile.isDefault()).isFalse(); assertThat(actualProfile.rules()) - .hasSize(468) + .hasSize(467) .extracting(BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey) .doesNotContainAnyElementsOf(List.of( "S101", From 01f617c20aa75b6d4e0391eb3ae522b6f8e42926 Mon Sep 17 00:00:00 2001 From: Dorian Burihabwa Date: Thu, 26 Mar 2026 11:39:10 +0100 Subject: [PATCH 4/8] SONARJAVA-6205 Include DBD and security rules as part of Agentic profile Refactored to re-use the same code to initialize quality profiles assuming that all DBD and security rules should be part of the new quality profile. --- .../java/BuiltInJavaQualityProfile.java | 127 ++++++++++++++++++ .../plugins/java/JavaAgenticWayProfile.java | 31 ++--- .../plugins/java/JavaSonarWayProfile.java | 79 ++--------- .../plugins/java/QualityProfileUtils.java | 52 ------- .../java/JavaAgenticWayProfileTest.java | 10 +- .../plugins/java/JavaSonarWayProfileTest.java | 15 ++- 6 files changed, 167 insertions(+), 147 deletions(-) create mode 100644 sonar-java-plugin/src/main/java/org/sonar/plugins/java/BuiltInJavaQualityProfile.java delete mode 100644 sonar-java-plugin/src/main/java/org/sonar/plugins/java/QualityProfileUtils.java 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 new file mode 100644 index 00000000000..07e8a97b6da --- /dev/null +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/BuiltInJavaQualityProfile.java @@ -0,0 +1,127 @@ +/* + * SonarQube Java + * Copyright (C) 2012-2025 SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.plugins.java; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; +import org.sonar.java.GeneratedCheckList; +import org.sonar.java.annotations.VisibleForTesting; +import org.sonar.plugins.java.api.ProfileRegistrar; +import org.sonarsource.analyzer.commons.BuiltInQualityProfileJsonLoader; + + +/** + * Defines a Java quality profile including rules from sonar-java, Java SE, DBD and Security. + */ +abstract class BuiltInJavaQualityProfile implements BuiltInQualityProfilesDefinition { + private static final Logger LOG = LoggerFactory.getLogger(BuiltInJavaQualityProfile.class); + + static final String SECURITY_RULES_CLASS_NAME = "com.sonar.plugins.security.api.JavaRules"; + static final String DBD_RULES_CLASS_NAME = "com.sonarsource.plugins.dbd.api.JavaRules"; + static final String SECURITY_RULE_KEYS_METHOD_NAME = "getSecurityRuleKeys"; + static final String DBD_RULE_KEYS_METHOD_NAME = "getDataflowBugDetectionRuleKeys"; + static final String GET_REPOSITORY_KEY = "getRepositoryKey"; + static final String SECURITY_REPOSITORY_KEY = "javasecurity"; + static final String DBD_REPOSITORY_KEY = "javabugs"; + + + protected final ProfileRegistrar[] profileRegistrars; + + BuiltInJavaQualityProfile(@Nullable ProfileRegistrar[] profileRegistrars) { + this.profileRegistrars = profileRegistrars; + } + + abstract String getProfileName(); + + abstract String getPathToJsonProfile(); + + abstract boolean isDefault(); + + @Override + public void define(Context context) { + // Create a new profile + BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile(getProfileName(), Java.KEY); + // Load rules from local JSON + Set ruleKeys = registerRulesFromJson(getPathToJsonProfile(), profileRegistrars); + + // FIXME as part of SONARJAVA-6207 + // Former activation mechanism, it should be removed once sonar-security and sonar-dataflow-bug-detection + // support the new mechanism: + // registrarContext.internal().registerDefaultQualityProfileRules(ruleKeys); + // For now, it still uses reflexion if rules are not yet defined + if (ruleKeys.stream().noneMatch(rule -> SECURITY_REPOSITORY_KEY.equals(rule.repository()))) { + ruleKeys.addAll(getSecurityRuleKeys()); + } + if (ruleKeys.stream().noneMatch(rule -> DBD_REPOSITORY_KEY.equals(rule.repository()))) { + ruleKeys.addAll(getDataflowBugDetectionRuleKeys()); + } + + ruleKeys.forEach(ruleKey -> profile.activateRule(ruleKey.repository(), ruleKey.rule())); + profile.setDefault(isDefault()); + profile.done(); + } + + static Set registerRulesFromJson(String pathToJsonProfile, @Nullable ProfileRegistrar[] profileRegistrars) { + Set ruleKeys = new HashSet<>(loadRuleKeys(pathToJsonProfile)); + if (profileRegistrars != null) { + for (ProfileRegistrar profileRegistrar : profileRegistrars) { + profileRegistrar.register(ruleKeys::addAll); + } + } + + return ruleKeys; + } + + static Set loadRuleKeys(final String pathToJsonProfile) { + return BuiltInQualityProfileJsonLoader.loadActiveKeysFromJsonProfile(pathToJsonProfile).stream() + .map(rule -> RuleKey.of(GeneratedCheckList.REPOSITORY_KEY, rule)) + .collect(Collectors.toSet()); + } + + @VisibleForTesting + Set getSecurityRuleKeys() { + return getExternalRuleKeys(SECURITY_RULES_CLASS_NAME, SECURITY_RULE_KEYS_METHOD_NAME, "security"); + } + + @VisibleForTesting + Set getDataflowBugDetectionRuleKeys() { + return getExternalRuleKeys(DBD_RULES_CLASS_NAME, DBD_RULE_KEYS_METHOD_NAME, "dataflow bug detection"); + } + + @VisibleForTesting + Set getExternalRuleKeys(String className, String ruleKeysMethod, String rulesCategory) { + try { + Class javaRulesClass = Class.forName(className); + Method getRuleKeysMethod = javaRulesClass.getMethod(ruleKeysMethod); + Set ruleKeys = (Set) getRuleKeysMethod.invoke(null); + Method getRepositoryKeyMethod = javaRulesClass.getMethod(GET_REPOSITORY_KEY); + String repositoryKey = (String) getRepositoryKeyMethod.invoke(null); + return ruleKeys.stream().map(k -> RuleKey.of(repositoryKey, k)).collect(Collectors.toSet()); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + LOG.debug(String.format("[%s], no %s rules added to %s java profile: %s", e.getClass().getSimpleName(), rulesCategory, getProfileName(), e.getMessage())); + } + return new HashSet<>(); + } +} diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java index 67efdb9e5bc..5b94afc4e0e 100644 --- a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java @@ -16,37 +16,32 @@ */ package org.sonar.plugins.java; -import java.util.Set; import javax.annotation.Nullable; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; import org.sonar.plugins.java.api.ProfileRegistrar; import org.sonarsource.api.sonarlint.SonarLintSide; @SonarLintSide -public class JavaAgenticWayProfile implements BuiltInQualityProfilesDefinition { - private final ProfileRegistrar[] profileRegistrars; - - /** - * Constructor used by Pico container (SC) when no ProfileRegistrar are available - */ +public class JavaAgenticWayProfile extends BuiltInJavaQualityProfile { public JavaAgenticWayProfile() { this(null); } public JavaAgenticWayProfile(@Nullable ProfileRegistrar[] profileRegistrars) { - this.profileRegistrars = profileRegistrars; + super(profileRegistrars); + } + + @Override + String getProfileName() { + return "AI Quality Profile"; } @Override - public void define(Context context) { - NewBuiltInQualityProfile agenticWay = context.createBuiltInQualityProfile("AI Quality Profile", Java.KEY); - Set ruleKeys = QualityProfileUtils.registerRulesFromJson( - "/org/sonar/l10n/java/rules/java/Agentic_way_profile.json", - this.profileRegistrars - ); + String getPathToJsonProfile() { + return "/org/sonar/l10n/java/rules/java/Agentic_way_profile.json"; + } - ruleKeys.forEach(ruleKey -> agenticWay.activateRule(ruleKey.repository(), ruleKey.rule())); - agenticWay.done(); + @Override + boolean isDefault() { + return false; } } 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 4f183d4ba4f..6705e0033fd 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 @@ -16,17 +16,9 @@ */ package org.sonar.plugins.java; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; import javax.annotation.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.sonar.api.rule.RuleKey; -import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; -import org.sonar.java.annotations.VisibleForTesting; import org.sonar.plugins.java.api.ProfileRegistrar; import org.sonarsource.api.sonarlint.SonarLintSide; @@ -34,22 +26,9 @@ * define built-in profile */ @SonarLintSide -public class JavaSonarWayProfile implements BuiltInQualityProfilesDefinition { - - private static final Logger LOG = LoggerFactory.getLogger(JavaSonarWayProfile.class); - - static final String SECURITY_RULES_CLASS_NAME = "com.sonar.plugins.security.api.JavaRules"; - static final String DBD_RULES_CLASS_NAME = "com.sonarsource.plugins.dbd.api.JavaRules"; - static final String SECURITY_RULE_KEYS_METHOD_NAME = "getSecurityRuleKeys"; - static final String DBD_RULE_KEYS_METHOD_NAME = "getDataflowBugDetectionRuleKeys"; - static final String GET_REPOSITORY_KEY = "getRepositoryKey"; - static final String SECURITY_REPOSITORY_KEY = "javasecurity"; - static final String DBD_REPOSITORY_KEY = "javabugs"; - +public class JavaSonarWayProfile extends BuiltInJavaQualityProfile { static final String SONAR_WAY_PATH = "/org/sonar/l10n/java/rules/java/Sonar_way_profile.json"; - private final ProfileRegistrar[] profileRegistrars; - /** * Constructor used by Pico container (SC) when no ProfileRegistrar are available */ @@ -58,59 +37,25 @@ public JavaSonarWayProfile() { } public JavaSonarWayProfile(@Nullable ProfileRegistrar[] profileRegistrars) { - this.profileRegistrars = profileRegistrars; + super(profileRegistrars); } @Override - public void define(Context context) { - NewBuiltInQualityProfile sonarWay = context.createBuiltInQualityProfile("Sonar way", Java.KEY); - Set ruleKeys = QualityProfileUtils.registerRulesFromJson( - SONAR_WAY_PATH, - this.profileRegistrars - ); - - // Former activation mechanism, it should be removed once sonar-security and sonar-dataflow-bug-detection - // support the new mechanism: - // registrarContext.internal().registerDefaultQualityProfileRules(ruleKeys); - // For now, it still uses reflexion if rules are not yet defined - if (ruleKeys.stream().noneMatch(rule -> SECURITY_REPOSITORY_KEY.equals(rule.repository()))) { - ruleKeys.addAll(getSecurityRuleKeys()); - } - if (ruleKeys.stream().noneMatch(rule -> DBD_REPOSITORY_KEY.equals(rule.repository()))) { - ruleKeys.addAll(getDataflowBugDetectionRuleKeys()); - } - - ruleKeys.forEach(ruleKey -> sonarWay.activateRule(ruleKey.repository(), ruleKey.rule())); - sonarWay.done(); - } - - static Set sonarJavaSonarWayRuleKeys() { - return QualityProfileUtils.loadRuleKeys(SONAR_WAY_PATH); + String getProfileName() { + return "Sonar way"; } - @VisibleForTesting - static Set getSecurityRuleKeys() { - return getExternalRuleKeys(SECURITY_RULES_CLASS_NAME, SECURITY_RULE_KEYS_METHOD_NAME, "security"); + @Override + String getPathToJsonProfile() { + return SONAR_WAY_PATH; } - @VisibleForTesting - static Set getDataflowBugDetectionRuleKeys() { - return getExternalRuleKeys(DBD_RULES_CLASS_NAME, DBD_RULE_KEYS_METHOD_NAME, "dataflow bug detection"); + @Override + boolean isDefault() { + return true; } - @SuppressWarnings("unchecked") - @VisibleForTesting - static Set getExternalRuleKeys(String className, String ruleKeysMethod, String rulesCategory) { - try { - Class javaRulesClass = Class.forName(className); - Method getRuleKeysMethod = javaRulesClass.getMethod(ruleKeysMethod); - Set ruleKeys = (Set) getRuleKeysMethod.invoke(null); - Method getRepositoryKeyMethod = javaRulesClass.getMethod(GET_REPOSITORY_KEY); - String repositoryKey = (String) getRepositoryKeyMethod.invoke(null); - return ruleKeys.stream().map(k -> RuleKey.of(repositoryKey, k)).collect(Collectors.toSet()); - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - LOG.debug(String.format("[%s], no %s rules added to Sonar way java profile: %s", e.getClass().getSimpleName(), rulesCategory, e.getMessage())); - } - return new HashSet<>(); + public static Set sonarJavaSonarWayRuleKeys() { + return loadRuleKeys(SONAR_WAY_PATH); } } diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/QualityProfileUtils.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/QualityProfileUtils.java deleted file mode 100644 index ac768d9eded..00000000000 --- a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/QualityProfileUtils.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube Java - * Copyright (C) 2012-2025 SonarSource Sàrl - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA. - * - * 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 Sonar Source-Available License for more details. - * - * You should have received a copy of the Sonar Source-Available License - * along with this program; if not, see https://sonarsource.com/license/ssal/ - */ -package org.sonar.plugins.java; - -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; -import javax.annotation.Nullable; -import org.sonar.api.rule.RuleKey; -import org.sonar.java.GeneratedCheckList; -import org.sonar.plugins.java.api.ProfileRegistrar; -import org.sonarsource.analyzer.commons.BuiltInQualityProfileJsonLoader; - -class QualityProfileUtils { - private QualityProfileUtils() { - /* This utility class should not be instantiated */ - } - - static Set registerRulesFromJson( - String pathToJsonProfile, - @Nullable ProfileRegistrar[] profileRegistrars) { - - Set ruleKeys = new HashSet<>(loadRuleKeys(pathToJsonProfile)); - if (profileRegistrars != null) { - for (ProfileRegistrar profileRegistrar : profileRegistrars) { - profileRegistrar.register(ruleKeys::addAll); - } - } - - return ruleKeys; - } - - static Set loadRuleKeys(final String pathToJsonProfile) { - return BuiltInQualityProfileJsonLoader.loadActiveKeysFromJsonProfile(pathToJsonProfile).stream() - .map(rule -> RuleKey.of(GeneratedCheckList.REPOSITORY_KEY, rule)) - .collect(Collectors.toSet()); - } -} 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 582d05a2d6f..f468edfcd0d 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 @@ -29,6 +29,7 @@ import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.csv.CSVFormat; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; @@ -118,16 +119,19 @@ void profile_is_registered_as_expected() { )); } - @Disabled("This method should be used as a utility method to generate a quality profile from a CSV file") + + @Test + @Disabled("A utility method to generate a quality profile from a CSV file") void generate_ai_quality_profile() throws IOException { - generate( + Path generatedQualityProfile = generate( Path.of("Path", "to", "your", "input.csv"), RULE_DESCRIPTION_DIRECTORY.resolve("Agentic_way_profile.json"), "AI Quality Profile" ); + Assertions.fail(String.format("The generated quality profile was written to %s".formatted(generatedQualityProfile))); } - public static Path generate(Path input, Path output, String profileName) throws IOException { + static Path generate(Path input, Path output, String profileName) throws IOException { CSVFormat csvFormat = CSVFormat.DEFAULT.builder() .setHeader() .setSkipHeaderRecord(true) 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 25a1c717e18..b99a8014896 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 @@ -62,6 +62,7 @@ void should_create_sonar_way_profile() { assertThat(activeRules.stream().filter(r -> r.repoKey().equals("common-java"))).isEmpty(); assertThat(activeRules).as("Expected number of rules in profile").hasSizeGreaterThanOrEqualTo(268); assertThat(profile.name()).isEqualTo("Sonar way"); + assertThat(profile.isDefault()).isTrue(); Set keys = new HashSet<>(); for (BuiltInQualityProfilesDefinition.BuiltInActiveRule activeRule : activeRules) { keys.add(RuleKey.of(activeRule.repoKey(), activeRule.ruleKey())); @@ -95,40 +96,40 @@ void should_activate_hotspots_when_supported() { void should_contains_security_rules_if_present() { // no security rules available com.sonar.plugins.security.api.JavaRules.ruleKeys = new HashSet<>(); - assertThat(JavaSonarWayProfile.getSecurityRuleKeys()).isEmpty(); + assertThat(new JavaSonarWayProfile().getSecurityRuleKeys()).isEmpty(); // one security rule available com.sonar.plugins.security.api.JavaRules.ruleKeys = new HashSet<>(Arrays.asList("S3649")); - assertThat(JavaSonarWayProfile.getSecurityRuleKeys()).containsOnly(RuleKey.of("security-repo-key", "S3649")); + assertThat(new JavaSonarWayProfile().getSecurityRuleKeys()).containsOnly(RuleKey.of("security-repo-key", "S3649")); } @Test void should_contains_dataflow_bug_detection_rules_if_present() { // no dataflow bug detection rules available com.sonarsource.plugins.dbd.api.JavaRules.ruleKeys = new HashSet<>(); - assertThat(JavaSonarWayProfile.getDataflowBugDetectionRuleKeys()).isEmpty(); + assertThat(new JavaSonarWayProfile().getDataflowBugDetectionRuleKeys()).isEmpty(); // one dataflow bug detection rule available com.sonarsource.plugins.dbd.api.JavaRules.ruleKeys = new HashSet<>(Arrays.asList("S6322")); - assertThat(JavaSonarWayProfile.getDataflowBugDetectionRuleKeys()).containsOnly(RuleKey.of("dbd-repo-key", "S6322")); + assertThat(new JavaSonarWayProfile().getDataflowBugDetectionRuleKeys()).containsOnly(RuleKey.of("dbd-repo-key", "S6322")); } @Test void external_rule_keys_missing_class() { - JavaSonarWayProfile.getExternalRuleKeys("silly.name", "getDataflowBugDetectionRuleKeys", "ruleCategory"); + new JavaSonarWayProfile().getExternalRuleKeys("silly.name", "getDataflowBugDetectionRuleKeys", "ruleCategory"); assertThat(logTester.logs(Level.DEBUG)).containsExactly("[ClassNotFoundException], no ruleCategory rules added to Sonar way java profile: silly.name"); } @Test void external_rule_keys_missing_method() { - JavaSonarWayProfile.getExternalRuleKeys(DBD_RULES_CLASS_NAME, "nonExistingRuleKeysMethod", "ruleCategory"); + new JavaSonarWayProfile().getExternalRuleKeys(DBD_RULES_CLASS_NAME, "nonExistingRuleKeysMethod", "ruleCategory"); assertThat(logTester.logs(Level.DEBUG)) .containsExactly("[NoSuchMethodException], no ruleCategory rules added to Sonar way java profile: com.sonarsource.plugins.dbd.api.JavaRules.nonExistingRuleKeysMethod()"); } @Test void external_rule_keys_method_throws_exception() { - JavaSonarWayProfile.getExternalRuleKeys(DBD_RULES_CLASS_NAME, "methodThrowingException", "ruleCategory"); + new JavaSonarWayProfile().getExternalRuleKeys(DBD_RULES_CLASS_NAME, "methodThrowingException", "ruleCategory"); assertThat(logTester.logs(Level.DEBUG)).containsExactly("[InvocationTargetException], no ruleCategory rules added to Sonar way java profile: null"); } } From eda5bf4fe3b0e3e443b5d48aa3ad5dca96a719f3 Mon Sep 17 00:00:00 2001 From: Dorian Burihabwa Date: Fri, 27 Mar 2026 13:51:36 +0100 Subject: [PATCH 5/8] SONARJAVA-6205 Rename profile to 'Sonar Agentic AI' --- .../plugins/java/JavaAgenticWayProfile.java | 6 ++++-- ...rofile.json => Sonar_agentic_ai_profile.json} | 2 +- .../plugins/java/JavaAgenticWayProfileTest.java | 16 ++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) rename sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/{Agentic_way_profile.json => Sonar_agentic_ai_profile.json} (99%) diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java index 5b94afc4e0e..b56d46a6677 100644 --- a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java @@ -22,6 +22,8 @@ @SonarLintSide public class JavaAgenticWayProfile extends BuiltInJavaQualityProfile { + static final String PROFILE_NAME = "Sonar agentic AI"; + public JavaAgenticWayProfile() { this(null); } @@ -32,12 +34,12 @@ public JavaAgenticWayProfile(@Nullable ProfileRegistrar[] profileRegistrars) { @Override String getProfileName() { - return "AI Quality Profile"; + return PROFILE_NAME; } @Override String getPathToJsonProfile() { - return "/org/sonar/l10n/java/rules/java/Agentic_way_profile.json"; + return "/org/sonar/l10n/java/rules/java/Sonar_agentic_ai_profile.json"; } @Override diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Agentic_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_ai_profile.json similarity index 99% rename from sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Agentic_way_profile.json rename to sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_ai_profile.json index 3163088ae65..8938ea8aad5 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Agentic_way_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_ai_profile.json @@ -1,5 +1,5 @@ { - "name": "AI Quality Profile", + "name": "Sonar Agentic AI", "ruleKeys": [ "S106", "S107", 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 f468edfcd0d..961c082271f 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 @@ -38,7 +38,7 @@ class JavaAgenticWayProfileTest { - private static final Path RULE_DESCRIPTION_DIRECTORY = Path.of("sonar-java-plugin", "src", "main", "resources", "org", "sonar", "l10n", "java", "rules", "java"); + private static final Path RULE_DESCRIPTION_DIRECTORY = Path.of("src", "main", "resources", "org", "sonar", "l10n", "java", "rules", "java"); @Test void profile_is_registered_as_expected() { @@ -48,9 +48,9 @@ void profile_is_registered_as_expected() { Map> profilesPerLanguages = context.profilesByLanguageAndName(); assertThat(profilesPerLanguages).containsOnlyKeys("java"); - assertThat(profilesPerLanguages.get("java")).containsOnlyKeys("AI Quality Profile"); + assertThat(profilesPerLanguages.get("java")).containsOnlyKeys("Sonar agentic AI"); - BuiltInQualityProfilesDefinition.BuiltInQualityProfile actualProfile = profilesPerLanguages.get("java").get("AI Quality Profile"); + BuiltInQualityProfilesDefinition.BuiltInQualityProfile actualProfile = profilesPerLanguages.get("java").get("Sonar agentic AI"); assertThat(actualProfile.isDefault()).isFalse(); assertThat(actualProfile.rules()) .hasSize(467) @@ -124,9 +124,9 @@ void profile_is_registered_as_expected() { @Disabled("A utility method to generate a quality profile from a CSV file") void generate_ai_quality_profile() throws IOException { Path generatedQualityProfile = generate( - Path.of("Path", "to", "your", "input.csv"), - RULE_DESCRIPTION_DIRECTORY.resolve("Agentic_way_profile.json"), - "AI Quality Profile" + Path.of("Path to your CSV input file relative to sonar-java-plugin"), + RULE_DESCRIPTION_DIRECTORY.resolve("Sonar_agentic_ai_profile.json"), + JavaAgenticWayProfile.PROFILE_NAME ); Assertions.fail(String.format("The generated quality profile was written to %s".formatted(generatedQualityProfile))); } @@ -177,10 +177,10 @@ static Path generate(Path input, Path output, String profileName) throws IOExcep private static Set getImplementedRuleKeys() throws IOException { if (!Files.isDirectory(RULE_DESCRIPTION_DIRECTORY)) { - throw new IllegalStateException("This should not happen!"); + throw new IllegalStateException("Could not find path to %s".formatted(RULE_DESCRIPTION_DIRECTORY)); } return Files.list(RULE_DESCRIPTION_DIRECTORY) - .filter(path -> !path.endsWith("_profile.json")) + .filter(path -> !path.toString().endsWith("_profile.json")) .map(path -> { String fileName = path.getFileName().toString(); return fileName.substring(0, fileName.lastIndexOf('.')); From d70455a82b43f58bb2099e2e8c15239659418811 Mon Sep 17 00:00:00 2001 From: Dorian Burihabwa Date: Mon, 30 Mar 2026 13:13:22 +0200 Subject: [PATCH 6/8] SONARJAVA-6205 Restrict visibility of utility test methods --- .../main/java/org/sonar/plugins/java/JavaSonarWayProfile.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 6705e0033fd..f8792aadce1 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 @@ -23,7 +23,7 @@ import org.sonarsource.api.sonarlint.SonarLintSide; /** - * define built-in profile + * Define the default Sonar way profile. */ @SonarLintSide public class JavaSonarWayProfile extends BuiltInJavaQualityProfile { @@ -55,7 +55,7 @@ boolean isDefault() { return true; } - public static Set sonarJavaSonarWayRuleKeys() { + static Set sonarJavaSonarWayRuleKeys() { return loadRuleKeys(SONAR_WAY_PATH); } } From a6c1b6bb1b10cc73e428b5ca517d3f3e7b567165 Mon Sep 17 00:00:00 2001 From: Dorian Burihabwa Date: Mon, 30 Mar 2026 14:29:09 +0200 Subject: [PATCH 7/8] SONARJAVA-6205 Remove reflection to load DBD rule keys DBD registers rules using ProfileRegistrar defined in the sonar-java API and is therefore automatically loaded into all built-in quality profiles. --- .../sonar/plugins/java/BuiltInJavaQualityProfile.java | 10 ---------- .../sonar/plugins/java/JavaSonarWayProfileTest.java | 11 ----------- 2 files changed, 21 deletions(-) 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 07e8a97b6da..2858e5c702d 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 @@ -41,10 +41,8 @@ abstract class BuiltInJavaQualityProfile implements BuiltInQualityProfilesDefini static final String SECURITY_RULES_CLASS_NAME = "com.sonar.plugins.security.api.JavaRules"; static final String DBD_RULES_CLASS_NAME = "com.sonarsource.plugins.dbd.api.JavaRules"; static final String SECURITY_RULE_KEYS_METHOD_NAME = "getSecurityRuleKeys"; - static final String DBD_RULE_KEYS_METHOD_NAME = "getDataflowBugDetectionRuleKeys"; static final String GET_REPOSITORY_KEY = "getRepositoryKey"; static final String SECURITY_REPOSITORY_KEY = "javasecurity"; - static final String DBD_REPOSITORY_KEY = "javabugs"; protected final ProfileRegistrar[] profileRegistrars; @@ -74,9 +72,6 @@ public void define(Context context) { if (ruleKeys.stream().noneMatch(rule -> SECURITY_REPOSITORY_KEY.equals(rule.repository()))) { ruleKeys.addAll(getSecurityRuleKeys()); } - if (ruleKeys.stream().noneMatch(rule -> DBD_REPOSITORY_KEY.equals(rule.repository()))) { - ruleKeys.addAll(getDataflowBugDetectionRuleKeys()); - } ruleKeys.forEach(ruleKey -> profile.activateRule(ruleKey.repository(), ruleKey.rule())); profile.setDefault(isDefault()); @@ -105,11 +100,6 @@ Set getSecurityRuleKeys() { return getExternalRuleKeys(SECURITY_RULES_CLASS_NAME, SECURITY_RULE_KEYS_METHOD_NAME, "security"); } - @VisibleForTesting - Set getDataflowBugDetectionRuleKeys() { - return getExternalRuleKeys(DBD_RULES_CLASS_NAME, DBD_RULE_KEYS_METHOD_NAME, "dataflow bug detection"); - } - @VisibleForTesting Set getExternalRuleKeys(String className, String ruleKeysMethod, String rulesCategory) { try { 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 b99a8014896..b9c23a450e7 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 @@ -103,17 +103,6 @@ void should_contains_security_rules_if_present() { assertThat(new JavaSonarWayProfile().getSecurityRuleKeys()).containsOnly(RuleKey.of("security-repo-key", "S3649")); } - @Test - void should_contains_dataflow_bug_detection_rules_if_present() { - // no dataflow bug detection rules available - com.sonarsource.plugins.dbd.api.JavaRules.ruleKeys = new HashSet<>(); - assertThat(new JavaSonarWayProfile().getDataflowBugDetectionRuleKeys()).isEmpty(); - - // one dataflow bug detection rule available - com.sonarsource.plugins.dbd.api.JavaRules.ruleKeys = new HashSet<>(Arrays.asList("S6322")); - assertThat(new JavaSonarWayProfile().getDataflowBugDetectionRuleKeys()).containsOnly(RuleKey.of("dbd-repo-key", "S6322")); - } - @Test void external_rule_keys_missing_class() { new JavaSonarWayProfile().getExternalRuleKeys("silly.name", "getDataflowBugDetectionRuleKeys", "ruleCategory"); From 1c4cd23c4771208d6bc7cd17d38d73dad789f513 Mon Sep 17 00:00:00 2001 From: Dorian Burihabwa Date: Mon, 30 Mar 2026 17:42:24 +0200 Subject: [PATCH 8/8] SONARJAVA-6205 Rename class to match profile name --- ...JavaAgenticWayProfile.java => JavaAgenticAIProfile.java} | 6 +++--- .../src/main/java/org/sonar/plugins/java/JavaPlugin.java | 2 +- .../org/sonar/plugins/java/JavaAgenticWayProfileTest.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename sonar-java-plugin/src/main/java/org/sonar/plugins/java/{JavaAgenticWayProfile.java => JavaAgenticAIProfile.java} (86%) diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticAIProfile.java similarity index 86% rename from sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java rename to sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticAIProfile.java index b56d46a6677..1831bc0b68d 100644 --- a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticWayProfile.java +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaAgenticAIProfile.java @@ -21,14 +21,14 @@ import org.sonarsource.api.sonarlint.SonarLintSide; @SonarLintSide -public class JavaAgenticWayProfile extends BuiltInJavaQualityProfile { +public class JavaAgenticAIProfile extends BuiltInJavaQualityProfile { static final String PROFILE_NAME = "Sonar agentic AI"; - public JavaAgenticWayProfile() { + public JavaAgenticAIProfile() { this(null); } - public JavaAgenticWayProfile(@Nullable ProfileRegistrar[] profileRegistrars) { + public JavaAgenticAIProfile(@Nullable ProfileRegistrar[] profileRegistrars) { super(profileRegistrars); } diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaPlugin.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaPlugin.java index 7cd9a3788cc..3de169b4f4a 100644 --- a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaPlugin.java +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaPlugin.java @@ -62,7 +62,7 @@ public void define(Context context) { list.addAll(SurefireExtensions.getExtensions()); list.add(DroppedPropertiesSensor.class); list.add(JavaSonarWayProfile.class); - list.add(JavaAgenticWayProfile.class); + list.add(JavaAgenticAIProfile.class); list.add(ClasspathForMain.class); ExternalReportExtensions.define(context); 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 961c082271f..2209bd62e86 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 @@ -42,7 +42,7 @@ class JavaAgenticWayProfileTest { @Test void profile_is_registered_as_expected() { - JavaAgenticWayProfile profile = new JavaAgenticWayProfile(); + JavaAgenticAIProfile profile = new JavaAgenticAIProfile(); BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); profile.define(context); @@ -126,7 +126,7 @@ void generate_ai_quality_profile() throws IOException { Path generatedQualityProfile = generate( Path.of("Path to your CSV input file relative to sonar-java-plugin"), RULE_DESCRIPTION_DIRECTORY.resolve("Sonar_agentic_ai_profile.json"), - JavaAgenticWayProfile.PROFILE_NAME + JavaAgenticAIProfile.PROFILE_NAME ); Assertions.fail(String.format("The generated quality profile was written to %s".formatted(generatedQualityProfile))); }