Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
62 changes: 62 additions & 0 deletions .junie/guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# 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.15.0
- Java source compatibility: 8 to 24 (including 24-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.
13 changes: 1 addition & 12 deletions integration-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

<artifactId>integration-test</artifactId>
<name>SonarQube PMD Plugin Integration Test</name>
<packaging>sonar-plugin</packaging>
<packaging>jar</packaging>
<inceptionYear>2013</inceptionYear>

<properties>
Expand Down Expand Up @@ -96,17 +96,6 @@
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
<artifactId>sonar-packaging-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<pluginClass>org.sonar.examples.pmd.PmdExtensionPlugin</pluginClass>
<pluginDescription>Integration Test PmdExtensionPlugin</pluginDescription>
<!-- This is important. It means that this plugin extends the PMD plugin -->
<basePlugin>pmd</basePlugin>
</configuration>
</plugin>
<plugin>
<!-- delete the target dirs in the sub projects, not cleaned by default -->
<artifactId>maven-clean-plugin</artifactId>
Expand Down
63 changes: 63 additions & 0 deletions integration-test/projects/pmd-kotlin-rules/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<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-kotlin-rules</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<kotlin.version>1.9.0</kotlin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>

<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>5.1.0.4751</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,19 @@
package com.example

class KotlinErrors {
// This function name is too short, which will trigger the FunctionNameTooShort rule
fun fn() {
println("This function name is too short")
}
}

// This class overrides equals but not hashCode, which will trigger the OverrideBothEqualsAndHashcode rule
class EqualsOnly {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return true
}

// Missing hashCode() override
}
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
@@ -0,0 +1,147 @@
/*
* SonarQube PMD7 Plugin Integration Test
* Copyright (C) 2013-2021 SonarSource SA and others
* mailto:jborgers AT jpinpoint DOT com; peter.paul.bakker AT stokpop DOT nl
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* 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 GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
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 com.sonar.orchestrator.http.HttpException;
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 java.util.stream.Collectors;

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

class PmdKotlinIT {

private static PmdTestOrchestrator ORCHESTRATOR;

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

@Test
void testKotlinRules() {
// given
final String projectName = "pmd-kotlin-rules";
final MavenBuild build = MavenBuild
.create(TestUtils.projectPom(projectName))
.setCleanSonarGoals()
.setProperty("sonar.kotlin.file.suffixes", ".kt")
.setProperty("sonar.sources", "src/main/kotlin")
.setProperty("sonar.java.binaries", "target/classes")
.setProperty("sonar.log.level", "DEBUG")
.setProperty("sonar.verbose", "true");

try {
ORCHESTRATOR.associateProjectToQualityProfile("pmd-kotlin-profile", projectName, "kotlin");

// when
final BuildResult buildResult = ORCHESTRATOR.executeBuild(build);

// then
final String log = buildResult.getLogs();
assertThat(log).contains("Kotlin");

System.out.println("[DEBUG_LOG] Build log: " + log);

final List<Issue> issues = retrieveIssues(keyFor(projectName, "com/example/", "KotlinErrors"));
System.out.println("[DEBUG_LOG] Issues found: " + issues.size());

// Also check for issues on EqualsOnly class specifically
final List<Issue> equalsOnlyIssues = retrieveIssues(keyFor(projectName, "com/example/", "EqualsOnly"));
System.out.println("[DEBUG_LOG] EqualsOnly issues found: " + equalsOnlyIssues.size());

final List<String> messages = issues
.stream()
.map(Issue::message)
.collect(Collectors.toList());

System.out.println("[DEBUG_LOG] Messages: " + messages);

assertThat(issues).isNotEmpty();

assertThat(messages)
.contains(
"Function names should have non-cryptic and clear names.",
"Ensure you override both equals() and hashCode()"
);
} catch (HttpException e) {
System.out.println("Failed to associate Project To Quality Profile: " + e.getMessage() + " body: " + e.getBody());
throw e;
} finally {
// Cleanup
ORCHESTRATOR.resetData(projectName);
}
}

@Test
void pmdKotlinShouldRunWithAllRulesEnabled() {
// given
final String projectName = "pmd-kotlin-rules";
final MavenBuild build = MavenBuild
.create(TestUtils.projectPom(projectName))
.setCleanPackageSonarGoals()
.setProperty("sonar.kotlin.file.suffixes", ".kt")
.setProperty("sonar.sources", "src/main/kotlin")
.setProperty("sonar.java.binaries", "target/classes")
.setProperty("sonar.log.level", "DEBUG")
.setProperty("sonar.verbose", "true");
try {
ORCHESTRATOR.associateProjectToQualityProfile("pmd-kotlin-all-rules", projectName, "kotlin");

// when
final BuildResult buildResult = ORCHESTRATOR.executeBuild(build);

// then
final String log = buildResult.getLogs();
System.out.println("[DEBUG_LOG] Build log: " + log);

final List<Issue> issues = retrieveIssues(keyFor(projectName, "com/example/", "KotlinErrors"));
System.out.println("[DEBUG_LOG] Issues found: " + issues.size());

// Also check for issues on EqualsOnly class specifically
final List<Issue> equalsOnlyIssues = retrieveIssues(keyFor(projectName, "com/example/", "EqualsOnly"));
System.out.println("[DEBUG_LOG] EqualsOnly issues found: " + equalsOnlyIssues.size());

assertThat(issues).isNotEmpty();

} catch (HttpException e) {
System.out.println("Failed to associate Project To Quality Profile: " + e.getMessage() + " body: " + e.getBody());
throw e;
} finally {
// Cleanup
ORCHESTRATOR.resetData(projectName);
}
}

private List<Issue> retrieveIssues(String componentKey) {
final IssueQuery issueQuery = IssueQuery.create();
issueQuery.urlParams().put("componentKeys", componentKey);
return ORCHESTRATOR.retrieveIssues(issueQuery);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
public class PmdTestOrchestrator {

private static final String SONAR_JAVA_PLUGIN_VERSION_KEY = "test.sonar.plugin.version.java";
private static final String SONAR_KOTLIN_PLUGIN_VERSION_KEY = "test.sonar.plugin.version.kotlin";
private static final String SONAR_VERSION_KEY = "test.sonar.version";
private static final String LANGUAGE_KEY = "java";
private static final String CENTRAL_MAVEN = "https://repo1.maven.org/maven2";
Expand Down Expand Up @@ -87,9 +88,13 @@ public List<Issue> retrieveIssues(IssueQuery query) {
}

public void associateProjectToQualityProfile(String profile, String project) {
associateProjectToQualityProfile(profile, project, LANGUAGE_KEY);
}

public void associateProjectToQualityProfile(String profile, String project, String language) {
final String projectKey = deriveProjectKey(project);
delegate.getServer().provisionProject(projectKey, project);
delegate.getServer().associateProjectToQualityProfile(projectKey, LANGUAGE_KEY, profile);
delegate.getServer().associateProjectToQualityProfile(projectKey, language, profile);
}

public static PmdTestOrchestrator init() {
Expand All @@ -102,12 +107,19 @@ public static PmdTestOrchestrator init() {
"sonar-java-plugin",
determineJavaPluginVersion()
))
.addPlugin(MavenLocation.create(
"org.sonarsource.kotlin",
"sonar-kotlin-plugin",
determineKotlinPluginVersion()
))
.addPlugin(byWildcardMavenFilename(new File("../sonar-pmd-plugin/target"), "sonar-pmd-plugin-*.jar"))
.addPlugin(byWildcardMavenFilename(new File("./target"), "integration-test-*.jar"))
.restoreProfileAtStartup(ofClasspath("/com/sonar/it/java/PmdTest/pmd-extensions-profile.xml"))
.restoreProfileAtStartup(ofClasspath("/com/sonar/it/java/PmdTest/pmd-backup.xml"))
.restoreProfileAtStartup(ofClasspath("/com/sonar/it/java/PmdTest/pmd-all-rules.xml"))
.restoreProfileAtStartup(ofClasspath("/com/sonar/it/java/PmdTest/pmd-test-rule.xml"))
.restoreProfileAtStartup(ofClasspath("/com/sonar/it/java/PmdTest/pmd-kotlin-profile.xml"))
.restoreProfileAtStartup(ofClasspath("/com/sonar/it/java/PmdTest/pmd-kotlin-all-rules.xml"))
.restoreProfileAtStartup(ofClasspath("/com/sonar/it/java/PmdTest/pmd-apex-profile.xml"))
.build();

return new PmdTestOrchestrator(orchestrator);
Expand All @@ -126,6 +138,10 @@ private static String determineJavaPluginVersion() {
return System.getProperty(SONAR_JAVA_PLUGIN_VERSION_KEY, "LATEST_RELEASE[8.15]"); // use 8.9 to test with SQ 9.9
}

private static String determineKotlinPluginVersion() {
return System.getProperty(SONAR_KOTLIN_PLUGIN_VERSION_KEY, "LATEST_RELEASE[2.15]");
}

private static String determineSonarqubeVersion() {
return System.getProperty(SONAR_VERSION_KEY, "LATEST_RELEASE[25.6]"); // use SQ 9.9.4 to test with old version
}
Expand Down
Loading