pmdtester 1.6.2 → 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 +7 -7
  4. data/.github/workflows/manual-integration-tests.yml +7 -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 +47 -0
  10. data/Manifest.txt +13 -0
  11. data/README.rdoc +76 -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 +133 -37
  20. data/lib/pmdtester/builders/project_hasher.rb +24 -25
  21. data/lib/pmdtester/builders/rule_set_builder.rb +2 -0
  22. data/lib/pmdtester/builders/summary_report_builder.rb +6 -1
  23. data/lib/pmdtester/cmd.rb +16 -7
  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,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PmdTester
4
+ # This class represents a location in a source file
5
+ class Location
6
+ attr_reader :beginline, :endline, :begincolumn, :endcolumn
7
+
8
+ def initialize(beginline:, endline:, begincolumn:, endcolumn:)
9
+ @beginline = beginline
10
+ @endline = endline
11
+ @begincolumn = begincolumn
12
+ @endcolumn = endcolumn
13
+ end
14
+
15
+ def eql?(other)
16
+ beginline == other.beginline &&
17
+ endline == other.endline &&
18
+ begincolumn == other.begincolumn &&
19
+ endcolumn == other.endcolumn
20
+ end
21
+
22
+ def hash
23
+ [beginline, endline, begincolumn, endcolumn].hash
24
+ end
25
+
26
+ def to_s
27
+ if beginline == endline
28
+ if begincolumn == endcolumn
29
+ "#{beginline}:#{begincolumn}"
30
+ else
31
+ "#{beginline}:#{begincolumn}-#{endcolumn}"
32
+ end
33
+ else
34
+ "#{beginline}:#{begincolumn}-#{endline}:#{endcolumn}"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ module PmdTester
5
+ # Parses a CPD report XML file into a set of duplications and errors
6
+ class CpdReportDocument < Nokogiri::XML::SAX::Document
7
+ attr_reader :duplications, :errors
8
+
9
+ def initialize(branch_name, working_dir)
10
+ super()
11
+ @duplications = []
12
+ @errors = []
13
+
14
+ @working_dir = working_dir
15
+ @branch_name = branch_name
16
+
17
+ @current_duplication = nil
18
+ @current_error = nil
19
+ @cur_text = String.new(capacity: 200)
20
+ end
21
+
22
+ def parse(file_path)
23
+ parser = Nokogiri::XML::SAX::Parser.new(self)
24
+ parser.parse(File.open(file_path)) if File.exist?(file_path)
25
+ self
26
+ end
27
+
28
+ def start_element(name, attrs = [])
29
+ attrs = attrs.to_h
30
+
31
+ case name
32
+ when 'duplication'
33
+ handle_start_duplication attrs
34
+ when 'file'
35
+ handle_start_file attrs
36
+ when 'codefragment'
37
+ handle_start_codefragment
38
+ when 'error'
39
+ handle_start_error attrs
40
+ end
41
+ end
42
+
43
+ def characters(string)
44
+ @cur_text << string
45
+ end
46
+
47
+ def cdata_block(string)
48
+ @cur_text << string
49
+ end
50
+
51
+ def end_element(name)
52
+ case name
53
+ when 'duplication'
54
+ handle_end_duplication
55
+ when 'codefragment'
56
+ handle_end_codefragment
57
+ when 'error'
58
+ handle_end_error
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ # Modifies the string in place and returns it
65
+ # (this is what sub! does, except it returns nil if no replacement occurred)
66
+ def remove_work_dir!(str)
67
+ str.sub!(%r{#{@working_dir}/}, '')
68
+ str
69
+ end
70
+
71
+ def finish_text!
72
+ remove_work_dir!(@cur_text)
73
+ res = @cur_text.dup.freeze
74
+ @cur_text.clear
75
+ res
76
+ end
77
+
78
+ def handle_start_duplication(attrs)
79
+ @current_duplication = { lines: attrs['lines'].to_i,
80
+ tokens: attrs['tokens'].to_i,
81
+ files: [],
82
+ codefragment: '',
83
+ branch: @branch_name }
84
+ end
85
+
86
+ def handle_end_duplication
87
+ @duplications << Duplication.new(
88
+ lines: @current_duplication[:lines],
89
+ tokens: @current_duplication[:tokens],
90
+ files: @current_duplication[:files],
91
+ codefragment: @current_duplication[:codefragment],
92
+ branch: @current_duplication[:branch]
93
+ )
94
+ @current_duplication = nil
95
+ end
96
+
97
+ def handle_start_file(attrs)
98
+ return if @current_duplication.nil?
99
+
100
+ file_info = DuplicationFileInfo.new(
101
+ path: remove_work_dir!(attrs['path']),
102
+ location: Location.new(
103
+ beginline: attrs['line'].to_i,
104
+ endline: attrs['endline'].to_i,
105
+ begincolumn: attrs['column'].to_i,
106
+ endcolumn: attrs['endcolumn'].to_i
107
+ ),
108
+ begintoken: attrs['begintoken'].to_i,
109
+ endtoken: attrs['endtoken'].to_i
110
+ )
111
+ @current_duplication[:files] << file_info
112
+ end
113
+
114
+ def handle_start_codefragment
115
+ @cur_text.clear
116
+ end
117
+
118
+ def handle_end_codefragment
119
+ @current_duplication[:codefragment] = finish_text!
120
+ end
121
+
122
+ def handle_start_error(attrs)
123
+ @current_error = PmdError.new(
124
+ filename: remove_work_dir!(attrs['filename']),
125
+ short_message: remove_work_dir!(attrs['msg']),
126
+ branch: @branch_name
127
+ )
128
+ @cur_text.clear
129
+ end
130
+
131
+ def handle_end_error
132
+ @current_error.stack_trace = finish_text!
133
+ @errors << @current_error
134
+ @current_error = nil
135
+ end
136
+ end
137
+
138
+ # Represents a duplication entry in a CPD report
139
+ class Duplication
140
+ attr_reader :lines, :tokens, :files, :codefragment, :branch, :old_lines, :old_tokens, :old_files, :old_codefragment
141
+
142
+ def initialize(lines:, tokens:, files:, codefragment:, branch:)
143
+ @lines = lines
144
+ @tokens = tokens
145
+ @files = files
146
+ @codefragment = codefragment
147
+ @branch = branch
148
+
149
+ @changed = false
150
+ @old_lines = nil
151
+ @old_tokens = nil
152
+ @old_files = nil
153
+ @old_codefragment = nil
154
+ end
155
+
156
+ def eql?(other)
157
+ return false unless other.is_a?(Duplication)
158
+
159
+ lines == other.lines &&
160
+ tokens == other.tokens &&
161
+ files.eql?(other.files) &&
162
+ codefragment == other.codefragment
163
+ end
164
+
165
+ def hash
166
+ [lines, tokens, files, codefragment].hash
167
+ end
168
+
169
+ def try_merge?(other)
170
+ if branch != BASE && branch != other.branch &&
171
+ !changed? && # not already changed
172
+ same_or_similar_locations?(other.files)
173
+ @changed = true
174
+ @old_lines = other.lines
175
+ @old_tokens = other.tokens
176
+ @old_files = other.files
177
+ @old_codefragment = other.codefragment
178
+ true
179
+ else
180
+ false
181
+ end
182
+ end
183
+
184
+ # only makes sense if this is a diff
185
+ def added?
186
+ branch != BASE && !changed?
187
+ end
188
+
189
+ # only makes sense if this is a diff
190
+ def changed?
191
+ @changed
192
+ end
193
+
194
+ # only makes sense if this is a diff
195
+ def removed?
196
+ branch == BASE
197
+ end
198
+
199
+ private
200
+
201
+ def same_or_similar_locations?(other_files)
202
+ files.each do |file_info|
203
+ other_files.each do |other_file_info|
204
+ return true if file_info.path == other_file_info.path &&
205
+ (file_info.location.eql?(other_file_info.location) ||
206
+ location_move?(file_info.location, other_file_info.location))
207
+ end
208
+ end
209
+ false
210
+ end
211
+
212
+ def location_move?(this_location, other_location)
213
+ (this_location.beginline - other_location.beginline).abs <= 5
214
+ end
215
+ end
216
+
217
+ # Represents a single file location of a duplication in a CPD report
218
+ class DuplicationFileInfo
219
+ attr_reader :path, :location, :begintoken, :endtoken
220
+
221
+ def initialize(path:, location:, begintoken:, endtoken:)
222
+ @path = path
223
+ @location = location
224
+ @begintoken = begintoken
225
+ @endtoken = endtoken
226
+ end
227
+
228
+ def eql?(other)
229
+ return false unless other.is_a?(DuplicationFileInfo)
230
+
231
+ path == other.path &&
232
+ location.eql?(other.location) &&
233
+ begintoken == other.begintoken &&
234
+ endtoken == other.endtoken
235
+ end
236
+
237
+ def hash
238
+ [path, location, begintoken, endtoken].hash
239
+ end
240
+ end
241
+ end
@@ -5,6 +5,7 @@ require 'slop'
5
5
  module PmdTester
6
6
  class MissRequiredOptionError < StandardError; end
7
7
  class InvalidModeError < StandardError; end
8
+ class InvalidOptionError < StandardError; end
8
9
 
9
10
  # The Options is a class responsible of parsing all the
10
11
  # command line options
@@ -36,6 +37,8 @@ module PmdTester
36
37
  attr_reader :keep_reports
37
38
  attr_reader :error_recovery
38
39
  attr_reader :baseline_download_url_prefix
40
+ attr_reader :run_cpd
41
+ attr_reader :run_pmd
39
42
 
40
43
  def initialize(argv)
41
44
  options = parse(argv)
@@ -56,6 +59,8 @@ module PmdTester
56
59
  @keep_reports = options.keep_reports?
57
60
  @error_recovery = options.error_recovery?
58
61
  url = options[:baseline_download_url]
62
+ @run_cpd = !options.no_cpd?
63
+ @run_pmd = !options.no_pmd?
59
64
  @baseline_download_url_prefix = if url[-1] == '/'
60
65
  url
61
66
  else
@@ -116,6 +121,10 @@ module PmdTester
116
121
  o.string '--baseline-download-url',
117
122
  'download url prefix from where to download the baseline in online mode',
118
123
  default: DEFAULT_BASELINE_URL_PREFIX
124
+ o.bool '--no-cpd', 'do not execute CPD (Copy Paste Detector) and compare duplications; only execute PMD',
125
+ default: false
126
+ o.bool '--no-pmd', 'do not execute PMD and compare rule violations; only execute CPD (Copy Paste Detector)',
127
+ default: false
119
128
  o.on '-v', '--version' do
120
129
  puts VERSION
121
130
  exit
@@ -129,6 +138,7 @@ module PmdTester
129
138
 
130
139
  def check_options
131
140
  check_common_options
141
+ check_pmd_cpd_options
132
142
  case @mode
133
143
  when LOCAL
134
144
  check_local_options
@@ -164,6 +174,15 @@ module PmdTester
164
174
  check_option(ANY, 'patch branch name', @patch_branch)
165
175
  end
166
176
 
177
+ def check_pmd_cpd_options
178
+ return unless !@run_pmd && !@run_cpd
179
+
180
+ msg = 'Both "--no-cpd" and "--no-pmd" are given. At least one of PMD and CPD must be executed. ' \
181
+ 'Please check your options.'
182
+ logger.error msg
183
+ raise InvalidOptionError, msg
184
+ end
185
+
167
186
  def check_option(mode, option_name, option)
168
187
  if option.nil?
169
188
  msg = "#{option_name} is required in #{mode} mode."
@@ -10,6 +10,7 @@ module PmdTester
10
10
  attr_reader :configerrors
11
11
 
12
12
  def initialize(branch_name, working_dir, filter_set = nil)
13
+ super()
13
14
  @violations = CollectionByFile.new
14
15
  @errors = CollectionByFile.new
15
16
  @configerrors = Hash.new { |hash, key| hash[key] = [] }
@@ -25,6 +26,12 @@ module PmdTester
25
26
  @cur_text = String.new(capacity: 200)
26
27
  end
27
28
 
29
+ def parse(file_path)
30
+ parser = Nokogiri::XML::SAX::Parser.new(self)
31
+ parser.parse(File.open(file_path)) if File.exist?(file_path)
32
+ self
33
+ end
34
+
28
35
  def start_element(name, attrs = [])
29
36
  attrs = attrs.to_h
30
37
 
@@ -109,7 +116,13 @@ module PmdTester
109
116
  info_url: attrs['externalInfoUrl'],
110
117
  bline: attrs['beginline'].to_i,
111
118
  rule_name: attrs['rule'],
112
- ruleset_name: attrs['ruleset'].freeze
119
+ ruleset_name: attrs['ruleset'].freeze,
120
+ location: Location.new(
121
+ beginline: attrs['beginline'].to_i,
122
+ endline: attrs['endline'].to_i,
123
+ begincolumn: attrs['begincolumn'].to_i,
124
+ endcolumn: attrs['endcolumn'].to_i
125
+ )
113
126
  )
114
127
  end
115
128
 
@@ -17,7 +17,7 @@ module PmdTester
17
17
  end
18
18
 
19
19
  def schema_file_path
20
- ResourceLocator.locate('config/projectlist_1_2_0.xsd')
20
+ ResourceLocator.locate('config/projectlist_1_3_0.xsd')
21
21
  end
22
22
  end
23
23
 
@@ -16,6 +16,9 @@ module PmdTester
16
16
  attr_accessor :execution_time
17
17
  attr_accessor :jdk_version
18
18
  attr_accessor :language
19
+ attr_accessor :cpu_info
20
+ attr_accessor :physical_memory
21
+ attr_accessor :os_info
19
22
  attr_accessor :pull_request
20
23
 
21
24
  def self.branch_filename(branch_name)
@@ -33,6 +36,10 @@ module PmdTester
33
36
  # the result of command 'java -version' is going to stderr
34
37
  @jdk_version = Cmd.stderr_of('java -version')
35
38
  @language = ENV.fetch('LANG') # the locale
39
+ system_info = PmdTester::SystemInfo.new
40
+ @cpu_info = system_info.cpu_info
41
+ @physical_memory = system_info.physical_memory
42
+ @os_info = system_info.uname
36
43
 
37
44
  prnum = ENV.fetch(PR_NUM_ENV_VAR, 'false')
38
45
  @pull_request = prnum == 'false' ? nil : prnum
@@ -41,19 +48,14 @@ module PmdTester
41
48
  def self.load(branch_name, logger)
42
49
  details = PmdBranchDetail.new(branch_name)
43
50
  if File.exist?(details.path_to_save_file)
44
- hash = JSON.parse(File.read(details.path_to_save_file))
45
- details.branch_last_sha = hash['branch_last_sha']
46
- details.branch_last_message = hash['branch_last_message']
47
- details.branch_name = hash['branch_name']
48
- details.timestamp = hash['timestamp']
49
- details.execution_time = hash['execution_time']
50
- details.jdk_version = hash['jdk_version']
51
- details.language = hash['language']
52
- details.pull_request = hash['pull_request']
51
+ details.parse_from_json
53
52
  else
54
53
  details.timestamp = Time.now
55
54
  details.jdk_version = ''
56
55
  details.language = ''
56
+ details.cpu_info = ''
57
+ details.physical_memory = ''
58
+ details.os_info = ''
57
59
  logger&.warn "#{details.path_to_save_file} doesn't exist!"
58
60
  end
59
61
  details
@@ -66,6 +68,9 @@ module PmdTester
66
68
  timestamp: @timestamp,
67
69
  execution_time: @execution_time,
68
70
  jdk_version: @jdk_version,
71
+ cpu_info: @cpu_info,
72
+ physical_memory: @physical_memory,
73
+ os_info: @os_info,
69
74
  language: @language,
70
75
  pull_request: @pull_request }
