pmdtester 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +12 -0
  3. data/.github/workflows/build.yml +9 -7
  4. data/.github/workflows/manual-integration-tests.yml +9 -7
  5. data/.github/workflows/publish-release.yml +9 -9
  6. data/.hoerc +1 -1
  7. data/.rubocop_todo.yml +1 -14
  8. data/.vscode/launch.json +32 -0
  9. data/History.md +54 -0
  10. data/Manifest.txt +13 -0
  11. data/README.rdoc +77 -30
  12. data/Rakefile +11 -11
  13. data/config/custom.jfc +1126 -0
  14. data/config/project-list-with-cpd.xml +268 -0
  15. data/config/projectlist_1_2_0.xsd +1 -1
  16. data/config/projectlist_1_3_0.xsd +53 -0
  17. data/lib/pmdtester/builders/cpd_project_hasher.rb +70 -0
  18. data/lib/pmdtester/builders/liquid_renderer.rb +111 -16
  19. data/lib/pmdtester/builders/pmd_report_builder.rb +139 -41
  20. data/lib/pmdtester/builders/project_hasher.rb +24 -25
  21. data/lib/pmdtester/builders/rule_set_builder.rb +43 -11
  22. data/lib/pmdtester/builders/summary_report_builder.rb +6 -1
  23. data/lib/pmdtester/cmd.rb +24 -9
  24. data/lib/pmdtester/cpd_report_diff.rb +99 -0
  25. data/lib/pmdtester/jfr_summary.rb +119 -0
  26. data/lib/pmdtester/location.rb +38 -0
  27. data/lib/pmdtester/parsers/cpd_report_document.rb +241 -0
  28. data/lib/pmdtester/parsers/options.rb +19 -0
  29. data/lib/pmdtester/parsers/pmd_report_document.rb +14 -1
  30. data/lib/pmdtester/parsers/projects_parser.rb +1 -1
  31. data/lib/pmdtester/pmd_branch_detail.rb +29 -9
  32. data/lib/pmdtester/pmd_report_detail.rb +54 -13
  33. data/lib/pmdtester/pmd_tester_utils.rb +45 -17
  34. data/lib/pmdtester/pmd_violation.rb +15 -6
  35. data/lib/pmdtester/project.rb +63 -3
  36. data/lib/pmdtester/report_diff.rb +5 -13
  37. data/lib/pmdtester/runner.rb +185 -37
  38. data/lib/pmdtester/system_info.rb +58 -0
  39. data/lib/pmdtester/word_differ.rb +132 -0
  40. data/lib/pmdtester.rb +8 -1
  41. data/pmdtester.gemspec +17 -17
  42. data/resources/css/pmd-tester.css +15 -0
  43. data/resources/js/project-report.js +293 -112
  44. data/resources/project_cpd_report.html +144 -0
  45. data/resources/project_diff_report.html +151 -18
  46. data/resources/project_index.html +12 -3
  47. data/resources/project_pmd_report.html +17 -2
  48. metadata +63 -43
