diff --git a/integration-test/pom.xml b/integration-test/pom.xml index 52bb43cf..cdad522a 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -105,6 +105,8 @@ Integration Test PmdExtensionPlugin pmd + java:8.0.0.0,kotlin:2.0.0.0 + java,kotlin diff --git a/integration-test/projects/pmd-kotlin-rules/pom.xml b/integration-test/projects/pmd-kotlin-rules/pom.xml new file mode 100644 index 00000000..81e8e9a2 --- /dev/null +++ b/integration-test/projects/pmd-kotlin-rules/pom.xml @@ -0,0 +1,63 @@ + + 4.0.0 + + com.sonarsource.it.projects + pmd-kotlin-rules + 1.0-SNAPSHOT + + 1.9.0 + UTF-8 + + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + + + ${project.basedir}/src/main/kotlin + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + + compile + + + + + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 5.2.0.4988 + + + + + + + + skipSonar + + + skipTestProjects + true + + + + true + + + + diff --git a/integration-test/projects/pmd-kotlin-rules/src/main/kotlin/com/example/KotlinErrors.kt b/integration-test/projects/pmd-kotlin-rules/src/main/kotlin/com/example/KotlinErrors.kt new file mode 100644 index 00000000..5d8881cd --- /dev/null +++ b/integration-test/projects/pmd-kotlin-rules/src/main/kotlin/com/example/KotlinErrors.kt @@ -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 +} \ No newline at end of file diff --git a/integration-test/src/test/java/com/sonar/it/java/suite/PmdIT.java b/integration-test/src/test/java/com/sonar/it/java/suite/PmdIT.java index 4fe12f6f..6405efcb 100644 --- a/integration-test/src/test/java/com/sonar/it/java/suite/PmdIT.java +++ b/integration-test/src/test/java/com/sonar/it/java/suite/PmdIT.java @@ -71,7 +71,7 @@ void testPmdExtensionsWithDifferentJavaVersions(DefinedJavaVersion version) { .contains("Start MaximumMethodsCountCheck") .contains("End MaximumMethodsCountCheck"); - final List issues = retrieveIssues(keyFor(projectName, "pmd/", "Errors")); + final List issues = retrieveIssues(keyFor(projectName, "src/main/java", "pmd", "Errors", ".java")); final List messages = issues .stream() @@ -116,13 +116,15 @@ void testJunitRules() { ORCHESTRATOR.executeBuild(build); // then - String testComponentKey = keyFor("pmd-junit-rules", "src/test/java/", "", "ProductionCodeTest" + ".java"); + // (component -> com.sonarsource.it.projects:pmd-junit-rules:src/test/java/ProductionCodeTest.java) + String testComponentKey = keyFor("pmd-junit-rules", "src/test/java", "", "ProductionCodeTest", ".java"); final List testIssues = retrieveIssues(testComponentKey); assertThat(testIssues).hasSize(1); assertThat(testIssues.get(0).message()).isEqualTo("Unit tests should not contain more than 1 assert(s)."); assertThat(testIssues.get(0).ruleKey()).isEqualTo("pmd:UnitTestContainsTooManyAsserts"); - final List prodIssues = retrieveIssues(keyFor(projectName, "", "ProductionCode")); + // component -> com.sonarsource.it.projects:pmd-junit-rules:src/main/java/ProductionCode.java + final List prodIssues = retrieveIssues(keyFor(projectName, "src/main/java", "", "ProductionCode", ".java")); assertThat(prodIssues).hasSize(1); assertThat(prodIssues.get(0).message()).contains("Avoid unused private fields such as 'unused'."); assertThat(prodIssues.get(0).ruleKey()).isEqualTo("pmd:UnusedPrivateField"); @@ -150,11 +152,11 @@ void testRuleAvoidDuplicateLiterals() { ORCHESTRATOR.executeBuild(build); // then + String avoidDuplicateLiteralsKey = keyFor(projectName, "src/main/java", "", "AvoidDuplicateLiterals", ".java"); final List issues = ORCHESTRATOR.retrieveIssues( IssueQuery.create() .rules("pmd:AvoidDuplicateLiterals") - .components(keyFor(projectName, "", "AvoidDuplicateLiterals") - ) + .components(avoidDuplicateLiteralsKey) ); assertThat(issues) @@ -191,9 +193,10 @@ void pmdShouldHaveAccessToExternalLibrariesInItsClasspath() { // then // PMD7-MIGRATION: added to force one violation in pmdShouldHaveAccessToExternalLibrariesInItsClasspath: is this testing the correct thing? - final List issues = retrieveIssues(keyFor(projectName, "pmd/", "Bar")); + final List issues = retrieveIssues(keyFor(projectName, "src/main/java/", "pmd/", "Errors", ".java")); + assertThat(issues) - .hasSize(1); + .hasSize(3); } catch (HttpException e) { System.out.println("Failed to associate Project To Quality Profile: " + e.getMessage() + " body: " + e.getBody()); @@ -219,7 +222,8 @@ void pmdShouldRunWithAllRulesEnabled() { ORCHESTRATOR.executeBuild(build); // then - final List issues = retrieveIssues(keyFor(projectName, "pmd/", "Bar")); + final List issues = retrieveIssues(keyFor(projectName, "src/main/java", "pmd", "Bar", ".java")); + assertThat(issues) .isNotEmpty(); diff --git a/integration-test/src/test/java/com/sonar/it/java/suite/PmdKotlinIT.java b/integration-test/src/test/java/com/sonar/it/java/suite/PmdKotlinIT.java new file mode 100644 index 00000000..0ce6f891 --- /dev/null +++ b/integration-test/src/test/java/com/sonar/it/java/suite/PmdKotlinIT.java @@ -0,0 +1,128 @@ +/* + * 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 String suffix = ".kt"; + final String srcDir = "src/main/kotlin"; + + final MavenBuild build = MavenBuild + .create(TestUtils.projectPom(projectName)) + .setCleanSonarGoals(); + + 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"); + + final List issues = retrieveIssues(keyFor(projectName, srcDir, "com/example", "KotlinErrors", suffix)); + + final List messages = issues + .stream() + .map(Issue::message) + .collect(Collectors.toList()); + + assertThat(issues).hasSize(2); + + 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(); + try { + orchestrator.associateProjectToQualityProfile("pmd-kotlin-all-rules", projectName, "kotlin"); + + // when + final BuildResult buildResult = orchestrator.executeBuild(build); + + // then + final String log = buildResult.getLogs(); + assertThat(log).contains("Kotlin"); + + final List issues = retrieveIssues(keyFor(projectName, "src/main/kotlin", "com/example", "KotlinErrors", ".kt")); + + assertThat(issues).hasSize(2); + Issue functionNameTooShort = issues.stream().filter(i -> i.ruleKey().equals("pmd-kotlin:FunctionNameTooShort")).findFirst().orElseThrow(); + assertThat(functionNameTooShort.severity()).isEqualTo("MAJOR"); + + } catch (HttpException e) { + System.err.println("Failed to associate Project To Quality Profile: " + e.getMessage() + " body: " + e.getBody()); + throw e; + } finally { + // Cleanup + orchestrator.resetData(projectName); + } + } + + private List retrieveIssues(String componentKey) { + final IssueQuery issueQuery = IssueQuery.create(); + issueQuery.urlParams().put("componentKeys", componentKey); + return orchestrator.retrieveIssues(issueQuery); + } +} diff --git a/integration-test/src/test/java/com/sonar/it/java/suite/SanitySonarVersionsIT.java b/integration-test/src/test/java/com/sonar/it/java/suite/SanitySonarVersionsIT.java new file mode 100644 index 00000000..d9f0f4b8 --- /dev/null +++ b/integration-test/src/test/java/com/sonar/it/java/suite/SanitySonarVersionsIT.java @@ -0,0 +1,74 @@ +/* + * SonarQube PMD7 Plugin Integration Test + */ +package com.sonar.it.java.suite; + +import com.sonar.it.java.suite.orchestrator.PmdTestOrchestrator; +import com.sonar.orchestrator.build.MavenBuild; +import com.sonar.orchestrator.build.BuildResult; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Sanity check that our integration test suite can start and run against both + * the lowest and highest supported SonarQube versions. + */ +class SanitySonarVersionsIT { + + private static final String SONAR_VERSION_KEY = "test.sonar.version"; + + @ParameterizedTest(name = "sanity on SonarQube {0}") + @ValueSource(strings = { + // Lowest supported SonarQube LTS line + "LATEST_RELEASE[9.9]", + // Highest supported SonarQube current line (see README table) + "LATEST_RELEASE[25.6]" + }) + void sanity_runs_on_lowest_and_highest_supported_versions(String sonarqubeVersion) { + final String previous = System.getProperty(SONAR_VERSION_KEY); + System.setProperty(SONAR_VERSION_KEY, sonarqubeVersion); + + PmdTestOrchestrator orchestrator = null; + try { + orchestrator = PmdTestOrchestrator.init(); + orchestrator.start(); + + final String projectName = "pmd-extensions"; + final MavenBuild build = MavenBuild + .create(TestUtils.projectPom(projectName)) + .setCleanSonarGoals() + // keep analysis minimal for sanity run + .setProperty("sonar.java.binaries", "."); + + orchestrator.associateProjectToQualityProfile("pmd-extensions-profile", projectName); + final BuildResult result = orchestrator.executeBuild(build); // will throw if analysis fails + assertThat(result.getLogs()).contains("[INFO] Sensor PmdSensor [pmd]"); + + // Additionally run a minimal Kotlin project analysis to ensure Kotlin support works + final String kotlinProject = "pmd-kotlin-rules"; + final MavenBuild kotlinBuild = MavenBuild + .create(TestUtils.projectPom(kotlinProject)) + .setCleanSonarGoals(); + orchestrator.associateProjectToQualityProfile("pmd-kotlin-profile", kotlinProject, "kotlin"); + final BuildResult kotlinResult = orchestrator.executeBuild(kotlinBuild); + assertThat(kotlinResult.getLogs()).contains("[INFO] Sensor PmdSensor [pmd]"); + } + finally { + // restore previous property to not affect other tests + if (previous != null) { + System.setProperty(SONAR_VERSION_KEY, previous); + } else { + System.clearProperty(SONAR_VERSION_KEY); + } + if (orchestrator != null) { + try { + orchestrator.stop(); + } catch (Throwable ignored) { + // ignore + } + } + } + } +} diff --git a/integration-test/src/test/java/com/sonar/it/java/suite/TestUtils.java b/integration-test/src/test/java/com/sonar/it/java/suite/TestUtils.java index 1928f415..a003069e 100644 --- a/integration-test/src/test/java/com/sonar/it/java/suite/TestUtils.java +++ b/integration-test/src/test/java/com/sonar/it/java/suite/TestUtils.java @@ -22,6 +22,7 @@ import java.io.File; import org.apache.commons.io.FileUtils; +import org.jetbrains.annotations.NotNull; class TestUtils { @@ -40,12 +41,28 @@ static File projectPom(String projectName) { return new File(HOME, "projects/" + projectName + "/pom.xml"); } + static String keyFor(String projectKey) { + return "com.sonarsource.it.projects:" + projectKey; + } + static String keyFor(String projectKey, String srcDir, String pkgDir, String cls) { - return "com.sonarsource.it.projects:" + projectKey + ":" + srcDir + pkgDir + cls; + srcDir = ensureEndsWithSlash(srcDir); + pkgDir = ensureEndsWithSlash(pkgDir); + return keyFor(projectKey) + ":" + srcDir + pkgDir + cls; + } + + private static @NotNull String ensureEndsWithSlash(String srcDir) { + if (!srcDir.isEmpty() && !srcDir.endsWith("/")) { + srcDir = srcDir + "/"; + } + return srcDir; } - static String keyFor(String projectKey, String pkgDir, String cls) { - return keyFor(projectKey, "src/main/java/", pkgDir, cls + ".java"); + static String keyFor(String projectKey, String srcDir, String pkgDir, String cls, String suffix) { + if (!suffix.isEmpty() && !suffix.startsWith(".")) { + suffix = "." + suffix; + } + return keyFor(projectKey, srcDir, pkgDir, cls + suffix); } } diff --git a/integration-test/src/test/java/com/sonar/it/java/suite/orchestrator/PmdTestOrchestrator.java b/integration-test/src/test/java/com/sonar/it/java/suite/orchestrator/PmdTestOrchestrator.java index 2b6f5673..a46d7ff6 100644 --- a/integration-test/src/test/java/com/sonar/it/java/suite/orchestrator/PmdTestOrchestrator.java +++ b/integration-test/src/test/java/com/sonar/it/java/suite/orchestrator/PmdTestOrchestrator.java @@ -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"; @@ -75,7 +76,23 @@ public void start() { delegate.start(); } + public void stop() { + try { + // OrchestratorRule typically exposes stop(); if not, close() will be called by JUnit rule, but here we drive it manually. + delegate.stop(); + } catch (Throwable t) { + // ignore stop failures to avoid masking test results + System.out.println("WARN: Failed to stop orchestrator cleanly: " + t.getMessage()); + } + } + public BuildResult executeBuild(MavenBuild build) { + // use this to enable debug: build.setDebugLogs(true) + // avoid this: [DEBUG] Plugins not loaded because they are optional: [java, pmd] + // and the following: Cannot invoke "org.sonar.core.platform.ExplodedPlugin.getPluginInfo()" because the return value of "java.util.Map.get(Object)" is null + // sonar.plugins.downloadOnlyRequired turned to default "true" in SonarQube 10.5": https://sonarsource.atlassian.net/browse/SONAR-22074 + // update: fixed by specifying required plugins in the plugin manifests! + // use this to override: build.setProperty("sonar.plugins.downloadOnlyRequired", "false") return delegate.executeBuild(build); } @@ -87,27 +104,39 @@ public List 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() { try { final OrchestratorRule orchestrator = OrchestratorRule - .builderEnv().useDefaultAdminCredentialsForBuilds(true) + .builderEnv() + .useDefaultAdminCredentialsForBuilds(true) .setSonarVersion(determineSonarqubeVersion()) .addPlugin(MavenLocation.create( "org.sonarsource.java", "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")) .build(); return new PmdTestOrchestrator(orchestrator); @@ -123,10 +152,14 @@ private static String deriveProjectKey(String projectName) { } 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 + return System.getProperty(SONAR_JAVA_PLUGIN_VERSION_KEY, "LATEST_RELEASE[8.9]"); // use 8.9 to test with SQ 9.9 + } + + private static String determineKotlinPluginVersion() { + return System.getProperty(SONAR_KOTLIN_PLUGIN_VERSION_KEY, "LATEST_RELEASE[2.23]"); } private static String determineSonarqubeVersion() { - return System.getProperty(SONAR_VERSION_KEY, "LATEST_RELEASE[25.6]"); // use SQ 9.9.4 to test with old version + return System.getProperty(SONAR_VERSION_KEY, "LATEST_RELEASE[25.3]"); // use SQ 9.9.4 to test with old version } } diff --git a/integration-test/src/test/resources/com/sonar/it/java/PmdTest/pmd-kotlin-all-rules.xml b/integration-test/src/test/resources/com/sonar/it/java/PmdTest/pmd-kotlin-all-rules.xml new file mode 100644 index 00000000..ea1391c6 --- /dev/null +++ b/integration-test/src/test/resources/com/sonar/it/java/PmdTest/pmd-kotlin-all-rules.xml @@ -0,0 +1,17 @@ + + + pmd-kotlin-all-rules + kotlin + + + pmd-kotlin + FunctionNameTooShort + MAJOR + + + pmd-kotlin + OverrideBothEqualsAndHashcode + MAJOR + + + diff --git a/integration-test/src/test/resources/com/sonar/it/java/PmdTest/pmd-kotlin-profile.xml b/integration-test/src/test/resources/com/sonar/it/java/PmdTest/pmd-kotlin-profile.xml new file mode 100644 index 00000000..c50b5369 --- /dev/null +++ b/integration-test/src/test/resources/com/sonar/it/java/PmdTest/pmd-kotlin-profile.xml @@ -0,0 +1,17 @@ + + + pmd-kotlin-profile + kotlin + + + pmd-kotlin + FunctionNameTooShort + CRITICAL + + + pmd-kotlin + OverrideBothEqualsAndHashcode + MAJOR + + + diff --git a/pom.xml b/pom.xml index 9bfb0c81..c28d85e8 100644 --- a/pom.xml +++ b/pom.xml @@ -74,15 +74,14 @@ 3.27.6 3.18.0 2.0.1 - 8.18.0.40025 33.5.0-jre - 2.7.1.392 2.0.6.1 1.23.0.740 - 25.9.0.112764 - 13.0.0.3026 - 8.17.1.39878 + 25.6.0.109173 + 13.2.0.3137 + 8.18.0.40025 + 2.7.1.392 5.6.2.2625 5.1 diff --git a/sonar-pmd-plugin/pom.xml b/sonar-pmd-plugin/pom.xml index 21585246..6d684ec6 100644 --- a/sonar-pmd-plugin/pom.xml +++ b/sonar-pmd-plugin/pom.xml @@ -183,6 +183,7 @@ org.sonar.plugins.pmd.PmdPlugin Analyze Java and Kotlin code with PMD. java,kotlin + java:8.0.0.0,kotlin:2.0.0.0 diff --git a/sonar-pmd-plugin/src/main/java/org/sonar/plugins/pmd/PmdConstants.java b/sonar-pmd-plugin/src/main/java/org/sonar/plugins/pmd/PmdConstants.java index d8bb5323..56a5c1a4 100644 --- a/sonar-pmd-plugin/src/main/java/org/sonar/plugins/pmd/PmdConstants.java +++ b/sonar-pmd-plugin/src/main/java/org/sonar/plugins/pmd/PmdConstants.java @@ -29,6 +29,7 @@ public final class PmdConstants { public static final String MAIN_KOTLIN_REPOSITORY_KEY = "pmd-kotlin"; public static final String REPOSITORY_NAME = "PMD"; public static final String REPOSITORY_KOTLIN_NAME = "PMD Kotlin"; + public static final String XPATH_CLASS = "net.sourceforge.pmd.lang.rule.xpath.XPathRule"; public static final String XPATH_EXPRESSION_PARAM = "xpath"; public static final String XPATH_MESSAGE_PARAM = "message"; diff --git a/sonar-pmd-plugin/src/main/java/org/sonar/plugins/pmd/PmdPlugin.java b/sonar-pmd-plugin/src/main/java/org/sonar/plugins/pmd/PmdPlugin.java index 0936dcc1..9c516dfb 100644 --- a/sonar-pmd-plugin/src/main/java/org/sonar/plugins/pmd/PmdPlugin.java +++ b/sonar-pmd-plugin/src/main/java/org/sonar/plugins/pmd/PmdPlugin.java @@ -37,7 +37,6 @@ public void define(Context context) { .name("Generate XML Report") .hidden() .build(), - PmdSensor.class, PmdConfiguration.class, PmdJavaExecutor.class,