71
76
 
@@ -92,5 +97,20 @@ module PmdTester
92
97
  def format_execution_time
93
98
  PmdReportDetail.convert_seconds(@execution_time)
94
99
  end
100
+
101
+ def parse_from_json
102
+ hash = JSON.parse(File.read(path_to_save_file))
103
+ @branch_last_sha = hash['branch_last_sha']
104
+ @branch_last_message = hash['branch_last_message']
105
+ @branch_name = hash['branch_name']
106
+ @timestamp = hash['timestamp']
107
+ @execution_time = hash['execution_time']
108
+ @jdk_version = hash['jdk_version']
109
+ @language = hash['language']
110
+ @cpu_info = hash['cpu_info']
111
+ @physical_memory = hash['physical_memory']
112
+ @os_info = hash['os_info']
113
+ @pull_request = hash['pull_request']
114
+ end
95
115
  end
96
116
  end
@@ -3,29 +3,32 @@
3
3
  require 'json'
4
4
 
5
5
  module PmdTester
6
- # This class represents all details about report of pmd
6
+ # This class represents all details about an execution of PMD
7
7
  class PmdReportDetail
8
8
  attr_accessor :execution_time
9
9
  attr_accessor :timestamp
10
10
  attr_accessor :working_dir
11
+ attr_accessor :cmdline
11
12
  attr_accessor :exit_code