@@ -0,0 +1,268 @@
1
+ <?xml version="1.0"?>
2
+
3
+ <projectlist xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+ xsi:noNamespaceSchemaLocation="projectlist_1_3_0.xsd">
5
+ <description>Standard Projects</description>
6
+
7
+ <project>
8
+ <name>checkstyle</name>
9
+ <type>git</type>
10
+ <connection>https://github.com/checkstyle/checkstyle</connection>
11
+ <tag>checkstyle-9.1</tag>
12
+
13
+ <exclude-pattern>.*/target/test-classes/com/puppycrawl/tools/checkstyle/.*</exclude-pattern>
14
+ <exclude-pattern>.*/target/generated-sources/.*</exclude-pattern>
15
+ <exclude-pattern>.*/src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/javaparser/InputJavaParserNoFreezeOnDeeplyNestedLambdas.java</exclude-pattern>
16
+
17
+ <build-command><![CDATA[#!/usr/bin/env bash
18
+ if test -e classpath.txt; then
19
+ exit
20
+ fi
21
+
22
+ set -e
23
+
24
+ # Make sure to use java11. This is already installed by build.yml/publish-snapshot.yml/publish-release.yml
25
+ export JAVA_HOME=${HOME}/openjdk11
26
+ export PATH=$JAVA_HOME/bin:$PATH
27
+
28
+ mvn test-compile -B
29
+ mvn dependency:build-classpath -DincludeScope=test -Dmdep.outputFile=classpath.txt -B
30
+ ]]></build-command>
31
+ <auxclasspath-command>echo -n "${HOME}/openjdk11/lib/jrt-fs.jar:$(pwd)/target/classes:$(pwd)/target/test-classes:"; cat classpath.txt</auxclasspath-command>
32
+ <cpd-options>
33
+ <language>java</language>
34
+ <minimum-tokens>50</minimum-tokens>
35
+ <max-memory>4g</max-memory>
36
+ <directories>
37
+ <!-- this avoids analyzing src/test/resources-noncompilable ... -->
38
+ <directory>src/main/java</directory>
39
+ <directory>src/test/java</directory>
40
+ </directories>
41
+ </cpd-options>
42
+ </project>
43
+
44
+ <project>
45
+ <name>spring-framework</name>
46
+ <type>git</type>
47
+ <connection>https://github.com/spring-projects/spring-framework</connection>
48
+ <tag>v5.3.13</tag>
49
+
50
+ <exclude-pattern>.*/build/generated-sources/.*</exclude-pattern>
51
+ <exclude-pattern>.*/build/resources/.*</exclude-pattern>
52
+
53
+ <build-command><![CDATA[#!/usr/bin/env bash
54
+ ## Skip gradle execution
55
+ if test -e classpath.txt; then
56
+ exit
57
+ fi
58
+
59
+ set -e
60
+
61
+ # Make sure to use java11. This is already installed by build.yml/publish-snapshot.yml/publish-release.yml
62
+ export JAVA_HOME=${HOME}/openjdk11
63
+ export PATH=$JAVA_HOME/bin:$PATH
64
+
65
+ ## Patches
66
+ # keep the tabs!!
67
+ # Patch 1: See https://github.com/spring-projects/spring-framework/commit/381b7d035a16d430b8783b7390c1677c9e7d1f68
68
+ # and https://github.com/spring-projects/spring-framework/commit/9e1ed6c7718d38c4b9fe5f75921abad33264307c
69
+ (cat <<EOF
70
+ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
71
+ index 37f5884e67..53022443ee 100644
72
+ --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
73
+ +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java
74
+ @@ -539,7 +539,9 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
75
+ */
76
+ @SuppressWarnings({"deprecation", "cast"})
77
+ protected boolean determineRequiredStatus(MergedAnnotation<?> ann) {
78
+ - return determineRequiredStatus(
79
+ + // Cast to (AnnotationAttributes) is required. Otherwise, the :spring-beans:compileGroovy
80
+ + // task fails in the Gradle build.
81
+ + return determineRequiredStatus((AnnotationAttributes)
82
+ ann.asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType())));
83
+ }
84
+
85
+ EOF
86
+ ) | patch --strip=1
87
+
88
+ # Patch 2: Ignore compiler warnings
89
+ (cat <<EOF
90
+ diff --git a/buildSrc/src/main/java/org/springframework/build/compile/CompilerConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/build/compile/CompilerConventionsPlugin.java
91
+ index f2424c549e..b6ec8b04da 100644
92
+ --- a/buildSrc/src/main/java/org/springframework/build/compile/CompilerConventionsPlugin.java
93
+ +++ b/buildSrc/src/main/java/org/springframework/build/compile/CompilerConventionsPlugin.java
94
+ @@ -51,7 +51,7 @@ public class CompilerConventionsPlugin implements Plugin<Project> {
95
+ COMPILER_ARGS.addAll(commonCompilerArgs);
96
+ COMPILER_ARGS.addAll(Arrays.asList(
97
+ "-Xlint:varargs", "-Xlint:fallthrough", "-Xlint:rawtypes", "-Xlint:deprecation",
98
+ - "-Xlint:unchecked", "-Werror"
99
+ + "-Xlint:unchecked"//, "-Werror"
100
+ ));
101
+ TEST_COMPILER_ARGS = new ArrayList<>();
102
+ TEST_COMPILER_ARGS.addAll(commonCompilerArgs);
103
+ diff --git a/spring-beans/spring-beans.gradle b/spring-beans/spring-beans.gradle
104
+ index e3f6f73b76..48c4d9e3fb 100644
105
+ --- a/spring-beans/spring-beans.gradle
106
+ +++ b/spring-beans/spring-beans.gradle
107
+ @@ -23,7 +23,7 @@ sourceSets {
108
+ }
109
+
110
+ compileGroovy {
111
+ - options.compilerArgs += "-Werror"
112
+ +// options.compilerArgs += "-Werror"
113
+ }
114
+
115
+ // This module also builds Kotlin code and the compileKotlin task naturally depends on
116
+ EOF
117
+ ) | patch --strip=1
118
+
119
+ # Patch 3: Add task createSquishClasspath
120
+ (cat <<EOF
121
+ diff --git a/build.gradle b/build.gradle
122
+ index 6021fa574d..15d29ed699 100644
123
+ --- a/build.gradle
124
+ +++ b/build.gradle
125
+ @@ -431,3 +431,19 @@ configure(rootProject) {
126
+ }
127
+ }
128
+ }
129
+ +
130
+ +// see https://stackoverflow.com/questions/28986968/generate-classpath-from-all-multiproject-gradle-build-dependencies
131
+ +task createSquishClasspath {
132
+ + doLast {
133
+ + def dependencies = new LinkedHashSet()
134
+ + dependencies.addAll(moduleProjects.configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.file.flatten())
135
+ + dependencies.addAll(moduleProjects.configurations.testCompileClasspath.resolvedConfiguration.resolvedArtifacts.file.flatten())
136
+ +
137
+ + def paths = new ArrayList()
138
+ + paths.addAll(moduleProjects.jar.outputs.files.asPath)
139
+ + paths.addAll(moduleProjects.sourceSets.test.output.resourcesDir)
140
+ + paths.addAll(moduleProjects.sourceSets.test.output.classesDirs.files.flatten())
141
+ + paths.addAll(dependencies)
142
+ + println paths.join(File.pathSeparator)
143
+ + }
144
+ +}
145
+ EOF
146
+ ) | patch --strip=1
147
+
148
+ # Patch 4: Add https://maven.repository.redhat.com/ga/ as repository in order to resolve
149
+ # dependency com.ibm.websphere/uow/6.0.2.17
150
+ # See https://spring.io/blog/2020/10/29/notice-of-permissions-changes-to-repo-spring-io-fall-and-winter-2020
151
+ (cat <<EOF
152
+ diff --git a/build.gradle b/build.gradle
153
+ index 6021fa57..8319ff76 100644
154
+ --- a/build.gradle
155
+ +++ b/build.gradle
156
+ @@ -291,6 +291,7 @@ configure(allprojects) { project ->
157
+ }
158
+ repositories {
159
+ mavenCentral()
160
+ + maven { url "https://maven.repository.redhat.com/ga/" }
161
+ maven { url "https://repo.spring.io/libs-spring-framework-build" }
162
+ }
163
+ }
164
+ EOF
165
+ ) | patch --strip=1
166
+
167
+ ./gradlew --console=plain --build-cache --no-daemon --max-workers=4 build testClasses -x test -x javadoc -x api -x asciidoctor -x asciidoctorPdf
168
+ ./gradlew --console=plain --build-cache --no-daemon --max-workers=4 createSquishClasspath -q > classpath.txt
169
+ ]]></build-command>
170
+ <auxclasspath-command>echo -n "${HOME}/openjdk11/lib/jrt-fs.jar:"; cat classpath.txt</auxclasspath-command>
171
+ <cpd-options>
172
+ <language>java</language>
173
+ <minimum-tokens>150</minimum-tokens>
174
+ <max-memory>4g</max-memory>
175
+ </cpd-options>
176
+ </project>
177
+
178
+ <project>
179
+ <name>openjdk-11</name>
180
+ <type>git</type>
181
+ <connection>https://github.com/openjdk/jdk</connection>
182
+ <tag>jdk-11+28</tag>
183
+ <src-subpath>src/java.base</src-subpath>
184
+ <auxclasspath-command>echo -n "${HOME}/openjdk11/lib/jrt-fs.jar"</auxclasspath-command>
185
+ <cpd-options>
186
+ <language>java</language>
187
+ <minimum-tokens>150</minimum-tokens>
188
+ <max-memory>4g</max-memory>
189
+ <directories>
190
+ <directory>src/java.base</directory>
191
+ </directories>
192
+ </cpd-options>
193
+ </project>
194
+
195
+ <project>
196
+ <name>Schedul-o-matic-9000</name>
197
+ <type>git</type>
198
+ <connection>https://github.com/SalesforceLabs/Schedul-o-matic-9000</connection>
199
+ <tag>6b1229ba43b38931fbbab5924bc9b9611d19a786</tag>
200
+ <cpd-options>
201
+ <language>apex</language>
202
+ <minimum-tokens>100</minimum-tokens>
203
+ <max-memory>512m</max-memory>
204
+ </cpd-options>
205
+ </project>
206
+
207
+ <project>
208
+ <name>fflib-apex-common</name>
209
+ <type>git</type>
210
+ <connection>https://github.com/apex-enterprise-patterns/fflib-apex-common</connection>
211
+ <tag>7e0891efb86d23de62811af56d87d0959082a322</tag>
212
+ <cpd-options>
213
+ <language>apex</language>
214
+ <minimum-tokens>100</minimum-tokens>
215
+ <max-memory>512m</max-memory>
216
+ </cpd-options>
217
+ </project>
218
+
219
+ <project>
220
+ <name>apex-link</name>
221
+ <type>git</type>
222
+ <connection>https://github.com/nawforce/apex-link</connection>
223
+ <tag>v2.3.0</tag>
224
+ <src-subpath>samples</src-subpath>
225
+ <cpd-options>
226
+ <language>apex</language>
227
+ <minimum-tokens>100</minimum-tokens>
228
+ <max-memory>512m</max-memory>
229
+ </cpd-options>
230
+ </project>
231
+
232
+ <project>
233
+ <name>java-regression-tests</name>
234
+ <type>git</type>
235
+ <connection>https://github.com/pmd/java-regression-tests</connection>
236
+ <tag>main</tag>
237
+ <auxclasspath-command>realpath java-regression-tests-*.jar</auxclasspath-command>
238
+ <cpd-options>
239
+ <language>java</language>
240
+ <minimum-tokens>100</minimum-tokens>
241
+ <max-memory>512m</max-memory>
242
+ </cpd-options>
243
+ </project>
244
+
245
+ <project>
246
+ <name>OracleDBUtils</name>
247
+ <type>git</type>
248
+ <connection>https://github.com/Qualtagh/OracleDBUtils</connection>
249
+ <tag>0513fe6b053b31e6c09ac6f86eb2064733ecf32d</tag>
250
+ <cpd-options>
251
+ <language>plsql</language>
252
+ <minimum-tokens>100</minimum-tokens>
253
+ <max-memory>512m</max-memory>
254
+ </cpd-options>
255
+ </project>
256
+
257
+ <project>
258
+ <name>alsa-firmware</name>
259
+ <type>git</type>
260
+ <connection>https://github.com/alsa-project/alsa-firmware</connection>
261
+ <tag>v1.2.1</tag>
262
+ <cpd-options>
263
+ <language>cpp</language>
264
+ <minimum-tokens>100</minimum-tokens>
265
+ <max-memory>4g</max-memory>
266
+ </cpd-options>
267
+ </project>
268
+ </projectlist>
@@ -1,5 +1,5 @@
1
1
  <?xml version="1.0" ?>
