Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5f10608
added PMD Apex rules - first try
stokpop Jul 3, 2025
28dd59d
added PMD Apex rules - attempt to activate scanning of apex files
stokpop Jul 4, 2025
2b90b9c
Merge branch 'master' into master-issue361-apex
stokpop Jul 15, 2025
e688291
integration-test: add Kotlin IT tests
stokpop Jul 15, 2025
aef9179
Merge branch 'master' into master-issue361-apex
stokpop Jul 20, 2025
52574ac
Merge branch 'master-activate-kotlin-sensor' into master-issue361-apex
stokpop Jul 20, 2025
fd0c9db
fix build after merge
stokpop Jul 20, 2025
6d5122a
Merge branch 'master-activate-kotlin-sensor' into master-issue361-apex
stokpop Jul 20, 2025
70c0b17
added Apex executor
stokpop Jul 20, 2025
7e34249
added Apex Sensor detection
stokpop Jul 20, 2025
3440363
fix apex integration tests
stokpop Jul 20, 2025
7362b62
language is named "Apex" instead of "PMD Apex"
stokpop Jul 20, 2025
d14f096
small jar size is ok!
stokpop Jul 20, 2025
716fe25
update generated rules-apex.xml
stokpop Jul 21, 2025
c8300f2
fix unit tests, minus one
stokpop Sep 4, 2025
1b89825
fix integration tests - step 1
stokpop Sep 7, 2025
914f738
fix integration tests - all green
stokpop Sep 7, 2025
99efc05
refactor to consolidate similar code - part 1
stokpop Sep 7, 2025
35df2df
Merge branch 'master' into activate-kotlin-sensor-plus-issue361-apex
stokpop Sep 29, 2025
21c027b
fix IT test, increase allowed size to contain pmd-apex in the plugin
stokpop Sep 29, 2025
e3484e4
Merge branch 'master' into activate-kotlin-sensor-plus-issue361-apex
stokpop Oct 10, 2025
3525851
fix after main merge, re-generated rules-apex.xml for pmd 7.17.0 plus…
stokpop Oct 10, 2025
e1c64b7
update junie guidelines
stokpop Oct 10, 2025
949f6a4
extract common code from all PmdExecutors
stokpop Oct 10, 2025
751e3db
cleanup duplicate code in integration test
stokpop Oct 11, 2025
e091a5c
added missing file PmdConstants in sonar-pmd-lib
stokpop Oct 11, 2025
4fddfe8
step 1 to separate apex plugin
stokpop Oct 11, 2025
fede9db
step 2 to separate apex plugin
stokpop Oct 11, 2025
a0b944b
step 3 to separate apex plugin
stokpop Oct 11, 2025
59acf17
step 4 to separate apex plugin
stokpop Oct 11, 2025
00be91a
step 5 to separate apex plugin
stokpop Oct 11, 2025
c08a5de
step 6 to separate apex plugin
stokpop Oct 11, 2025
e6fa578
build: add publish apex plugin
stokpop Oct 11, 2025
6c926a1
add integration test for apex rules
stokpop Oct 12, 2025
ab6c5ee
remove duplicate code
stokpop Oct 12, 2025
c78e241
fix Sonar Cloud violation in Apex test case
stokpop Oct 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ jobs:
name: sonar-pmd-plugin-${{ env.TAG_NAME }}${{ env.ARTIFACT_SUFFIX }}
path: sonar-pmd-plugin/target/sonar-pmd-plugin-*.jar

- name: Upload sonar-pmd-apex-plugin jar
uses: actions/upload-artifact@v4
with:
name: sonar-pmd-apex-plugin-${{ env.TAG_NAME }}${{ env.ARTIFACT_SUFFIX }}
path: sonar-pmd-apex-plugin/target/sonar-pmd-apex-plugin-*.jar

- name: Upload sonar-pmd-lib jar
uses: actions/upload-artifact@v4
with:
Expand Down
71 changes: 71 additions & 0 deletions .junie/guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Sonar-PMD Plugin Guidelines

## Project Overview
Sonar-PMD is a SonarQube plugin that integrates PMD (a static code analyzer) into SonarQube. It provides coding rules from PMD for use in SonarQube, allowing users to detect code quality issues in their Java, Apex, and Kotlin code.

The project is currently maintained by Jeroen Borgers and Peter Paul Bakker, and is sponsored by Rabobank. It was previously maintained by SonarSource and later by Jens Gerdes before being transferred to the current maintainers in 2022.

## Project Structure
The project is organized as a multi-module Maven project with the following modules:

1. **sonar-pmd-lib**: Core library containing the PMD rule definitions and integration logic
2. **sonar-pmd-plugin**: The actual SonarQube plugin that gets packaged and deployed
3. **integration-test**: Integration tests for the plugin

Key directories:
- `/sonar-pmd-lib/src/main/java`: Core implementation classes
- `/sonar-pmd-plugin/src/main/java`: Plugin-specific implementation
- `/integration-test/src/test/java`: Integration tests

## Build Requirements
- Java 17 is required to build the plugin
- Maven 3.8+ is required

## Testing Guidelines
When making changes to the plugin, Junie should:

