Skip to content

Commit 5175a49

Browse files
SONARJAVA-6205 Add an agentic focused quality profile for Java (#5531)
1 parent 388e1a6 commit 5175a49

10 files changed

Lines changed: 872 additions & 92 deletions

File tree

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@
280280
<artifactId>commons-lang3</artifactId>
281281
<version>3.20.0</version>
282282
</dependency>
283+
<dependency>
284+
<groupId>org.apache.commons</groupId>
285+
<artifactId>commons-csv</artifactId>
286+
<version>1.14.1</version>
287+
</dependency>
283288
<dependency>
284289
<groupId>org.springframework</groupId>
285290
<artifactId>spring-expression</artifactId>

sonar-java-plugin/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@
137137
<version>${project.version}</version>
138138
<scope>test</scope>
139139
</dependency>
140+
<dependency>
141+
<groupId>org.apache.commons</groupId>
142+
<artifactId>commons-csv</artifactId>
143+
<scope>test</scope>
144+
</dependency>
140145
</dependencies>
141146

142147
<build>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.plugins.java;
18+
19+
import java.lang.reflect.InvocationTargetException;
20+
import java.lang.reflect.Method;
21+
import java.util.HashSet;
22+
import java.util.Set;
23+
import java.util.stream.Collectors;
24+
import javax.annotation.Nullable;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
import org.sonar.api.rule.RuleKey;
28+
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
29+
import org.sonar.java.GeneratedCheckList;
30+
import org.sonar.java.annotations.VisibleForTesting;
31+
import org.sonar.plugins.java.api.ProfileRegistrar;
32+
import org.sonarsource.analyzer.commons.BuiltInQualityProfileJsonLoader;
33+
34+
35+
/**
36+
* Defines a Java quality profile including rules from sonar-java, Java SE, DBD and Security.
37+
*/
38+
abstract class BuiltInJavaQualityProfile implements BuiltInQualityProfilesDefinition {
39+
private static final Logger LOG = LoggerFactory.getLogger(BuiltInJavaQualityProfile.class);
40+
41+
static final String SECURITY_RULES_CLASS_NAME = "com.sonar.plugins.security.api.JavaRules";
42+
static final String DBD_RULES_CLASS_NAME = "com.sonarsource.plugins.dbd.api.JavaRules";
43+
static final String SECURITY_RULE_KEYS_METHOD_NAME = "getSecurityRuleKeys";
44+
static final String GET_REPOSITORY_KEY = "getRepositoryKey";
45+
static final String SECURITY_REPOSITORY_KEY = "javasecurity";
46+
47+
48+
protected final ProfileRegistrar[] profileRegistrars;
49+
50+
BuiltInJavaQualityProfile(@Nullable ProfileRegistrar[] profileRegistrars) {
51+
this.profileRegistrars = profileRegistrars;
52+
}
53+
54+
abstract String getProfileName();
55+
56+
abstract String getPathToJsonProfile();
57+
58+
abstract boolean isDefault();
59+
60+
@Override
61+
public void define(Context context) {
62+
// Create a new profile
63+
BuiltInQualityProfilesDefinition.NewBuiltInQualityProfile profile = context.createBuiltInQualityProfile(getProfileName(), Java.KEY);
64+
// Load rules from local JSON
65+
Set<RuleKey> ruleKeys = registerRulesFromJson(getPathToJsonProfile(), profileRegistrars);
66+
67+
// FIXME as part of SONARJAVA-6207
68+
// Former activation mechanism, it should be removed once sonar-security and sonar-dataflow-bug-detection
69+
// support the new mechanism:
70+
// <code> registrarContext.internal().registerDefaultQualityProfileRules(ruleKeys); </code>
71+
// For now, it still uses reflexion if rules are not yet defined
72+
if (ruleKeys.stream().noneMatch(rule -> SECURITY_REPOSITORY_KEY.equals(rule.repository()))) {
73+
ruleKeys.addAll(getSecurityRuleKeys());
74+
}
75+
76+
ruleKeys.forEach(ruleKey -> profile.activateRule(ruleKey.repository(), ruleKey.rule()));
77+
profile.setDefault(isDefault());
78+
profile.done();
79+
}
80+
81+
static Set<RuleKey> registerRulesFromJson(String pathToJsonProfile, @Nullable ProfileRegistrar[] profileRegistrars) {
82+
Set<RuleKey> ruleKeys = new HashSet<>(loadRuleKeys(pathToJsonProfile));
83+
if (profileRegistrars != null) {
84+
for (ProfileRegistrar profileRegistrar : profileRegistrars) {
85+
profileRegistrar.register(ruleKeys::addAll);
86+
}
87+
}
88+
89+
return ruleKeys;
90+
}
91+
92+
static Set<RuleKey> loadRuleKeys(final String pathToJsonProfile) {
93+
return BuiltInQualityProfileJsonLoader.loadActiveKeysFromJsonProfile(pathToJsonProfile).stream()
94+
.map(rule -> RuleKey.of(GeneratedCheckList.REPOSITORY_KEY, rule))
95+
.collect(Collectors.toSet());
96+
}
97+
98+
@VisibleForTesting
99+
Set<RuleKey> getSecurityRuleKeys() {
100+
return getExternalRuleKeys(SECURITY_RULES_CLASS_NAME, SECURITY_RULE_KEYS_METHOD_NAME, "security");
101+
}
102+
103+
@VisibleForTesting
104+
Set<RuleKey> getExternalRuleKeys(String className, String ruleKeysMethod, String rulesCategory) {
105+
try {
106+
Class<?> javaRulesClass = Class.forName(className);
107+
Method getRuleKeysMethod = javaRulesClass.getMethod(ruleKeysMethod);
108+
Set<String> ruleKeys = (Set<String>) getRuleKeysMethod.invoke(null);
109+
Method getRepositoryKeyMethod = javaRulesClass.getMethod(GET_REPOSITORY_KEY);
110+
String repositoryKey = (String) getRepositoryKeyMethod.invoke(null);
111+
return ruleKeys.stream().map(k -> RuleKey.of(repositoryKey, k)).collect(Collectors.toSet());
112+
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
113+
LOG.debug(String.format("[%s], no %s rules added to %s java profile: %s", e.getClass().getSimpleName(), rulesCategory, getProfileName(), e.getMessage()));
114+
}
115+
return new HashSet<>();
116+
}
117+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.plugins.java;
18+
19+
import javax.annotation.Nullable;
20+
import org.sonar.plugins.java.api.ProfileRegistrar;
21+
import org.sonarsource.api.sonarlint.SonarLintSide;
22+
23+
@SonarLintSide
24+
public class JavaAgenticAIProfile extends BuiltInJavaQualityProfile {
25+
static final String PROFILE_NAME = "Sonar agentic AI";
26+
27+
public JavaAgenticAIProfile() {
28+
this(null);
29+
}
30+
31+
public JavaAgenticAIProfile(@Nullable ProfileRegistrar[] profileRegistrars) {
32+
super(profileRegistrars);
33+
}
34+
35+
@Override
36+
String getProfileName() {
37+
return PROFILE_NAME;
38+
}
39+
40+
@Override
41+
String getPathToJsonProfile() {
42+
return "/org/sonar/l10n/java/rules/java/Sonar_agentic_ai_profile.json";
43+
}
44+
45+
@Override
46+
boolean isDefault() {
47+
return false;
48+
}
49+
}

sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaPlugin.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public void define(Context context) {
6262
list.addAll(SurefireExtensions.getExtensions());
6363
list.add(DroppedPropertiesSensor.class);
6464
list.add(JavaSonarWayProfile.class);
65+
list.add(JavaAgenticAIProfile.class);
6566
list.add(ClasspathForMain.class);
6667

6768
ExternalReportExtensions.define(context);

sonar-java-plugin/src/main/java/org/sonar/plugins/java/JavaSonarWayProfile.java

Lines changed: 13 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,19 @@
1616
*/
1717
package org.sonar.plugins.java;
1818

19-
import java.lang.reflect.InvocationTargetException;
20-
import java.lang.reflect.Method;
21-
import java.util.HashSet;
2219
import java.util.Set;
23-
import java.util.stream.Collectors;
2420
import javax.annotation.Nullable;
25-
import org.slf4j.Logger;
26-
import org.slf4j.LoggerFactory;
2721
import org.sonar.api.rule.RuleKey;
28-
import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
29-
import org.sonar.java.GeneratedCheckList;
30-
import org.sonar.java.annotations.VisibleForTesting;
3122
import org.sonar.plugins.java.api.ProfileRegistrar;
32-
import org.sonarsource.analyzer.commons.BuiltInQualityProfileJsonLoader;
3323
import org.sonarsource.api.sonarlint.SonarLintSide;
3424

3525
/**
36-
* define built-in profile
26+
* Define the default Sonar way profile.
3727
*/
3828
@SonarLintSide
39-
public class JavaSonarWayProfile implements BuiltInQualityProfilesDefinition {
40-
41-
private static final Logger LOG = LoggerFactory.getLogger(JavaSonarWayProfile.class);
42-
43-
static final String SECURITY_RULES_CLASS_NAME = "com.sonar.plugins.security.api.JavaRules";
44-
static final String DBD_RULES_CLASS_NAME = "com.sonarsource.plugins.dbd.api.JavaRules";
45-
static final String SECURITY_RULE_KEYS_METHOD_NAME = "getSecurityRuleKeys";
46-
static final String DBD_RULE_KEYS_METHOD_NAME = "getDataflowBugDetectionRuleKeys";
47-
static final String GET_REPOSITORY_KEY = "getRepositoryKey";
48-
static final String SECURITY_REPOSITORY_KEY = "javasecurity";
49-
static final String DBD_REPOSITORY_KEY = "javabugs";
50-
29+
public class JavaSonarWayProfile extends BuiltInJavaQualityProfile {
5130
static final String SONAR_WAY_PATH = "/org/sonar/l10n/java/rules/java/Sonar_way_profile.json";
5231

53-
private final ProfileRegistrar[] profileRegistrars;
54-
5532
/**
5633
* Constructor used by Pico container (SC) when no ProfileRegistrar are available
5734
*/
@@ -60,63 +37,25 @@ public JavaSonarWayProfile() {
6037
}
6138

6239
public JavaSonarWayProfile(@Nullable ProfileRegistrar[] profileRegistrars) {
63-
this.profileRegistrars = profileRegistrars;
40+
super(profileRegistrars);
6441
}
6542

6643
@Override
67-
public void define(Context context) {
68-
NewBuiltInQualityProfile sonarWay = context.createBuiltInQualityProfile("Sonar way", Java.KEY);
69-
Set<RuleKey> ruleKeys = new HashSet<>(sonarJavaSonarWayRuleKeys());
70-
if (profileRegistrars != null) {
71-
for (ProfileRegistrar profileRegistrar : profileRegistrars) {
72-
profileRegistrar.register(ruleKeys::addAll);
73-
}
74-
}
75-
76-
// Former activation mechanism, it should be removed once sonar-security and sonar-dataflow-bug-detection
77-
// support the new mechanism:
78-
// <code> registrarContext.internal().registerDefaultQualityProfileRules(ruleKeys); </code>
79-
// For now, it still uses reflexion if rules are not yet defined
80-
if (ruleKeys.stream().noneMatch(rule -> SECURITY_REPOSITORY_KEY.equals(rule.repository()))) {
81-
ruleKeys.addAll(getSecurityRuleKeys());
82-
}
83-
if (ruleKeys.stream().noneMatch(rule -> DBD_REPOSITORY_KEY.equals(rule.repository()))) {
84-
ruleKeys.addAll(getDataflowBugDetectionRuleKeys());
85-
}
86-
87-
ruleKeys.forEach(ruleKey -> sonarWay.activateRule(ruleKey.repository(), ruleKey.rule()));
88-
sonarWay.done();
44+
String getProfileName() {
45+
return "Sonar way";
8946
}
9047

91-
static Set<RuleKey> sonarJavaSonarWayRuleKeys() {
92-
return BuiltInQualityProfileJsonLoader.loadActiveKeysFromJsonProfile(SONAR_WAY_PATH).stream()
93-
.map(rule -> RuleKey.of(GeneratedCheckList.REPOSITORY_KEY, rule))
94-
.collect(Collectors.toSet());
95-
}
96-
97-
@VisibleForTesting
98-
static Set<RuleKey> getSecurityRuleKeys() {
99-
return getExternalRuleKeys(SECURITY_RULES_CLASS_NAME, SECURITY_RULE_KEYS_METHOD_NAME, "security");
48+
@Override
49+
String getPathToJsonProfile() {
50+
return SONAR_WAY_PATH;
10051
}
10152

102-
@VisibleForTesting
103-
static Set<RuleKey> getDataflowBugDetectionRuleKeys() {
104-
return getExternalRuleKeys(DBD_RULES_CLASS_NAME, DBD_RULE_KEYS_METHOD_NAME, "dataflow bug detection");
53+
@Override
54+
boolean isDefault() {
55+
return true;
10556
}
10657

107-
@SuppressWarnings("unchecked")
108-
@VisibleForTesting
109-
static Set<RuleKey> getExternalRuleKeys(String className, String ruleKeysMethod, String rulesCategory) {
110-
try {
111-
Class<?> javaRulesClass = Class.forName(className);
112-
Method getRuleKeysMethod = javaRulesClass.getMethod(ruleKeysMethod);
113-
Set<String> ruleKeys = (Set<String>) getRuleKeysMethod.invoke(null);
114-
Method getRepositoryKeyMethod = javaRulesClass.getMethod(GET_REPOSITORY_KEY);
115-
String repositoryKey = (String) getRepositoryKeyMethod.invoke(null);
116-
return ruleKeys.stream().map(k -> RuleKey.of(repositoryKey, k)).collect(Collectors.toSet());
117-
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
118-
LOG.debug(String.format("[%s], no %s rules added to Sonar way java profile: %s", e.getClass().getSimpleName(), rulesCategory, e.getMessage()));
119-
}
120-
return new HashSet<>();
58+
static Set<RuleKey> sonarJavaSonarWayRuleKeys() {
59+
return loadRuleKeys(SONAR_WAY_PATH);
12160
}
12261
}

0 commit comments

Comments
 (0)