2
- <!-- version 1.1.0 -->
2
+ <!-- version 1.2.0 -->
3
3
  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
4
4
  <xs:element name="projectlist">
5
5
  <xs:complexType>
@@ -0,0 +1,53 @@
1
+ <?xml version="1.0" ?>
2
+ <!-- version 1.3.0 -->
3
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
4
+ <xs:element name="projectlist">
5
+ <xs:complexType>
6
+ <xs:sequence>
7
+ <xs:element name="description" type="xs:string" minOccurs="1" maxOccurs="1"/>
8
+ <xs:element name="project" type="project" minOccurs="1" maxOccurs="unbounded"/>
9
+ </xs:sequence>
10
+ </xs:complexType>
11
+ </xs:element>
12
+ <xs:complexType name="project">
13
+ <xs:sequence>
14
+ <xs:element name="name" type="xs:string" minOccurs="1" maxOccurs="1"/>
15
+ <xs:element name="type" minOccurs="1" maxOccurs="1">
16
+ <xs:simpleType>
17
+ <xs:restriction base="xs:string">
18
+ <xs:enumeration value="git"/>
19
+ </xs:restriction>
20
+ </xs:simpleType>
21
+ </xs:element>
22
+ <xs:element name="connection" type="xs:string" minOccurs="1" maxOccurs="1"/>
23
+ <xs:element name="webview-url" type="xs:string" minOccurs="0" maxOccurs="1"/>
24
+ <xs:element name="tag" type="xs:string" minOccurs="0" maxOccurs="1"/>
25
+ <xs:element name="src-subpath" type="xs:string" minOccurs="0" maxOccurs="1" default=".">
26
+ <xs:annotation>
27
+ <xs:documentation>
28
+ Value of the -dir option for the PMD run.
29
+ The value must be a directory path relative to the root directory of the clone.
30
+ Defaults to just '.', ie the entire directory.
31
+ </xs:documentation>
32
+ </xs:annotation>
33
+ </xs:element>
34
+ <xs:element name="exclude-pattern" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
35
+ <xs:element name="build-command" type="xs:string" minOccurs="0" maxOccurs="1"/>
36
+ <xs:element name="auxclasspath-command" type="xs:string" minOccurs="0" maxOccurs="1"/>
37
+ <xs:element name="cpd-options" type="cpd-options" minOccurs="0" maxOccurs="1"/>
38
+ </xs:sequence>
39
+ </xs:complexType>
40
+ <xs:complexType name="cpd-options">
41
+ <xs:sequence>
42
+ <xs:element name="language" type="xs:string" minOccurs="0" maxOccurs="1" default="java"/>
43
+ <xs:element name="minimum-tokens" type="xs:integer" minOccurs="0" maxOccurs="1" default="100"/>
44
+ <xs:element name="max-memory" type="xs:string" minOccurs="0" maxOccurs="1" default="512m"/>
45
+ <xs:element name="directories" type="directories" minOccurs="0" maxOccurs="1"/>
46
+ </xs:sequence>
47
+ </xs:complexType>
48
+ <xs:complexType name="directories">
49
+ <xs:sequence>
50
+ <xs:element name="directory" type="xs:string" minOccurs="1" maxOccurs="unbounded"/>
51
+ </xs:sequence>
52
+ </xs:complexType>
53
+ </xs:schema>
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PmdTester
4
+ # Turn a CPD project report into a hash that can be rendered somewhere else
5
+ module CpdProjectHasher
6
+ include PmdTester
7
+
8
+ def cpd_report_diff_to_h(cpd_rdiff)
9
+ {
10
+ 'duplication_counts' => cpd_rdiff.duplication_counts.to_h.transform_keys(&:to_s),
11
+ 'error_counts' => cpd_rdiff.error_counts.to_h.transform_keys(&:to_s),
12
+
13
+ 'base_details' => cpd_rdiff.base_report.report_details.to_h,
14
+ 'patch_details' => cpd_rdiff.patch_report.report_details.to_h,
15
+ 'diff_execution_time' => PmdReportDetail.convert_seconds(cpd_rdiff.patch_report.report_details.execution_time -
16
+ cpd_rdiff.base_report.report_details.execution_time)
17
+ }
18
+ end
19
+
20
+ def duplications_to_hash(project, duplications, is_diff)
21
+ filename_index = {}
22
+ duplications.each do |d|
23
+ d.files.each do |f|
24
+ local_path = project.get_local_path(f.path)
25
+ filename_index[local_path] = filename_index.size unless filename_index.include?(local_path)
26
+ end
27
+ end
28
+
29
+ duplications_table = duplications.map do |d|
30
+ make_duplication_table(project, filename_index, d, is_diff)
31
+ end
32
+
33
+ {
34
+ 'file_index' => filename_index.keys,
35
+ 'duplications' => duplications_table
36
+ }
37
+ end
38
+
39
+ def cpd_errors_to_h(project)
40
+ errors = project.cpd_report_diff.error_diffs
41
+ errors.map { |e| error_to_hash(e, project) }
42
+ end
43
+
44
+ private
45
+
46
+ def make_duplication_table(project, filename_index, duplication, is_diff)
47
+ locations = duplication.files.map do |f|
48
+ [filename_index[project.get_local_path(f.path)], f.location.beginline, f.location.endline, f.location.to_s]
49
+ end
50
+ type = if !is_diff || duplication.added?
51
+ '+'
52
+ elsif duplication.changed?
53
+ '~'
54
+ else
55
+ '-'
56
+ end
57
+ [locations, duplication.lines, duplication.tokens, duplication.codefragment,
58
+ type] + generate_old_duplication_info(project, filename_index, duplication, is_diff)
59
+ end
60
+
61
+ def generate_old_duplication_info(project, filename_index, duplication, is_diff)
62
+ return [] unless is_diff && duplication.changed?
63
+
64
+ old_files = duplication.old_files.map do |f|
65
+ [filename_index[project.get_local_path(f.path)], f.location.beginline, f.location.endline, f.location.to_s]
66
+ end
67
+ [duplication.old_lines, duplication.old_tokens, old_files]
68
+ end
69
+ end
70
+ end
@@ -12,7 +12,7 @@ module PmdTester
12
12
  def render_liquid(template_path, env)
