11package com .salesforce .sfca .cpdwrapper ;
22
3+ import com .salesforce .sfca .shared .CodeLocation ;
4+ import com .salesforce .sfca .shared .ProcessingError ;
5+ import com .salesforce .sfca .shared .ProgressReporter ;
36import net .sourceforge .pmd .cpd .CPDConfiguration ;
47import net .sourceforge .pmd .cpd .CPDListener ;
58import net .sourceforge .pmd .cpd .CpdAnalysis ;
69import net .sourceforge .pmd .cpd .Mark ;
710import net .sourceforge .pmd .cpd .Match ;
811import net .sourceforge .pmd .lang .Language ;
9- import net .sourceforge .pmd .lang .document .FileLocation ;
1012import net .sourceforge .pmd .reporting .Report ;
1113import net .sourceforge .pmd .util .log .PmdReporter ;
1214import org .slf4j .event .Level ;
@@ -30,12 +32,13 @@ class CpdRunner {
3032 public Map <String , CpdLanguageRunResults > run (CpdRunInputData runInputData ) throws IOException {
3133 validateRunInputData (runInputData );
3234
33- List <String > languagesToProcess = new ArrayList <>(runInputData .runDataPerLanguage .keySet ());
34- progressReporter .initialize (languagesToProcess );
35+ Map <String , Integer > languageFileCounts = runInputData .runDataPerLanguage .entrySet ().stream ()
36+ .collect (Collectors .toMap (Map .Entry ::getKey , entry -> entry .getValue ().filesToScan .size ()));
37+ progressReporter .initialize (languageFileCounts );
3538
3639 Map <String , CpdLanguageRunResults > results = new HashMap <>();
37- for (String language : languagesToProcess ) {
38- LanguageSpecificRunData languageSpecificRunData = runInputData .runDataPerLanguage .get (language );
40+ for (String language : runInputData . runDataPerLanguage . keySet () ) {
41+ CpdRunInputData . LanguageSpecificRunData languageSpecificRunData = runInputData .runDataPerLanguage .get (language );
3942 List <Path > pathsToScan = languageSpecificRunData .filesToScan .stream ().map (Paths ::get ).collect (Collectors .toList ());
4043 CpdLanguageRunResults languageRunResults = runLanguage (
4144 language , pathsToScan , languageSpecificRunData .minimumTokens , runInputData .skipDuplicateFiles );
@@ -47,6 +50,9 @@ public Map<String, CpdLanguageRunResults> run(CpdRunInputData runInputData) thro
4750 }
4851
4952 private CpdLanguageRunResults runLanguage (String language , List <Path > pathsToScan , int minimumTokens , boolean skipDuplicateFiles ) throws IOException {
53+ System .out .println ("Running CPD for language '" + language + "' with " + pathsToScan .size () +
54+ " file(s) to scan using minimumTokens=" + minimumTokens + " and skipDuplicateFiles=" + skipDuplicateFiles + "." );
55+
5056 // Note that the name "minimumTokens" comes from the public facing documentation and the cli but
5157 // behind the scenes, it maps to MinimumTileSize. To learn more about the mappings to the config, see:
5258 // https://github.com/pmd/pmd/blob/main/pmd-cli/src/main/java/net/sourceforge/pmd/cli/commands/internal/CpdCommand.java
@@ -76,11 +82,8 @@ private CpdLanguageRunResults runLanguage(String language, List<Path> pathsToSca
7682
7783 cpd .performAnalysis (report -> {
7884 for (Report .ProcessingError reportProcessingError : report .getProcessingErrors ()) {
79- CpdLanguageRunResults .ProcessingError processingErr = new CpdLanguageRunResults .ProcessingError ();
80- processingErr .file = reportProcessingError .getFileId ().getAbsolutePath ();
81- processingErr .message = reportProcessingError .getMsg ();
82- processingErr .detail = reportProcessingError .getDetail ();
83- languageRunResults .processingErrors .add (processingErr );
85+ languageRunResults .processingErrors .add (
86+ ProcessingError .fromReportProcessingError (reportProcessingError ));
8487 }
8588
8689 for (Match match : report .getMatches ()) {
@@ -90,15 +93,8 @@ private CpdLanguageRunResults runLanguage(String language, List<Path> pathsToSca
9093 cpdMatch .numNonemptyLinesInBlock = match .getLineCount ();
9194
9295 for (Mark mark : match .getMarkSet ()) {
93- CpdLanguageRunResults .Match .BlockLocation blockLocation = new CpdLanguageRunResults .Match .BlockLocation ();
94- FileLocation location = mark .getLocation ();
95- blockLocation .file = location .getFileId ().getAbsolutePath ();
96- blockLocation .startLine = location .getStartLine ();
97- blockLocation .startCol = location .getStartColumn ();
98- blockLocation .endLine = location .getEndLine ();
99- blockLocation .endCol = location .getEndColumn ();
100-
101- cpdMatch .blockLocations .add (blockLocation );
96+ cpdMatch .blockLocations .add (
97+ CodeLocation .fromFileLocation (mark .getLocation ()));
10298 }
10399
104100 languageRunResults .matches .add (cpdMatch );
@@ -108,7 +104,7 @@ private CpdLanguageRunResults runLanguage(String language, List<Path> pathsToSca
108104 // Instead of throwing exceptions and causing the entire run to fail, instead we report exceptions as
109105 // if they are processing errors so that they can better be handled on the typescript side
110106 for (Exception ex : errorListener .exceptionsCaught ) {
111- CpdLanguageRunResults . ProcessingError processingErr = new CpdLanguageRunResults . ProcessingError ();
107+ ProcessingError processingErr = new ProcessingError ();
112108 processingErr .file = "unknown" ;
113109 processingErr .message = getStackTraceAsString (ex );
114110 processingErr .detail = "[TERMINATING_EXCEPTION]" ; // Marker to help typescript side know this isn't just a normal processing error
@@ -124,14 +120,14 @@ private void validateRunInputData(CpdRunInputData runInputData) {
124120 throw new RuntimeException ("The \" runDataPerLanguage\" field was not set." );
125121 }
126122
127- Set <Map .Entry <String , LanguageSpecificRunData >> entries = runInputData .runDataPerLanguage .entrySet ();
123+ Set <Map .Entry <String , CpdRunInputData . LanguageSpecificRunData >> entries = runInputData .runDataPerLanguage .entrySet ();
128124 if (entries .isEmpty ()) {
129125 throw new RuntimeException ("The \" runDataPerLanguage\" field didn't have any languages listed." );
130126 }
131127
132- for (Map .Entry <String , LanguageSpecificRunData > entry : entries ) {
128+ for (Map .Entry <String , CpdRunInputData . LanguageSpecificRunData > entry : entries ) {
133129 String language = entry .getKey ();
134- LanguageSpecificRunData languageSpecificRunData = entry .getValue ();
130+ CpdRunInputData . LanguageSpecificRunData languageSpecificRunData = entry .getValue ();
135131
136132 if (languageSpecificRunData .filesToScan == null || languageSpecificRunData .filesToScan .isEmpty ()) {
137133 throw new RuntimeException (("The \" filesToScan\" field was missing or empty for language: " + language ));
@@ -176,38 +172,6 @@ public int numErrors() {
176172 }
177173}
178174
179- // This class helps us track the overall progress of all language runs
180- class ProgressReporter {
181- private Map <String , Float > progressPerLanguage = new HashMap <>();
182- private float lastReportedProgress = 0.0f ;
183-
184- public void initialize (List <String > languages ) {
185- progressPerLanguage = new HashMap <>();
186- languages .forEach (l -> this .updateProgressForLanguage (l , 0.0f ));
187- }
188-
189- public void updateProgressForLanguage (String language , float percComplete ) {
190- progressPerLanguage .put (language , percComplete );
191- }
192-
193- public void reportOverallProgress () {
194- float currentProgress = this .calculateOverallPercentage ();
195- // The progress goes very fast, so we make sure to only report progress if there has been a significant enough increase (at least 1%)
196- if (currentProgress >= lastReportedProgress + 1 ) {
197- System .out .println ("[Progress]" + currentProgress );
198- lastReportedProgress = currentProgress ;
199- }
200- }
201-
202- private float calculateOverallPercentage () {
203- float sum = 0.0f ;
204- for (float progress : progressPerLanguage .values ()) {
205- sum += progress ;
206- }
207- return sum / progressPerLanguage .size ();
208- }
209- }
210-
211175// This class is a specific listener for a run of cpd for a single language.
212176class CpdLanguageRunListener implements CPDListener {
213177 private final ProgressReporter progressReporter ;
0 commit comments