12
-
13
- def initialize(execution_time: 0, timestamp: '', working_dir: Dir.getwd, exit_code: nil)
14
- @execution_time = execution_time
15
- @timestamp = timestamp
16
- @working_dir = working_dir
17
- @exit_code = exit_code.nil? ? '?' : exit_code.to_s
18
- end
13
+ attr_accessor :stdout
14
+ attr_accessor :stderr
15
+ attr_accessor :oom
16
+ attr_accessor :jfr_summary
19
17
 
20
18
  def save(report_info_path)
21
19
  hash = {
22
20
  execution_time: @execution_time,
23
21
  timestamp: @timestamp,
24
22
  working_dir: @working_dir,
25
- exit_code: @exit_code
23
+ cmdline: @cmdline,
24
+ exit_code: @exit_code,
25
+ stdout: @stdout,
26
+ stderr: @stderr,
27
+ oom: @oom,
28
+ jfr_summary: @jfr_summary.to_h
26
29
  }
27
30
  file = File.new(report_info_path, 'w')
28
- file.puts JSON.generate(hash)
31
+ file.puts JSON.pretty_generate(hash)
29
32
  file.close
30
33
  end
31
34
 
@@ -39,20 +42,58 @@ module PmdTester
39
42
  end
40
43
  end
41
44
 