1. **Run unit tests** to verify that the changes don't break existing functionality:
```
./mvnw clean test
```

2. **Run integration tests** for more comprehensive testing:
```
./mvnw clean verify
```

3. **Test with different SonarQube versions** if making changes that might affect compatibility. The plugin currently supports SonarQube 9.9.4 and above.

## Code Style
The project follows standard Java code style conventions. When making changes:

1. Keep code clean and readable
2. Add appropriate JavaDoc comments for public classes and methods
3. Follow existing patterns in the codebase
4. Ensure backward compatibility when possible

## Version Compatibility
The plugin has specific version compatibility requirements:
- PMD version: 7.17.0
- Java source compatibility: 8 to 25 (including 25-preview)
- SonarQube compatibility: 9.9.4 and above

## Release Process
The project uses semantic versioning:
- Major version changes indicate breaking changes (e.g., PMD 6 to PMD 7)
- Minor version changes indicate new features
- Patch version changes indicate bug fixes

## Important Notes
1. A number of the PMD rules have been rewritten in the default Sonar Java plugin. For known alternatives, the `has-sonar-alternative` tag is added with references to these alternative(s).
2. The plugin is licensed under the GNU Lesser General Public License, Version 3.0.
3. Parts of the rule descriptions displayed in SonarQube have been extracted from PMD and are licensed under a BSD-style license.

## Generate new rules files
Use maven command:

./mvnw generate-resources -Pgenerate-pmd-rules -pl sonar-pmd-plugin

