diff --git a/packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/main/java/com/salesforce/sfca/pmdwrapper/PmdRuleDescriber.java b/packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/main/java/com/salesforce/sfca/pmdwrapper/PmdRuleDescriber.java
index 4c14f02d..10337e6c 100644
--- a/packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/main/java/com/salesforce/sfca/pmdwrapper/PmdRuleDescriber.java
+++ b/packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/main/java/com/salesforce/sfca/pmdwrapper/PmdRuleDescriber.java
@@ -170,7 +170,11 @@ public void logEx(Level level, @Nullable String s, Object[] objects, @Nullable T
throw new RuntimeException("PMD threw an unexpected exception:\n" + message, throwable);
} else if (level == Level.WARN && s != null){
String message = MessageFormat.format(s, objects);
- System.out.println("[Warning] " + message.replaceAll("\n","{NEWLINE}"));
+ if (message.contains("Discontinue using Rule ")) {
+ System.out.println("[Warning] " + message.replaceAll("\n","{NEWLINE}"));
+ } else {
+ throw new RuntimeException("PMD threw an unexpected exception:\n" + message);
+ }
}else if (s != null) {
String message = MessageFormat.format(s, objects);
throw new RuntimeException("PMD threw an unexpected exception:\n" + message);
diff --git a/packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/test/java/com/salesforce/sfca/pmdwrapper/PmdWrapperTest.java b/packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/test/java/com/salesforce/sfca/pmdwrapper/PmdWrapperTest.java
index 02a78f3c..49120e8e 100644
--- a/packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/test/java/com/salesforce/sfca/pmdwrapper/PmdWrapperTest.java
+++ b/packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/test/java/com/salesforce/sfca/pmdwrapper/PmdWrapperTest.java
@@ -125,6 +125,70 @@ void whenCallingMainWithDescribeWithCustomRulesetsFile_thenRulesetsAreApplied(@T
assertThat(ruleInfo2.ruleSetFile, is(sampleRulesetFile2.toAbsolutePath().toString()));
}
+ @Test
+ void whenCallingMainWithDescribeWithNcssCountRuleset_thenRuleAppearsInDescribe(@TempDir Path tempDir) throws Exception {
+ // Create a minimal ruleset that references Apex NcssCount (metrics)
+ String rulesetXml = "\n" +
+ "\n" +
+ " Include Apex NcssCount\n" +
+ " \n" +
+ "";
+ Path ncssRuleset = tempDir.resolve("ncss-ruleset.xml");
+ Files.write(ncssRuleset, rulesetXml.getBytes());
+
+ // Prepare describe args with a custom rulesets list file
+ Path outputFile = tempDir.resolve("describe-output.json");
+ Path rulesetsList = tempDir.resolve("customRulesetsList.txt");
+ Files.write(rulesetsList, (ncssRuleset.toAbsolutePath().toString() + "\n").getBytes());
+
+ String[] args = {"describe", outputFile.toAbsolutePath().toString(),
+ rulesetsList.toAbsolutePath().toString(), "apex"};
+ callPmdWrapper(args);
+
+ // Parse output and assert NcssCount is present and references our ruleset file
+ String fileContents = Files.readString(outputFile);
+ Gson gson = new Gson();
+ Type pmdRuleInfoListType = new TypeToken>(){}.getType();
+ List pmdRuleInfoList = gson.fromJson(fileContents, pmdRuleInfoListType);
+ PmdRuleInfo ruleInfo = assertContainsOneRuleWithNameAndLanguage(pmdRuleInfoList, "NcssCount", "apex");
+ assertThat(ruleInfo.ruleSetFile, is(ncssRuleset.toAbsolutePath().toString()));
+ }
+
+ @Test
+ void whenCallingMainWithDescribeWithExcessiveClassLengthRuleset_thenRuleAppearsInDescribe(@TempDir Path tempDir) throws Exception {
+ // Create a minimal ruleset that references Apex ExcessiveClassLength (design)
+ String rulesetXml = "\n" +
+ "\n" +
+ " Include Apex ExcessiveClassLength\n" +
+ " \n" +
+ "";
+ Path excessiveClassLengthRuleset = tempDir.resolve("excessive-class-length-ruleset.xml");
+ Files.write(excessiveClassLengthRuleset, rulesetXml.getBytes());
+
+ // Prepare describe args with a custom rulesets list file
+ Path outputFile = tempDir.resolve("describe-output.json");
+ Path rulesetsList = tempDir.resolve("customRulesetsList.txt");
+ Files.write(rulesetsList, (excessiveClassLengthRuleset.toAbsolutePath().toString() + "\n").getBytes());
+
+ String[] args = {"describe", outputFile.toAbsolutePath().toString(),
+ rulesetsList.toAbsolutePath().toString(), "apex"};
+ callPmdWrapper(args);
+
+ // Parse output and assert ExcessiveClassLength is present and references our ruleset file
+ String fileContents = Files.readString(outputFile);
+ Gson gson = new Gson();
+ Type pmdRuleInfoListType = new TypeToken>(){}.getType();
+ List pmdRuleInfoList = gson.fromJson(fileContents, pmdRuleInfoListType);
+ PmdRuleInfo ruleInfo = assertContainsOneRuleWithNameAndLanguage(pmdRuleInfoList, "ExcessiveClassLength", "apex");
+ assertThat(ruleInfo.ruleSetFile, is(excessiveClassLengthRuleset.toAbsolutePath().toString()));
+ }
+
@Test
void whenCallingMainWithRunAndTwoFewArgs_thenError() {
String[] args = {"run", "notEnough"};
@@ -373,6 +437,97 @@ void whenCallingRunWithAnInvalidApexFileWithValidApexFile_thenSkipInvalidApexFil
assertThat(resultsJsonString, containsString("\"processingErrors\":[{\"file\":")); // Contains the processing error for the invalid file
}
+ @Test
+ void whenRunningWithNcssCountRule_thenReportsOrExecutesSuccessfully(@TempDir Path tempDir) throws Exception {
+ // Create a minimal ruleset that references the Apex NcssCount rule with a very low threshold
+ String rulesetXml = "\n" +
+ "\n" +
+ " Run Apex NcssCount\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "";
+ String rulesetFile = createTempFile(tempDir, "ncss-ruleset.xml", rulesetXml);
+
+ // Create a simple Apex file with a few statements; the low threshold should trigger a violation
+ String apexCode = "public class ManyStatements {\n" +
+ " public static void foo(){\n" +
+ " Integer a = 1; Integer b = 2; Integer c = 3; // multiple statements\n" +
+ " }\n" +
+ "}\n";
+ String apexFile = createTempFile(tempDir, "ManyStatements.cls", apexCode);
+
+ String inputJson = "{\n" +
+ " \"ruleSetInputFile\":\"" + makePathJsonSafe(rulesetFile) + "\",\n" +
+ " \"runDataPerLanguage\": {\n" +
+ " \"apex\": {\n" +
+ " \"filesToScan\": [\"" + makePathJsonSafe(apexFile) + "\"]\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+ String inputFile = createTempFile(tempDir, "input.json", inputJson);
+
+ String resultsOutputFile = tempDir.resolve("results.json").toAbsolutePath().toString();
+ String[] args = {"run", inputFile, resultsOutputFile};
+ callPmdWrapper(args); // Should not error
+
+ String resultsJsonString = new String(Files.readAllBytes(Paths.get(resultsOutputFile)));
+ JsonElement element = JsonParser.parseString(resultsJsonString); // Should not error
+ assertThat(element.isJsonObject(), is(true));
+ }
+
+ @Test
+ void whenRunningWithDeprecatedExcessiveClassLengthRule_thenExecutesSuccessfully(@TempDir Path tempDir) throws Exception {
+ // Create a minimal ruleset referencing the Apex ExcessiveClassLength rule with a low threshold
+ String rulesetXml = "\n" +
+ "\n" +
+ " Run Apex ExcessiveClassLength\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "";
+ String rulesetFile = createTempFile(tempDir, "excessive-class-length.xml", rulesetXml);
+
+ // Create an Apex class with enough lines to exceed the small threshold
+ StringBuilder apexBuilder = new StringBuilder();
+ apexBuilder.append("public class LargeClass {\n");
+ apexBuilder.append(" public static void m0(){ Integer x0 = 0; }\n");
+ apexBuilder.append(" public static void m1(){ Integer x1 = 1; }\n");
+ apexBuilder.append(" public static void m2(){ Integer x2 = 2; }\n");
+ apexBuilder.append(" public static void m3(){ Integer x3 = 3; }\n");
+ apexBuilder.append(" public static void m4(){ Integer x4 = 4; }\n");
+ apexBuilder.append("}\n");
+ String apexFile = createTempFile(tempDir, "LargeClass.cls", apexBuilder.toString());
+
+ String inputJson = "{\n" +
+ " \"ruleSetInputFile\":\"" + makePathJsonSafe(rulesetFile) + "\",\n" +
+ " \"runDataPerLanguage\": {\n" +
+ " \"apex\": {\n" +
+ " \"filesToScan\": [\"" + makePathJsonSafe(apexFile) + "\"]\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+ String inputFile = createTempFile(tempDir, "input-excessive-class-length.json", inputJson);
+
+ String resultsOutputFile = tempDir.resolve("results-excessive-class-length.json").toAbsolutePath().toString();
+ String[] args = {"run", inputFile, resultsOutputFile};
+ callPmdWrapper(args); // Should not error
+
+ String resultsJsonString = new String(Files.readAllBytes(Paths.get(resultsOutputFile)));
+ JsonElement element = JsonParser.parseString(resultsJsonString); // Should not error
+ assertThat(element.isJsonObject(), is(true));
+ }
+
private static String createSampleRulesetFile(Path tempDir) throws Exception {
String ruleSetContents = "\n" +