42
- def format_execution_time
45
+ def execution_time_formatted
43
46
  self.class.convert_seconds(@execution_time)
44
47
  end
45
48
 
46
- def self.create(execution_time: 0, timestamp: '', working_dir: Dir.getwd, exit_code: nil, report_info_path:)
49
+ def to_h
50
+ {
51
+ 'timestamp' => @timestamp,
52
+ 'exit_code' => @exit_code,
53
+ 'cmdline' => @cmdline,
54
+ 'execution_time' => execution_time_formatted,
55
+ 'oom' => @oom,
56
+ 'jfr_summary' => @jfr_summary.to_h_for_liquid
57
+ }
58
+ end
59
+
60
+ def self.create(execution_time: 0, timestamp: '', working_dir: Dir.getwd, cmdline: '',
61
+ exit_code: nil, stdout: '', stderr: '', oom: false,
62
+ report_info_path:, jfr_summary: nil)
47
63
  detail = PmdReportDetail.new(execution_time: execution_time, timestamp: timestamp,
48
- working_dir: working_dir, exit_code: exit_code)
64
+ working_dir: working_dir, cmdline: cmdline, oom: oom,
65
+ exit_code: exit_code, stdout: stdout, stderr: stderr, jfr_summary: jfr_summary)
49
66
  detail.save(report_info_path)
