Skip to content

Commit a5588c6

Browse files
NEW(cpd): @W-16866836@: Add java_command and rule_langage configurability to cpd engine (#123)
1 parent 2686136 commit a5588c6

File tree

19 files changed

+696
-4438
lines changed

19 files changed

+696
-4438
lines changed

packages/code-analyzer-pmd-engine/gradle/libs.versions.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "jun
1919
pmd-apex = { module = "net.sourceforge.pmd:pmd-apex", version.ref = "pmd" }
2020
pmd-core = { module = "net.sourceforge.pmd:pmd-core", version.ref = "pmd" }
2121
pmd-html = { module = "net.sourceforge.pmd:pmd-html", version.ref = "pmd" }
22-
pmd-java = { module = "net.sourceforge.pmd:pmd-java", version.ref = "pmd" }
2322
pmd-javascript = { module = "net.sourceforge.pmd:pmd-javascript", version.ref = "pmd" }
2423
pmd-visualforce = { module = "net.sourceforge.pmd:pmd-visualforce", version.ref = "pmd" }
2524
pmd-xml = { module = "net.sourceforge.pmd:pmd-xml", version.ref = "pmd" }
2625
slf4j-nop = { module = "org.slf4j:slf4j-nop", version.ref = "slf4j-nop" }
2726

2827
[bundles]
29-
pmd7 = ["pmd-apex", "pmd-core", "pmd-html", "pmd-java", "pmd-javascript", "pmd-visualforce", "pmd-xml"]
28+
pmd7 = ["pmd-apex", "pmd-core", "pmd-html", "pmd-javascript", "pmd-visualforce", "pmd-xml"]

packages/code-analyzer-pmd-engine/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@salesforce/code-analyzer-pmd-engine",
33
"description": "Plugin package that adds 'pmd' and 'cpd' as engines into Salesforce Code Analyzer",
4-
"version": "0.13.0",
4+
"version": "0.13.1",
55
"author": "The Salesforce Code Analyzer Team",
66
"license": "BSD-3-Clause",
77
"homepage": "https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview",

packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/main/java/com/salesforce/sfca/cpdwrapper/CpdRunner.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313

1414
import javax.annotation.Nullable;
1515
import java.io.IOException;
16+
import java.io.PrintWriter;
17+
import java.io.StringWriter;
1618
import java.nio.file.Path;
1719
import java.nio.file.Paths;
1820
import java.text.MessageFormat;
21+
import java.util.ArrayList;
1922
import java.util.HashMap;
2023
import java.util.List;
2124
import java.util.Map;
@@ -62,7 +65,9 @@ private CpdLanguageRunResults runLanguage(String language, List<Path> pathsToSca
6265
config.setMinimumTileSize(minimumTokens);
6366
config.setInputPathList(pathsToScan);
6467
config.setSkipDuplicates(skipDuplicateFiles);
65-
config.setReporter(new CpdErrorListener());
68+
CpdErrorListener errorListener = new CpdErrorListener();
69+
70+
config.setReporter(errorListener);
6671

6772
CpdLanguageRunResults languageRunResults = new CpdLanguageRunResults();
6873

@@ -105,6 +110,16 @@ private CpdLanguageRunResults runLanguage(String language, List<Path> pathsToSca
105110
languageRunResults.matches.add(cpdMatch);
106111
}
107112
});
113+
114+
// Instead of throwing exceptions and causing the entire run to fail, instead we report exceptions as
115+
// if they are processing errors so that they can better be handled on the typescript side
116+
for (Exception ex : errorListener.exceptionsCaught) {
117+
CpdLanguageRunResults.ProcessingError processingErr = new CpdLanguageRunResults.ProcessingError();
118+
processingErr.file = "unknown";
119+
processingErr.message = getStackTraceAsString(ex);
120+
processingErr.detail = "[TERMINATING_EXCEPTION]"; // Marker to help typescript side know this isn't just a normal processing error
121+
languageRunResults.processingErrors.add(processingErr);
122+
}
108123
}
109124