## Integration tests
The integration tests are run using the SonarQube Docker image.
Do not run the integration tests during Junie sessions unless it is the very last step to verify the plugins.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,19 @@ Sonar-PMD is licensed under the [GNU Lesser General Public License, Version 3.0]
Parts of the rule descriptions displayed in SonarQube have been extracted from [PMD](https://pmd.github.io/) and are licensed under a [BSD-style license](https://github.com/pmd/pmd/blob/master/LICENSE).

## Build and test the plugin
To build the plugin and run the integration tests (use java 17 to build the plugin):
To build the plugin and run the integration tests (use Java 17 to build the plugin):

./mvnw clean verify

## Generate PMD rules XML (Java and Kotlin)
To regenerate the `rules-java.xml` and `rules-kotlin.xml` from PMD 7 using the provided Groovy script, run from the project root:
### Failing Integration Tests

If you experience issues starting the integration tests, make sure there is enough free space
on your disk. Otherwise, ElasticSearch used by SonarQube might not be able to start due to bad
system health status (high disk watermark [90%] exceeded).

## Generate PMD rules XML (Java, Kotlin and Apex)
To regenerate the `rules-java.xml`, `rules-kotlin.xml` and `rules-apex.xml` from PMD 7
using the provided Groovy script, run from the project root:

./mvnw clean generate-resources -Pgenerate-pmd-rules -pl sonar-pmd-plugin -am

Expand Down
8 changes: 8 additions & 0 deletions integration-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@
<dependency>
<groupId>org.sonarsource.orchestrator</groupId>
<artifactId>sonar-orchestrator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.sonarsource.orchestrator</groupId>
<artifactId>sonar-orchestrator-junit4</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
Expand All @@ -66,6 +68,12 @@
<artifactId>sonar-pmd-lib</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.sonarsource.java</groupId>
<artifactId>java-frontend</artifactId>
<version>${sonar-java.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
Expand Down
38 changes: 38 additions & 0 deletions integration-test/projects/pmd-apex-rules/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.sonarsource.it.projects</groupId>
<artifactId>pmd-apex-rules</artifactId>
<version>1.0-SNAPSHOT</version>

<name>Integration test Apex project</name>
<description>Simple Apex project for PMD Apex sanity test</description>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>5.2.0.4988</version>
</plugin>
</plugins>
</pluginManagement>
</build>

<profiles>
<profile>
<id>skipSonar</id>
<activation>
<property>
<name>skipTestProjects</name>
<value>true</value>
</property>
</activation>
<properties>
<sonar.skip>true</sonar.skip>
</properties>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Violates the rule: Uses a Boolean parameter
public class BooleanViolation {
public static void doSomething(Boolean isSomething) {
if (isSomething == true) {
isSomething = false;
} else {
isSomething = null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
public class WeakCrypto {
public WeakCrypto() {
Blob exampleIv = Blob.valueOf('0000000000000000');
Blob key = Crypto.generateAesKey(128);
Blob data = Blob.valueOf('Data to be encrypted');
Blob encrypted = Crypto.encrypt('AES128', key, exampleIv, data);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>5.1.0.4751</version>
<version>5.2.0.4988</version>
</plugin>
</plugins>
</pluginManagement>
Expand Down
2 changes: 1 addition & 1 deletion integration-test/projects/pmd-extensions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>5.1.0.4751</version>
<version>5.2.0.4988</version>
</plugin>
</plugins>
</pluginManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@


import net.sourceforge.pmd.lang.java.ast.ASTClassBody;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.properties.NumericConstraints;
Expand All @@ -48,22 +49,24 @@ public MaximumMethodsCountCheck() {
definePropertyDescriptor(propertyDescriptor);
}

@Override
public void start(RuleContext ctx) {
LOG.info("Start " + getName());
}

@Override
public void end(RuleContext ctx) {
LOG.info("End " + getName());
}
// @Override
// public void start(RuleContext ctx) {
// LOG.info("Start {}", getName());
// }
//
// @Override
// public void end(RuleContext ctx) {
// LOG.info("End {}", getName());
// }

@Override
public Object visit(ASTClassBody node, Object data) {
List<ASTMethodDeclaration> methods = node.descendants(ASTMethodDeclaration.class).toList();
public Object visit(ASTCompilationUnit cUnit, Object data) {
LOG.info("Start {}", getName());
List<ASTMethodDeclaration> methods = cUnit.descendants(ASTMethodDeclaration.class).toList();
if (methods.size() > getProperty(propertyDescriptor)) {
asCtx(data).addViolation(node);
asCtx(data).addViolation(cUnit);
}
return super.visit(node, data);
LOG.info("End {}", getName());
return super.visit(cUnit, data);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class PmdExtensionRepository implements RulesDefinition {

private static final Logger LOGGER = LoggerFactory.getLogger(PmdExtensionRepository.class);

// Must be the same than the PMD plugin
// Must be the same as the PMD plugin
private static final String REPOSITORY_KEY = "pmd";
private static final String LANGUAGE_KEY = "java";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,20 @@
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule"
language="java">
<description>
IOException should never be extended. Either use it, or extend Exception for your own business exceptions.
IOException should never be extended. Either use it or extend Exception for your own business exceptions.
</description>
<properties>
<property name="xpath">
<value>
<![CDATA[
//ClassOrInterfaceType[pmd-java:typeIs('java.io.IOException')]
//ClassDeclaration/ExtendsList/ClassType[pmd-java:typeIs('java.io.IOException')]
]]>
</value>
</property>
</properties>
<example>
<![CDATA[
import java.io.IOException;
// don't do this!
class MyOwnIOException extends IOException {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* SonarQube PMD7 Plugin Integration Test - Apex sanity
*/
package com.sonar.it.java.suite;

import com.sonar.it.java.suite.orchestrator.PmdTestOrchestrator;
import com.sonar.orchestrator.build.BuildResult;
import com.sonar.orchestrator.build.MavenBuild;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.sonar.wsclient.issue.Issue;
import org.sonar.wsclient.issue.IssueQuery;

import java.util.List;

import static com.sonar.it.java.suite.TestUtils.keyFor;
import static org.assertj.core.api.Assertions.assertThat;

class PmdApexIT {

private static PmdTestOrchestrator ORCHESTRATOR;

@BeforeAll
static void startSonar() {
ORCHESTRATOR = PmdTestOrchestrator.init();
ORCHESTRATOR.start();
}

@Test
void sanity_apex_project_analyzes_and_reports_issue() {
// given
final String projectName = "pmd-apex-rules";
final MavenBuild build = MavenBuild
.create(TestUtils.projectPom(projectName))
.setCleanSonarGoals()
.setProperty("sonar.sources", "src/main/apex");

ORCHESTRATOR.associateProjectToQualityProfile("pmd-apex-profile", projectName, "apex");

// when
BuildResult result = ORCHESTRATOR.executeBuild(build);

// then
String logs = result.getLogs();
assertThat(logs)
.as("Sonar logs should mention PMD Apex execution")
.contains("PMD Apex");

List<Issue> issues1 = ORCHESTRATOR.retrieveIssues(IssueQuery.create()
.components(keyFor(projectName, "src/main/apex", "", "BooleanViolation", ".cls")));

assertThat(issues1)
.as("Expect at least one Apex issue to be reported for BooleanViolation.cls")
.isNotEmpty();

// Preferably, we find BooleanViolation rule
assertThat(issues1.stream().anyMatch(i -> "pmd-apex:AvoidBooleanMethodParameters".equals(i.ruleKey())))
.as("Expect BooleanViolation to be reported on BooleanViolation.cls")
.isTrue();

List<Issue> issuesCrypto = ORCHESTRATOR.retrieveIssues(IssueQuery.create()
.components(keyFor(projectName, "src/main/apex", "", "WeakCrypto", ".cls")));

assertThat(issuesCrypto)
.as("Expect at least one Apex issue to be reported for WeakCrypto.cls")
.isNotEmpty();

// Preferably, we find ApexBadCrypto rule
assertThat(issuesCrypto.stream().anyMatch(i -> "pmd-apex:ApexBadCrypto".equals(i.ruleKey())))
.as("Expect ApexBadCrypto to be reported on WeakCrypto.cls")
.isTrue();

// cleanup
ORCHESTRATOR.resetData(projectName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ static String keyFor(String projectKey, String srcDir, String pkgDir, String cls
}

private static @NotNull String ensureEndsWithSlash(String srcDir) {
if (!srcDir.isEmpty() && !srcDir.endsWith("/")) {
if (srcDir.isEmpty()) {
return srcDir;
}
if (!srcDir.endsWith("/")) {
srcDir = srcDir + "/";
}
return srcDir;
Expand Down
Loading