13
13
  to_render = File.read(ResourceLocator.resource(template_path))
14
14
  includes = Liquid::LocalFileSystem.new(ResourceLocator.resource('_includes'), '%s.html')
15
- Liquid::Template.file_system = includes
15
+ Liquid::Environment.default.file_system = includes
16
16
  template = Liquid::Template.parse(to_render, error_mode: :strict)
17
17
  template.render!(env, { strict_variables: true })
18
18
  end
@@ -43,6 +43,7 @@ module PmdTester
43
43
  class LiquidProjectRenderer
44
44
  include PmdTester
45
45
  include ProjectHasher
46
+ include CpdProjectHasher
46
47
  include LiquidRenderer
47
48
 
48
49
  def write_project_index(project, root)
@@ -50,28 +51,65 @@ module PmdTester
50
51
  'diff' => report_diff_to_h(project.report_diff),
51
52
  'error_diffs' => errors_to_h(project),
52
53
  'configerror_diffs' => configerrors_to_h(project),
54
+ 'cpd_diff' => cpd_report_diff_to_h(project.cpd_report_diff),
55
+ 'cpd_error_diffs' => cpd_errors_to_h(project),
53
56
  'project_name' => project.name
54
57
  }
55
58
 
56
59
  # Renders index.html using liquid