110125
return languageRunResults;
@@ -119,19 +134,28 @@ private void validateRunInputData(CpdRunInputData runInputData) {
119134
throw new RuntimeException("The \"minimumTokens\" field was not set to a positive number.");
120135
}
121136
}
137+
138+
private static String getStackTraceAsString(Throwable e) {
139+
StringWriter sw = new StringWriter();
140+
try (PrintWriter pw = new PrintWriter(sw)) {
141+
e.printStackTrace(pw);
142+
}
143+
return sw.toString();
144+
}
122145
}
123146

124147
// This class simply helps us process any errors that may be thrown by CPD. By default, CPD suppresses errors so that
125148
// they are not thrown. So here, we look out for the errors that we care about and process it to throw a better
126149
// error messages. We override the logEx method in particular because all other error methods call through to logEx.
127150
class CpdErrorListener implements PmdReporter {
151+
List<Exception> exceptionsCaught = new ArrayList<>();
128152
@Override
129153
public void logEx(Level level, @javax.annotation.Nullable String s, Object[] objects, @Nullable Throwable throwable) {
130154
if (throwable != null) {
131-
throw new RuntimeException("CPD threw an unexpected exception:\n" + throwable.getMessage(), throwable);
155+
exceptionsCaught.add(new RuntimeException("CPD threw an unexpected exception:\n" + throwable.getMessage(), throwable));
132156
} else if (s != null) {
133157
String message = MessageFormat.format(s, objects);
134-
throw new RuntimeException("CPD threw an unexpected exception:\n" + message);
158+
exceptionsCaught.add(new RuntimeException("CPD threw an unexpected exception:\n" + message));
135159
}
136160
}
137161

packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/main/java/com/salesforce/sfca/pmdwrapper/PmdRuleDescriber.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,6 @@ class PmdRuleDescriber {
4343
"html", Set.of(
4444
"category/html/bestpractices.xml"
4545
),
46-
"java", Set.of(
47-
"category/java/bestpractices.xml",
48-
"category/java/codestyle.xml",
49-
"category/java/design.xml",
50-
"category/java/documentation.xml",
51-
"category/java/errorprone.xml",
52-
"category/java/multithreading.xml",
53-
"category/java/performance.xml",
54-
"category/java/security.xml"
55-
),
5646
"pom", Set.of(
5747
"category/pom/errorprone.xml"
5848
),

packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/test/java/com/salesforce/sfca/cpdwrapper/CpdWrapperTest.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ void whenCallingRunWithNegativeMinimumTokensValue_thenError(@TempDir Path tempDi
182182
}
183183

184184
@Test
185-
void whenCallingRunWithFileToScanThatDoesNotExist_thenError(@TempDir Path tempDir) throws Exception {
185+
void whenCallingRunWithFileToScanThatDoesNotExist_thenExceptionIsForwardedAsProcessingErrorWithTerminatingExceptionMarker(@TempDir Path tempDir) throws Exception {
186186
String doesNotExist = tempDir.resolve("doesNotExist.cls").toAbsolutePath().toString();
187187
String inputFileContents = "{" +
188188
" \"filesToScanPerLanguage\": {" +
@@ -192,11 +192,16 @@ void whenCallingRunWithFileToScanThatDoesNotExist_thenError(@TempDir Path tempDi
192192
" \"skipDuplicateFiles\": false " +
193193
"}";
194194
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);
195+
String outputFile = tempDir.resolve("output.json").toAbsolutePath().toString();
195196

196-
String[] args = {"run", inputFile, "/does/not/matter"};
197-
RuntimeException thrown = assertThrows(RuntimeException.class, () -> callCpdWrapper(args));
198-
assertThat(thrown.getMessage(), is(
199-
"Error while attempting to invoke CpdRunner.run: CPD threw an unexpected exception:\nNo such file " + doesNotExist));
197+
String[] args = {"run", inputFile, outputFile};
198+
callCpdWrapper(args);
199+
200+
String resultsJsonString = new String(Files.readAllBytes(Paths.get(outputFile)));
201+
assertThat(resultsJsonString, allOf(
202+
containsString("\"processingErrors\":[{"),
203+
containsString("No such file"),
204+
containsString("\"detail\":\"[TERMINATING_EXCEPTION]\"")));
200205
}
201206

202207
@Test

packages/code-analyzer-pmd-engine/pmd-cpd-wrappers/src/test/java/com/salesforce/sfca/pmdwrapper/PmdRuleDescriberTest.java

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -71,23 +71,6 @@ void whenDescribeRulesForApex_thenCorrectRulesAreReturned() {
7171
assertThat(ruleInfo.ruleSetFile, is("category/apex/performance.xml"));
7272
}
7373

74-
@Test
75-
void whenDescribeRulesForJava_thenCorrectRulesAreReturned() {
76-
List<PmdRuleInfo> ruleInfoList = ruleDescriber.describeRulesFor(List.of(), Set.of("java"));
77-
assertThat(ruleInfoList.size(), is(greaterThan(0))); // Leaving this flexible. The actual list of rules are tested by typescript tests.
78-
for (PmdRuleInfo ruleInfo : ruleInfoList) {
79-
assertThat(ruleInfo.language, is("java"));
80-
}
81-
82-
// Sanity check one of the rules:
83-
PmdRuleInfo ruleInfo = assertContainsOneRuleWithNameAndLanguage(ruleInfoList, "AvoidReassigningParameters", "java");
84-
assertThat(ruleInfo.description, is("Reassigning values to incoming parameters of a method or constructor is not recommended, as this can make the code more difficult to understand. The code is often read with the assumption that parameter values don't change and an assignment violates therefore the principle of least astonishment. This is especially a problem if the parameter is documented e.g. in the method's... Learn more: " + ruleInfo.externalInfoUrl));
85-
assertThat(ruleInfo.externalInfoUrl, allOf(startsWith("https://"), endsWith(".html#avoidreassigningparameters")));
86-
assertThat(ruleInfo.ruleSet, is("Best Practices"));
87-
assertThat(ruleInfo.priority, is("Medium High"));
88-
assertThat(ruleInfo.ruleSetFile, is("category/java/bestpractices.xml"));
89-
}
90-
9174
@Test
9275
void whenDescribeRulesForEcmascript_thenCorrectRulesAreReturned() {
9376
List<PmdRuleInfo> ruleInfoList = ruleDescriber.describeRulesFor(List.of(), Set.of("ecmascript"));
@@ -192,10 +175,10 @@ void whenDescribeRulesIsGivenCustomRulesetThatIsOnJavaClasspath_thenReturnAssoci
192175
// cause any conflicts or errors.
193176
try (StdOutCaptor stdoutCaptor = new StdOutCaptor()) {
194177
List<PmdRuleInfo> ruleInfoList = ruleDescriber.describeRulesFor(
195-
List.of("category/java/codestyle.xml"),
196-
Set.of("java"));
178+
List.of("category/apex/codestyle.xml"),
179+
Set.of("apex"));
197180

198-
assertContainsOneRuleWithNameAndLanguage(ruleInfoList, "AtLeastOneConstructor", "java");
181+
assertContainsOneRuleWithNameAndLanguage(ruleInfoList, "ClassNamingConventions", "apex");
199182
assertThat(stdoutCaptor.getCapturedOutput(), containsString("Skipping rule "));
200183
}
201184
}
@@ -209,11 +192,11 @@ void whenDescribeRulesIsGivenCustomRulesetButCustomRuleLanguageIsNotSpecified_th
209192

210193
List<PmdRuleInfo> ruleInfoList = ruleDescriber.describeRulesFor(
211194
List.of(rulesetFile1.toAbsolutePath().toString(), rulesetFile2.toAbsolutePath().toString()),
212-
Set.of("java", "visualforce")); // ... but we don't have apex here but we do have visualforce...
195+
Set.of("ecmascript", "visualforce")); // ... but we don't have apex here but we do have visualforce...
213196

214197
assertContainsNoRuleWithNameAndLanguage(ruleInfoList, "sampleRule1", "apex"); // ... thus this rule should not show
215198
assertContainsOneRuleWithNameAndLanguage(ruleInfoList, "sampleRule2", "visualforce"); // Should show since visualforce is provided
216-
assertContainsOneRuleWithNameAndLanguage(ruleInfoList, "AtLeastOneConstructor", "java"); // Should show since visualforce is provided
199+
assertContainsOneRuleWithNameAndLanguage(ruleInfoList, "AvoidWithStatement", "ecmascript"); // Should show since ecmascript is provided
217200
}
218201

219202
@Test

0 commit comments

Comments
 (0)