Skip to content

Commit 29699b8

Browse files
CHANGE(cpd): @W-17386401@: Update cpd engine's minimum_tokens field to be an object with values per language
1 parent 84df89c commit 29699b8

File tree

10 files changed

+225
-98
lines changed

10 files changed

+225
-98
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
* Data structure for the CpdRunner that we can deserialize the input json file into
88
*/
99
class CpdRunInputData {
10-
public Map<String, List<String>> filesToScanPerLanguage;
11-
public int minimumTokens;
10+
public Map<String, LanguageSpecificRunData> runDataPerLanguage;
1211
public boolean skipDuplicateFiles;
1312
}
13+
14+
class LanguageSpecificRunData {
15+
public List<String> filesToScan;
16+
public int minimumTokens;
17+
}

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

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@
1818
import java.nio.file.Path;
1919
import java.nio.file.Paths;
2020
import java.text.MessageFormat;
21-
import java.util.ArrayList;
22-
import java.util.HashMap;
23-
import java.util.List;
24-
import java.util.Map;
21+
import java.util.*;
2522
import java.util.stream.Collectors;
2623

2724
/**
@@ -33,18 +30,15 @@ class CpdRunner {
3330
public Map<String, CpdLanguageRunResults> run(CpdRunInputData runInputData) throws IOException {
3431
validateRunInputData(runInputData);
3532

36-
List<String> languagesToProcess = runInputData.filesToScanPerLanguage.entrySet().stream()
37-
.filter(entry -> !entry.getValue().isEmpty()) // Keep only non-empty lists
38-
.map(Map.Entry::getKey)
39-
.collect(Collectors.toList());
40-
33+
List<String> languagesToProcess = new ArrayList<>(runInputData.runDataPerLanguage.keySet());
4134
progressReporter.initialize(languagesToProcess);
4235

4336
Map<String, CpdLanguageRunResults> results = new HashMap<>();
4437
for (String language : languagesToProcess) {
45-
List<String> filesToScan = runInputData.filesToScanPerLanguage.get(language);
46-
List<Path> pathsToScan = filesToScan.stream().map(Paths::get).collect(Collectors.toList());
47-
CpdLanguageRunResults languageRunResults = runLanguage(language, pathsToScan, runInputData.minimumTokens, runInputData.skipDuplicateFiles);
38+
LanguageSpecificRunData languageSpecificRunData = runInputData.runDataPerLanguage.get(language);
39+
List<Path> pathsToScan = languageSpecificRunData.filesToScan.stream().map(Paths::get).collect(Collectors.toList());
40+
CpdLanguageRunResults languageRunResults = runLanguage(
41+
language, pathsToScan, languageSpecificRunData.minimumTokens, runInputData.skipDuplicateFiles);
4842
if (!languageRunResults.matches.isEmpty() || !languageRunResults.processingErrors.isEmpty()) {
4943
results.put(language, languageRunResults);
5044
}
@@ -126,12 +120,24 @@ private CpdLanguageRunResults runLanguage(String language, List<Path> pathsToSca
126120
}
127121

128122
private void validateRunInputData(CpdRunInputData runInputData) {
129-
if (runInputData.filesToScanPerLanguage == null) {
130-
throw new RuntimeException("The \"filesToScanPerLanguage\" field was not set.");
131-
} else if (runInputData.filesToScanPerLanguage.isEmpty()) {
132-
throw new RuntimeException(("The \"filesToScanPerLanguage\" field was found to be empty."));
133-
} else if (runInputData.minimumTokens <= 0) {
134-
throw new RuntimeException("The \"minimumTokens\" field was not set to a positive number.");
123+
if (runInputData.runDataPerLanguage == null) {
124+
throw new RuntimeException("The \"runDataPerLanguage\" field was not set.");
125+
}
126+
127+
Set<Map.Entry<String, LanguageSpecificRunData>> entries = runInputData.runDataPerLanguage.entrySet();
128+
if (entries.isEmpty()) {
129+
throw new RuntimeException("The \"runDataPerLanguage\" field didn't have any languages listed.");
130+
}
131+
132+
for (Map.Entry<String, LanguageSpecificRunData> entry: entries) {
133+
String language = entry.getKey();
134+
LanguageSpecificRunData languageSpecificRunData = entry.getValue();
135+
136+
if (languageSpecificRunData.filesToScan == null || languageSpecificRunData.filesToScan.isEmpty()) {
137+
throw new RuntimeException(("The \"filesToScan\" field was missing or empty for language: " + language));
138+
} else if (languageSpecificRunData.minimumTokens <= 0) {
139+
throw new RuntimeException("The \"minimumTokens\" field was not set to a positive number for language: " + language);
140+
}
135141
}
136142
}
137143

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@
1717
* - {argsInputFile} is a JSON file containing the input arguments for the run command.
1818
* Example:
1919
* {
20-
* "filesToScanPerLanguage": {
21-
* "apex": ["/full/path/to/apex_file1.cls", "/full/path/to/apex_file2.trigger", ...],
20+
* "runDataPerLanguage": {
21+
* "apex": {
22+
* "filesToScan": ["/full/path/to/apex_file1.cls", "/full/path/to/apex_file2.trigger", ...],
23+
* "minimumTokens": 100
24+
* },
2225
* ...,
23-
* "xml": ["full/path/to/xml_file1.xml", "/full/path/to/xml_file2.xml", ...]
26+
* "xml": {
27+
* "filesToScan": ["full/path/to/xml_file1.xml", "/full/path/to/xml_file2.xml", ...],
28+
* "minimumTokens": 150
29+
* }
2430
* },
25-
* "minimumTokens": 100,
2631
* "skipDuplicateFiles": false
2732
* }
2833
* - {resultsOutputFile} is a JSON file to write CPD results to.

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

Lines changed: 86 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -99,43 +99,80 @@ void whenCallingMainWithRunAndInputFileThatDoesNotContainValidJson_thenError(@Te
9999
}
100100

101101
@Test
102-
void whenCallingRunWithMissingField_filesToScanPerLanguage_thenError(@TempDir Path tempDir) throws Exception {
102+
void whenCallingRunWithMissingField_runDataPerLanguage_thenError(@TempDir Path tempDir) throws Exception {
103103
String inputFileContents = "{" +
104-
" \"minimumTokens\": 100, " +
105104
" \"skipDuplicateFiles\": false " +
106105
"}";
107106
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);
108107

109108
String[] args = {"run", inputFile, "/does/not/matter"};
110109
RuntimeException thrown = assertThrows(RuntimeException.class, () -> callCpdWrapper(args));
111110
assertThat(thrown.getMessage(), is(
112-
"Error while attempting to invoke CpdRunner.run: The \"filesToScanPerLanguage\" field was not set."));
111+
"Error while attempting to invoke CpdRunner.run: The \"runDataPerLanguage\" field was not set."));
112+
}
113+
114+
@Test
115+
void whenCallingRunWithMissingField_filesToScan_thenError(@TempDir Path tempDir) throws Exception {
116+
String inputFileContents = "{" +
117+
" \"runDataPerLanguage\": {" +
118+
" \"apex\": {" +
119+
" \"minimumTokens\": 100 " +
120+
" }" +
121+
" }," +
122+
" \"skipDuplicateFiles\": false " +
123+
"}";
124+
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);
125+
126+
String[] args = {"run", inputFile, "/does/not/matter"};
127+
RuntimeException thrown = assertThrows(RuntimeException.class, () -> callCpdWrapper(args));
128+
assertThat(thrown.getMessage(), is(
129+
"Error while attempting to invoke CpdRunner.run: The \"filesToScan\" field was missing or empty for language: apex"));
130+
}
131+
132+
@Test
133+
void whenCallingRunWithEmptyArrayFor_filesToScan_thenError(@TempDir Path tempDir) throws Exception {
134+
String inputFileContents = "{" +
135+
" \"runDataPerLanguage\": {" +
136+
" \"apex\": {" +
137+
" \"filesToScan\": []," +
138+
" \"minimumTokens\": 100 " +
139+
" }" +
140+
" }," +
141+
" \"skipDuplicateFiles\": false " +
142+
"}";
143+
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);
144+
145+
String[] args = {"run", inputFile, "/does/not/matter"};
146+
RuntimeException thrown = assertThrows(RuntimeException.class, () -> callCpdWrapper(args));
147+
assertThat(thrown.getMessage(), is(
148+
"Error while attempting to invoke CpdRunner.run: The \"filesToScan\" field was missing or empty for language: apex"));
113149
}
114150

115151
@Test
116152
void whenCallingRunWithZeroLanguages_thenError(@TempDir Path tempDir) throws Exception {
117153
String inputFileContents = "{" +
118-
" \"filesToScanPerLanguage\": {" +
154+
" \"runDataPerLanguage\": {" +
119155
" }," +
120-
" \"minimumTokens\": 120," +
121156
" \"skipDuplicateFiles\": false " +
122157
"}";
123158
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);
124159

125160
String[] args = {"run", inputFile, "/does/not/matter"};
126161
RuntimeException thrown = assertThrows(RuntimeException.class, () -> callCpdWrapper(args));
127162
assertThat(thrown.getMessage(), is(
128-
"Error while attempting to invoke CpdRunner.run: The \"filesToScanPerLanguage\" field was found to be empty."));
163+
"Error while attempting to invoke CpdRunner.run: The \"runDataPerLanguage\" field didn't have any languages listed."));
129164
}
130165

131166
@Test
132167
void whenCallingRunWithInvalidLanguage_thenError(@TempDir Path tempDir) throws Exception {
133168
String dummyFile = createTempFile(tempDir, "dummy", "");
134169
String inputFileContents = "{" +
135-
" \"filesToScanPerLanguage\": {" +
136-
" \"unknownLanguage\": [\"" + makePathJsonSafe(dummyFile) + "\"]" +
170+
" \"runDataPerLanguage\": {" +
171+
" \"unknownLanguage\": {" +
172+
" \"filesToScan\": [\"" + makePathJsonSafe(dummyFile) + "\"]," +
173+
" \"minimumTokens\": 120" +
174+
" }" +
137175
" }," +
138-
" \"minimumTokens\": 120," +
139176
" \"skipDuplicateFiles\": false " +
140177
"}";
141178
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);
@@ -150,8 +187,10 @@ void whenCallingRunWithInvalidLanguage_thenError(@TempDir Path tempDir) throws E
150187
void whenCallingRunWithMissingField_minimumTokens_thenError(@TempDir Path tempDir) throws Exception {
151188
String dummyApexFile = createTempFile(tempDir, "dummy.cls", "");
152189
String inputFileContents = "{" +
153-
" \"filesToScanPerLanguage\": {" +
154-
" \"apex\": [\"" + makePathJsonSafe(dummyApexFile) + "\"]" +
190+
" \"runDataPerLanguage\": {" +
191+
" \"apex\": {" +
192+
" \"filesToScan\": [\"" + makePathJsonSafe(dummyApexFile) + "\"]" +
193+
" }" +
155194
" }," +
156195
" \"skipDuplicateFiles\": false " +
157196
"}";
@@ -160,35 +199,39 @@ void whenCallingRunWithMissingField_minimumTokens_thenError(@TempDir Path tempDi
160199
String[] args = {"run", inputFile, "/does/not/matter"};
161200
RuntimeException thrown = assertThrows(RuntimeException.class, () -> callCpdWrapper(args));
162201
assertThat(thrown.getMessage(), is(
163-
"Error while attempting to invoke CpdRunner.run: The \"minimumTokens\" field was not set to a positive number."));
202+
"Error while attempting to invoke CpdRunner.run: The \"minimumTokens\" field was not set to a positive number for language: apex"));
164203
}
165204

166205
@Test
167206
void whenCallingRunWithNegativeMinimumTokensValue_thenError(@TempDir Path tempDir) throws Exception {
168207
String dummyApexFile = createTempFile(tempDir, "dummy.cls", "");
169208
String inputFileContents = "{" +
170-
" \"filesToScanPerLanguage\": {" +
171-
" \"apex\": [\"" + makePathJsonSafe(dummyApexFile) + "\"]" +
209+
" \"runDataPerLanguage\": {" +
210+
" \"apex\": {" +
211+
" \"filesToScan\": [\"" + makePathJsonSafe(dummyApexFile) + "\"]," +
212+
" \"minimumTokens\": -1" +
213+
" }" +
172214
" }," +
173-
" \"minimumTokens\": -1," +
174215
" \"skipDuplicateFiles\": false " +
175216
"}";
176217
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);
177218

178219
String[] args = {"run", inputFile, "/does/not/matter"};
179220
RuntimeException thrown = assertThrows(RuntimeException.class, () -> callCpdWrapper(args));
180221
assertThat(thrown.getMessage(), is(
181-
"Error while attempting to invoke CpdRunner.run: The \"minimumTokens\" field was not set to a positive number."));
222+
"Error while attempting to invoke CpdRunner.run: The \"minimumTokens\" field was not set to a positive number for language: apex"));
182223
}
183224

184225
@Test
185226
void whenCallingRunWithFileToScanThatDoesNotExist_thenExceptionIsForwardedAsProcessingErrorWithTerminatingExceptionMarker(@TempDir Path tempDir) throws Exception {
186227
String doesNotExist = tempDir.resolve("doesNotExist.cls").toAbsolutePath().toString();
187228
String inputFileContents = "{" +
188-
" \"filesToScanPerLanguage\": {" +
189-
" \"apex\": [\"" + makePathJsonSafe(doesNotExist) + "\"]" +
229+
" \"runDataPerLanguage\": {" +
230+
" \"apex\": {" +
231+
" \"filesToScan\": [\"" + makePathJsonSafe(doesNotExist) + "\"]," +
232+
" \"minimumTokens\": 100" +
233+
" }" +
190234
" }," +
191-
" \"minimumTokens\": 100," +
192235
" \"skipDuplicateFiles\": false " +
193236
"}";
194237
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);
@@ -212,11 +255,16 @@ void whenCallingRunWithValidFilesThatHaveDuplicates_thenJsonOutputShouldContainR
212255
String jsFile2 = createTempFile(tempDir, "jsFile2.js", SAMPLE_JS_2);
213256

214257
String inputFileContents = "{" +
215-
" \"filesToScanPerLanguage\": {" +
216-
" \"apex\": [\"" + makePathJsonSafe(apexFile1) + "\", \"" + makePathJsonSafe(apexFile2) + "\"]," +
217-
" \"ecmascript\": [\"" + makePathJsonSafe(jsFile1) + "\", \"" + makePathJsonSafe(jsFile2) + "\"]" +
258+
" \"runDataPerLanguage\": {" +
259+
" \"apex\": {" +
260+
" \"filesToScan\": [\"" + makePathJsonSafe(apexFile1) + "\", \"" + makePathJsonSafe(apexFile2) + "\"]," +
261+
" \"minimumTokens\": 5" +
262+
" }," +
263+
" \"ecmascript\": {" +
264+
" \"filesToScan\": [\"" + makePathJsonSafe(jsFile1) + "\", \"" + makePathJsonSafe(jsFile2) + "\"]," +
265+
" \"minimumTokens\": 13" +
266+
" }" +
218267
" }," +
219-
" \"minimumTokens\": 5," +
220268
" \"skipDuplicateFiles\": false " +
221269
"}";
222270
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);
@@ -344,10 +392,12 @@ void whenCallingRunWithValidFilesHaveZeroDuplicatesSinceMinTokensIsHigh_thenJson
344392
String apexFile2 = createTempFile(tempDir, "ApexClass2.cls", SAMPLE_APEX_2);
345393

346394
String inputFileContents = "{" +
347-
" \"filesToScanPerLanguage\": {" +
348-
" \"apex\": [\"" + makePathJsonSafe(apexFile1) + "\", \"" + makePathJsonSafe(apexFile2) + "\"]" +
395+
" \"runDataPerLanguage\": {" +
396+
" \"apex\": {" +
397+
" \"filesToScan\": [\"" + makePathJsonSafe(apexFile1) + "\", \"" + makePathJsonSafe(apexFile2) + "\"]," +
398+
" \"minimumTokens\": 500" +
399+
" }" +
349400
" }," +
350-
" \"minimumTokens\": 500," + // This is why there are no dups found
351401
" \"skipDuplicateFiles\": false " +
352402
"}";
353403
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);
@@ -370,11 +420,12 @@ void whenCallingRunWithTwoIdenticalFilesButSkipDuplicateFilesIsFalse_thenJsonOut
370420
String apexFileInSubFolder = createTempFile(subFolder, "ApexClass1.cls", SAMPLE_APEX_1);
371421

372422
String inputFileContents = "{" +
373-
" \"filesToScanPerLanguage\": {" +
374-
" \"apex\": [\"" + makePathJsonSafe(apexFileInParentFolder) + "\", \"" + makePathJsonSafe(apexFileInSubFolder) + "\"]," +
375-
" \"xml\": []" + // Edge case - checking also that this doesn't blow up anything
423+
" \"runDataPerLanguage\": {" +
424+
" \"apex\": {" +
425+
" \"filesToScan\": [\"" + makePathJsonSafe(apexFileInParentFolder) + "\", \"" + makePathJsonSafe(apexFileInSubFolder) + "\"]," +
426+
" \"minimumTokens\": 15" +
427+
" }" +
376428
" }," +
377-
" \"minimumTokens\": 15," +
378429
" \"skipDuplicateFiles\": false " +
379430
"}";
380431
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);
@@ -434,10 +485,12 @@ void whenCallingRunWithTwoIdenticalFilesButSkipDuplicateFilesIsTrue_thenJsonOutp
434485
String apexFileInSubFolder = createTempFile(subFolder, "ApexClass1.cls", SAMPLE_APEX_1);
435486

436487
String inputFileContents = "{" +
437-
" \"filesToScanPerLanguage\": {" +
438-
" \"apex\": [\"" + makePathJsonSafe(apexFileInParentFolder) + "\", \"" + makePathJsonSafe(apexFileInSubFolder) + "\"]" +
488+
" \"runDataPerLanguage\": {" +
489+
" \"apex\": {" +
490+
" \"filesToScan\": [\"" + makePathJsonSafe(apexFileInParentFolder) + "\", \"" + makePathJsonSafe(apexFileInSubFolder) + "\"]," +
491+
" \"minimumTokens\": 15" +
492+
" }" +
439493
" }," +
440-
" \"minimumTokens\": 15," +
441494
" \"skipDuplicateFiles\": true " +
442495
"}";
443496
String inputFile = createTempFile(tempDir, "inputFile.json", inputFileContents);

0 commit comments

Comments
 (0)