57
60
  write_file("#{root}/index.html", render_liquid('project_diff_report.html', liquid_env))
61
+ write_pmd_diff_report(project, root)
62
+ write_pmd_full_report(project, root)
63
+ write_cpd_diff_report(project, root)
64
+ write_cpd_full_report(project, root)
65
+ end
66
+
67
+ private
68
+
69
+ def write_pmd_diff_report(project, root)
58
70
  # generate array of violations in json
59
- write_file("#{root}/project_data.js", dump_violations_json(project))
71
+ write_file("#{root}/diff_pmd_data.js", dump_violations_json(project))
60
72
  # copy original pmd reports
61
73
  copy_file("#{root}/base_pmd_report.xml", project.report_diff.base_report.file)
62
74
  copy_file("#{root}/patch_pmd_report.xml", project.report_diff.patch_report.file)
63
- # copy stdout and stderr outputs
64
- copy_file("#{root}/base_stdout.txt", "#{project.report_diff.base_report.report_folder}/stdout.txt")
65
- copy_file("#{root}/base_stderr.txt", "#{project.report_diff.base_report.report_folder}/stderr.txt")
66
- copy_file("#{root}/patch_stdout.txt", "#{project.report_diff.patch_report.report_folder}/stdout.txt")
67
- copy_file("#{root}/patch_stderr.txt", "#{project.report_diff.patch_report.report_folder}/stderr.txt")
75
+ write_pmd_stdout_stderr(root, project.report_diff)
76
+ copy_file("#{root}/base_pmd_recording.jfr",
77
+ project.report_diff.base_report.report_details.jfr_summary.recording_path)
78
+ copy_file("#{root}/patch_pmd_recording.jfr",
79
+ project.report_diff.patch_report.report_details.jfr_summary.recording_path)
80
+ end
81
+
82
+ def write_pmd_full_report(project, root)
68
83
  # render full pmd reports