50
67
  detail
51
68
  end
52
69
 
70
+ def self.empty
71
+ new(execution_time: 0, timestamp: '', working_dir: Dir.getwd, cmdline: '',
72
+ exit_code: nil, stdout: '', stderr: '', oom: false)
73
+ end
74
+
53
75
  # convert seconds into HH::MM::SS
54
76
  def self.convert_seconds(seconds)
55
77
  Time.at(seconds.abs).utc.strftime('%H:%M:%S')
56
78
  end
79
+
80
+ private
81
+
82
+ def initialize(execution_time: 0, timestamp: '', working_dir: Dir.getwd, cmdline: '',
83
+ exit_code: nil, stdout: '', stderr: '', oom: false, jfr_summary: nil)
84
+ @execution_time = execution_time
85
+ @timestamp = timestamp
86
+ @working_dir = working_dir
87
+ @cmdline = cmdline
88
+ @exit_code = exit_code.nil? ? '?' : exit_code.to_s
89
+ @stdout = stdout
90
+ @stderr = stderr
91
+ @oom = oom
92
+ @jfr_summary = if jfr_summary.instance_of? JfrSummary
93
+ jfr_summary
94
+ else
95
+ JfrSummary.from_h(jfr_summary || {})
96
+ end
97
+ end
57
98
  end
58
99
  end
@@ -7,12 +7,17 @@ module PmdTester
7
7
 
8
8
  # Parse the base and the patch report, compute their diff
9
9
  # Returns a +ReportDiff+
10
- def build_report_diff(base_report_file, patch_report_file, base_info, patch_info, filter_set = nil)
10
+ def build_report_diff(base_report_file, patch_report_file, base_info, patch_info, filter_set = nil,
11
+ rules_changed: true)
11
12
  base_details = PmdReportDetail.load(base_info)