69
84
  write_file("#{root}/base_pmd_report.html",
70
85
  render_liquid('project_pmd_report.html', pmd_report_liquid_env(project, BASE)))
71
- write_file("#{root}/base_data.js", dump_violations_json(project, BASE))
86
+ write_file("#{root}/base_pmd_data.js", dump_violations_json(project, BASE))
72
87
  write_file("#{root}/patch_pmd_report.html",
73
88
  render_liquid('project_pmd_report.html', pmd_report_liquid_env(project, PATCH)))
74
- write_file("#{root}/patch_data.js", dump_violations_json(project, PATCH))
89
+ write_file("#{root}/patch_pmd_data.js", dump_violations_json(project, PATCH))
90
+ end
91
+
92
+ def write_cpd_diff_report(project, root)
93
+ # generate array of cpd duplications in json
94
+ write_file("#{root}/diff_cpd_data.js", dump_cpd_duplications_json(project, 'diff'))
95
+ # copy original cpd reports
96
+ copy_file("#{root}/base_cpd_report.xml", project.cpd_report_diff.base_report.file)
97
+ copy_file("#{root}/patch_cpd_report.xml", project.cpd_report_diff.patch_report.file)
98
+ write_cpd_stdout_stderr(root, project.cpd_report_diff)
99
+ copy_file("#{root}/base_cpd_recording.jfr",
100
+ project.cpd_report_diff.base_report.report_details.jfr_summary.recording_path)
101
+ copy_file("#{root}/patch_cpd_recording.jfr",
102
+ project.cpd_report_diff.patch_report.report_details.jfr_summary.recording_path)
103
+ end
104
+
105
+ def write_cpd_full_report(project, root)
106
+ # render full cpd reports
107
+ write_file("#{root}/base_cpd_report.html",
108
+ render_liquid('project_cpd_report.html', cpd_report_liquid_env(project, BASE)))
109
+ write_file("#{root}/base_cpd_data.js", dump_cpd_duplications_json(project, BASE))
110
+ write_file("#{root}/patch_cpd_report.html",
111
+ render_liquid('project_cpd_report.html', cpd_report_liquid_env(project, PATCH)))
112
+ write_file("#{root}/patch_cpd_data.js", dump_cpd_duplications_json(project, PATCH))
75
113
  end
76
114
 
77
115
  def dump_violations_json(project, branch = 'diff')
@@ -89,18 +127,47 @@ module PmdTester
89
127
  **violations_to_hash(project, violations_by_file, branch == 'diff')
90
128
  }
91
129
 
92
- project_data = JSON.fast_generate(h, indent: ' ', object_nl: "\n", array_nl: "\n")
93
- "let project = #{project_data}"
130
+ project_data = JSON.generate(h, object_nl: "\n")
131
+ "let pmd_report = #{project_data}"
94
132
  end
95
133
 
96
- private
134
+ def dump_cpd_duplications_json(project, branch)
135
+ duplications = if branch == BASE
136
+ project.cpd_report_diff.base_report.duplications
137
+ elsif branch == PATCH
138
+ project.cpd_report_diff.patch_report.duplications
139
+ else
140
+ project.cpd_report_diff.duplication_diffs
141
+ end
142
+ h = {
143
+ 'source_link_base' => project.webview_url,
144
+ 'source_link_template' => link_template(project),
145
+ **duplications_to_hash(project, duplications, branch == 'diff')
146
+ }
147
+
148
+ "let cpd_report = #{JSON.generate(h, object_nl: "\n")}"
149
+ end
150
+
151
+ def write_pmd_stdout_stderr(root, report_diff)
152
+ write_file("#{root}/base_pmd_stdout.txt", report_diff.base_report.report_details.stdout)
153
+ write_file("#{root}/base_pmd_stderr.txt", report_diff.base_report.report_details.stderr)
154
+ write_file("#{root}/patch_pmd_stdout.txt", report_diff.patch_report.report_details.stdout)
155
+ write_file("#{root}/patch_pmd_stderr.txt", report_diff.patch_report.report_details.stderr)
156
+ end
157
+
158
+ def write_cpd_stdout_stderr(root, cpd_report_diff)
159
+ write_file("#{root}/base_cpd_stdout.txt", cpd_report_diff.base_report.report_details.stdout)
160
+ write_file("#{root}/base_cpd_stderr.txt", cpd_report_diff.base_report.report_details.stderr)
161
+ write_file("#{root}/patch_cpd_stdout.txt", cpd_report_diff.patch_report.report_details.stdout)
162
+ write_file("#{root}/patch_cpd_stderr.txt", cpd_report_diff.patch_report.report_details.stderr)
163
+ end
97
164
 