12
- patch_details = PmdReportDetail.load(patch_info)
13
-
14
13
  base_report = parse_pmd_report(base_report_file, BASE, base_details, filter_set)
15
- patch_report = parse_pmd_report(patch_report_file, PATCH, patch_details)
14
+
15
+ if rules_changed
16
+ patch_details = PmdReportDetail.load(patch_info)
17
+ patch_report = parse_pmd_report(patch_report_file, PATCH, patch_details)
18
+ else
19
+ patch_report = base_report
20
+ end
16
21
 
17
22
  logger.info 'Calculating diffs'
18
23
  ReportDiff.new(base_report: base_report, patch_report: patch_report)
@@ -21,35 +26,58 @@ module PmdTester
21
26
  # Parse the +report_file+ to produce a +Report+.
22
27
  # For the schema of xml reports, refer to https://pmd.github.io/schema/report_2_0_0.xsd
23
28
  def parse_pmd_report(report_file, branch, report_details, filter_set = nil)
24
- require 'nokogiri'
25
-
26
- logger.info "Parsing #{report_file}"
29
+ logger.info "Parsing PMD Report #{report_file}"
27
30
  doc = PmdReportDocument.new(branch, report_details.working_dir, filter_set)
28
- parser = Nokogiri::XML::SAX::Parser.new(doc)
29
- parser.parse_file(report_file) if File.exist?(report_file)
31
+ .parse(report_file)
30
32
  Report.new(
33
+ report_details: report_details,
31
34
  report_document: doc,
32
- file: report_file,
35
+ file: report_file
36
+ )
37
+ end
33
38
 
34
- timestamp: report_details.timestamp,
35
- exec_time: report_details.execution_time,
36
- exit_code: report_details.exit_code
39
+ # Parse the base and the patch CPD report, compute their diff
40
+ # Returns a +CpdReportDiff+
41
+ def build_cpd_report_diff(base_report_file, patch_report_file, base_info, patch_info, impl_changed:)
42
+ base_details = PmdReportDetail.load(base_info)
43
+ base_report = parse_cpd_report(base_report_file, BASE, base_details)
44
+
45
+ if impl_changed
46
+ patch_details = PmdReportDetail.load(patch_info)
47
+ patch_report = parse_cpd_report(patch_report_file, PATCH, patch_details)
48
+ else
49
+ patch_report = base_report
50
+ end
51
+
52
+ logger.info 'Calculating CPD diffs'
53
+ CpdReportDiff.new(base_report: base_report, patch_report: patch_report)
54
+ end
55
+
56
+ def parse_cpd_report(report_file, branch, report_details)
57
+ logger.info "Parsing CPD Report #{report_file}"
58
+ doc = CpdReportDocument.new(branch, report_details.working_dir).parse(report_file)
59
+ CpdReport.new(
60
+ report_details: report_details,
61
+ report_document: doc,
62
+ file: report_file
37
63
  )
38
64
  end
39
65
 
40
66
  # Fill the report_diff field of every project
41
- def compute_project_diffs(projects, base_branch, patch_branch, filter_set = nil)
67
+ def compute_project_diffs(projects, base_branch, patch_branch, filter_set, rules_changed:, impl_changed:)
42
68
  projects.each do |project|
43
69
  logger.info "Preparing report for #{project.name}"
44
70
  logger.info " with filter #{filter_set}" unless filter_set.nil?
45
- project.compute_report_diff(base_branch, patch_branch, filter_set)
71
+ project.compute_report_diff(base_branch, patch_branch, filter_set,
72
+ rules_changed: rules_changed, impl_changed: impl_changed)
46
73
  end
47
74
  end
48
75
 
49
76
  # Build the diff reports and write them all
50
- def build_html_reports(projects, base_branch_details, patch_branch_details, filter_set = nil)
77
+ def build_html_reports(projects, base_branch_details, patch_branch_details, filter_set,
78
+ rules_changed:, impl_changed:)
51
79
  compute_project_diffs(projects, base_branch_details.branch_name, patch_branch_details.branch_name,
52
- filter_set)
80
+ filter_set, rules_changed: rules_changed, impl_changed: impl_changed)
53
81
 
54
82
  SummaryReportBuilder.new.write_all_projects(projects,
55
83
  base_branch_details,