98
165
  def copy_file(target_file, source_file)
99
166
  if File.exist? source_file
100
167
  FileUtils.cp(source_file, target_file)
101
168
  logger&.info "Written #{target_file}"
102
169
  else
103
- logger&.warn "File #{source_file} not found"
170
+ logger&.warn "Cannot write #{target_file}. Source file #{source_file} not found"
104
171
  end
105
172
  end
106
173
 
@@ -117,20 +184,48 @@ module PmdTester
117
184
  }
118
185
  end
119
186
 
187
+ def cpd_report_liquid_env(project, branch)
188
+ report = if branch == BASE
189
+ project.cpd_report_diff.base_report
190
+ else
191
+ project.cpd_report_diff.patch_report
192
+ end
193
+ {
194
+ 'project_name' => project.name,
195
+ 'branch' => branch,
196
+ 'cpd_report' => cpd_report_to_h(project, report)
197
+ }
198
+ end
199
+
120
200
  def report_to_h(project, report)
121
201
  {
122
202
  'violation_counts' => report.violations_by_file.total_size,
123
203
  'error_counts' => report.errors_by_file.total_size,
124
204
  'configerror_counts' => report.configerrors_by_rule.values.flatten.length,
125
205
 
126
- 'execution_time' => PmdReportDetail.convert_seconds(report.exec_time),
127
- 'timestamp' => report.timestamp,
128
- 'exit_code' => report.exit_code,
206
+ 'execution_time' => report.report_details.execution_time_formatted,
207
+ 'timestamp' => report.report_details.timestamp,
208
+ 'exit_code' => report.report_details.exit_code,
209
+ 'jfr_summary' => report.report_details.jfr_summary.to_h_for_liquid,
129
210
 
130
211
  'rules' => report.rule_summaries,
131
212
  'errors' => report.errors_by_file.all_values.map { |e| error_to_hash(e, project) },
132
213
  'configerrors' => report.configerrors_by_rule.values.flatten.map { |e| configerror_to_hash(e) }
133
214
  }
134
215
  end
216
+
217
+ def cpd_report_to_h(project, cpd_report)
218
+ {
219
+ 'duplication_counts' => cpd_report.duplications.length,
220
+ 'error_counts' => cpd_report.errors.length,
221
+
222
+ 'execution_time' => cpd_report.report_details.execution_time_formatted,
223
+ 'timestamp' => cpd_report.report_details.timestamp,
224
+ 'exit_code' => cpd_report.report_details.exit_code,
225
+ 'jfr_summary' => cpd_report.report_details.jfr_summary.to_h_for_liquid,
226
+
227
+ 'errors' => cpd_report.errors.map { |e| error_to_hash(e, project) }
228
+ }
229
+ end
135
230
  end
136
231
  end