pmdtester 1.0.0.pre.beta3 → 1.1.2

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/build.sh +67 -0
  3. data/.ci/files/env.gpg +1 -0
  4. data/.ci/inc/install-openjdk.inc +26 -0
  5. data/.ci/manual-integration-tests.sh +20 -0
  6. data/.github/workflows/build.yml +39 -0
  7. data/.github/workflows/manual-integration-tests.yml +32 -0
  8. data/.gitignore +9 -0
  9. data/.hoerc +1 -1
  10. data/.rubocop.yml +13 -2
  11. data/.rubocop_todo.yml +7 -8
  12. data/.ruby-version +1 -0
  13. data/Gemfile +1 -12
  14. data/History.md +104 -0
  15. data/Manifest.txt +30 -6
  16. data/README.rdoc +110 -60
  17. data/Rakefile +27 -15
  18. data/config/all-java.xml +1 -1
  19. data/config/design.xml +1 -1
  20. data/config/projectlist_1_0_0.xsd +2 -1
  21. data/config/projectlist_1_1_0.xsd +31 -0
  22. data/lib/pmdtester.rb +12 -4
  23. data/lib/pmdtester/builders/liquid_renderer.rb +73 -0
  24. data/lib/pmdtester/builders/pmd_report_builder.rb +134 -60
  25. data/lib/pmdtester/builders/project_builder.rb +100 -0
  26. data/lib/pmdtester/builders/project_hasher.rb +126 -0
  27. data/lib/pmdtester/builders/rule_set_builder.rb +94 -48
  28. data/lib/pmdtester/builders/simple_progress_logger.rb +27 -0
  29. data/lib/pmdtester/builders/summary_report_builder.rb +62 -117
  30. data/lib/pmdtester/cmd.rb +15 -1
  31. data/lib/pmdtester/collection_by_file.rb +55 -0
  32. data/lib/pmdtester/parsers/options.rb +25 -2
  33. data/lib/pmdtester/parsers/pmd_report_document.rb +79 -27
  34. data/lib/pmdtester/parsers/projects_parser.rb +2 -4
  35. data/lib/pmdtester/pmd_branch_detail.rb +36 -12
  36. data/lib/pmdtester/pmd_configerror.rb +62 -0
  37. data/lib/pmdtester/pmd_error.rb +34 -34
  38. data/lib/pmdtester/pmd_report_detail.rb +10 -13
  39. data/lib/pmdtester/pmd_tester_utils.rb +57 -0
  40. data/lib/pmdtester/pmd_violation.rb +66 -26
  41. data/lib/pmdtester/project.rb +28 -23
  42. data/lib/pmdtester/report_diff.rb +194 -70
  43. data/lib/pmdtester/resource_locator.rb +4 -0
  44. data/lib/pmdtester/runner.rb +81 -54
  45. data/pmdtester.gemspec +64 -0
  46. data/resources/_includes/diff_pill_row.html +6 -0
  47. data/resources/css/bootstrap.min.css +7 -0
  48. data/resources/css/datatables.min.css +36 -0
  49. data/resources/css/pmd-tester.css +132 -0
  50. data/resources/js/bootstrap.min.js +7 -0
  51. data/resources/js/code-snippets.js +73 -0
  52. data/resources/js/datatables.min.js +726 -0
  53. data/resources/js/jquery-3.2.1.slim.min.js +4 -0
  54. data/resources/js/jquery.min.js +2 -0
  55. data/resources/js/popper.min.js +5 -0
  56. data/resources/js/project-report.js +136 -0
  57. data/resources/project_diff_report.html +205 -0
  58. data/resources/project_index.html +102 -0
  59. metadata +122 -38
  60. data/.travis.yml +0 -22
  61. data/lib/pmdtester/builders/diff_builder.rb +0 -30
  62. data/lib/pmdtester/builders/diff_report_builder.rb +0 -225
  63. data/lib/pmdtester/builders/html_report_builder.rb +0 -33
  64. data/resources/css/maven-base.css +0 -155
  65. data/resources/css/maven-theme.css +0 -171
@@ -1,30 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PmdTester
4
- # This class is used to store pmd errors and its size.
5
- class PmdErrors
6
- attr_reader :errors
7
- attr_reader :errors_size
8
-
9
- def initialize
10
- # key:filename as String => value:PmdError Array
11
- @errors = {}
12
- @errors_size = 0
13
- end
14
-
15
- def add_error_by_filename(filename, error)
16
- if @errors.key?(filename)
17
- @errors[filename].push(error)
18
- else
19
- @errors.store(filename, [error])
20
- end
21
- @errors_size += 1
22
- end
23
- end
24
-
25
4
  # This class represents a 'error' element of Pmd xml report
26
5
  # and which Pmd branch the 'error' is from
27
6
  class PmdError
7
+ include PmdTester
28
8
  # The pmd branch type, 'base' or 'patch'
29
9
  attr_reader :branch
30
10
 
@@ -37,31 +17,51 @@ module PmdTester
37
17
  # </xs:extension>
38
18
  # </xs:simpleContent>
39
19
  # </xs:complexType>
40
- attr_reader :attrs
41
- attr_accessor :text
42
-
43
- def initialize(attrs, branch)
44
- @attrs = attrs
20
+ attr_accessor :stack_trace
21
+ attr_accessor :old_error
22
+ attr_reader :filename, :short_message
45
23
 
24
+ def initialize(branch:, filename:, short_message:)
46
25
  @branch = branch
47
- @text = ''
26
+ @stack_trace = ''
27
+ @changed = false
28
+ @short_message = short_message
29
+ @filename = filename
48
30
  end
49
31
 
50
- def filename
51
- @attrs['filename']
32
+ def short_filename
33
+ filename.gsub(%r{([^/]*+/)+}, '')
52
34
  end
53
35
 
54
- def msg
55
- @attrs['msg']
36
+ def changed?
37
+ @changed
56
38
  end
57
39
 
58
40
  def eql?(other)
59
- filename.eql?(other.filename) && msg.eql?(other.msg) &&
60
- @text.eql?(other.text)
41
+ filename.eql?(other.filename) &&
42
+ short_message.eql?(other.short_message) &&
43
+ stack_trace.eql?(other.stack_trace)
61
44
  end
62
45
 
63
46
  def hash
64
- [filename, msg, @text].hash
47
+ [filename, stack_trace].hash
48
+ end
49
+
50
+ def sort_key
51
+ filename
52
+ end
53
+
54
+ def try_merge?(other)
55
+ if branch != BASE &&
56
+ branch != other.branch &&
57
+ filename == other.filename &&
58
+ !changed? # not already changed
59
+ @changed = true
60
+ @old_error = other
61
+ true
62
+ else
63
+ false
64
+ end
65
65
  end
66
66
  end
67
67
  end
@@ -7,12 +7,12 @@ module PmdTester
7
7
  class PmdReportDetail
8
8
  attr_accessor :execution_time
9
9
  attr_accessor :timestamp
10
- attr_reader :working_dir
10
+ attr_accessor :working_dir
11
11
 
12
- def initialize
13
- @execution_time = 0
14
- @timestamp = ''
15
- @working_dir = Dir.getwd
12
+ def initialize(execution_time: 0, timestamp: '', working_dir: Dir.getwd)
13
+ @execution_time = execution_time
14
+ @timestamp = timestamp
15
+ @working_dir = working_dir
16
16
  end
17
17
 
18
18
  def save(report_info_path)
@@ -22,16 +22,13 @@ module PmdTester
22
22
  file.close
23
23
  end
24
24
 
25
- def load(report_info_path)
25
+ def self.load(report_info_path)
26
26
  if File.exist?(report_info_path)
27
- hash = JSON.parse(File.read(report_info_path))
28
- @execution_time = hash['execution_time']
29
- @timestamp = hash['timestamp']
30
- @working_dir = hash['working_dir']
31
- hash
27
+ hash = JSON.parse(File.read(report_info_path), symbolize_names: true)
28
+ PmdReportDetail.new(**hash)
32
29
  else
33
- puts "#{report_info_path} doesn't exist"
34
- {}
30
+ PmdTester.logger.warn("#{report_info_path} doesn't exist")
31
+ PmdReportDetail.new
35
32
  end
36
33
  end
37
34
 
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PmdTester
4
+ # Some functions that that don't belong in a specific class,
5
+ module PmdTesterUtils
6
+ include PmdTester
7
+
8
+ # Parse the base and the patch report, compute their diff
9
+ # Returns a +ReportDiff+
10
+ def build_report_diff(base_report_file, patch_report_file, base_info, patch_info, filter_set = nil)
11
+ base_details = PmdReportDetail.load(base_info)
12
+ patch_details = PmdReportDetail.load(patch_info)
13
+
14
+ 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)
16
+
17
+ logger.info 'Calculating diffs'
18
+ ReportDiff.new(base_report: base_report, patch_report: patch_report)
19
+ end
20
+
21
+ # Parse the +report_file+ to produce a +Report+.
22
+ # For the schema of xml reports, refer to http://pmd.sourceforge.net/report_2_0_0.xsd
23
+ def parse_pmd_report(report_file, branch, report_details, filter_set = nil)
24
+ require 'nokogiri'
25
+
26
+ logger.info "Parsing #{report_file}"
27
+ 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)
30
+ Report.new(
31
+ report_document: doc,
32
+
33
+ timestamp: report_details.timestamp,
34
+ exec_time: report_details.execution_time
35
+ )
36
+ end
37
+
38
+ # Fill the report_diff field of every project
39
+ def compute_project_diffs(projects, base_branch, patch_branch, filter_set = nil)
40
+ projects.each do |project|
41
+ logger.info "Preparing report for #{project.name}"
42
+ logger.info " with filter #{filter_set}" unless filter_set.nil?
43
+ project.compute_report_diff(base_branch, patch_branch, filter_set)
44
+ end
45
+ end
46
+
47
+ # Build the diff reports and write them all
48
+ def build_html_reports(projects, base_branch_details, patch_branch_details, filter_set = nil)
49
+ compute_project_diffs(projects, base_branch_details.branch_name, patch_branch_details.branch_name,
50
+ filter_set)
51
+
52
+ SummaryReportBuilder.new.write_all_projects(projects,
53
+ base_branch_details,
54
+ patch_branch_details)
55
+ end
56
+ end
57
+ end
@@ -1,23 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PmdTester
4
- # This class is used to store pmd violations and its size.
5
- class PmdViolations
6
- attr_reader :violations
7
- attr_reader :violations_size
8
-
9
- def initialize
10
- # key:filename as String => value:PmdViolation Array
11
- @violations = {}
12
- @violations_size = 0
13
- end
14
-
15
- def add_violations_by_filename(filename, violations)
16
- @violations.store(filename, violations)
17
- @violations_size += violations.size
18
- end
19
- end
20
-
21
4
  # This class represents a 'violation' element of Pmd xml report
22
5
  # and which pmd branch the 'violation' is from
23
6
  class PmdViolation
@@ -44,23 +27,80 @@ module PmdTester
44
27
  # </xs:simpleContent>
45
28
  # </xs:complexType>
46
29
 
47
- attr_reader :attrs
48
- attr_accessor :text
30
+ attr_reader :fname, :info_url, :line, :old_line, :old_message, :rule_name, :ruleset_name
31
+ attr_accessor :message
49
32
 
50
- def initialize(attrs, branch)
51
- @attrs = attrs
33
+ # rubocop:disable Metrics/ParameterLists
34
+ # Disable it: how is replacing a long parameter list with a single hash helping?
35
+ def initialize(branch:, fname:, info_url:, bline:, rule_name:, ruleset_name:)
52
36
  @branch = branch
53
- @text = ''
37
+ @fname = fname
38
+ @message = ''
39
+
40
+ @info_url = info_url
41
+ @line = bline
42
+ @rule_name = rule_name
43
+
44
+ @ruleset_name = ruleset_name
45
+
46
+ @changed = false
47
+ @old_message = nil
48
+ @old_line = nil
49
+ end
50
+ # rubocop:enable Metrics/ParameterLists
51
+
52
+ def line_move?(other)
53
+ message.eql?(other.message) && (line - other.line).abs <= 5
54
+ end
55
+
56
+ def try_merge?(other)
57
+ if branch != BASE && branch != other.branch && rule_name == other.rule_name &&
58
+ !changed? && # not already changed
59
+ (line == other.line || line_move?(other))
60
+ @changed = true
61
+ @old_message = other.message
62
+ @old_line = other.line
63
+ true
64
+ else
65
+ false
66
+ end
67
+ end
68
+
69
+ # only makes sense if this is a diff
70
+ def added?
71
+ branch != BASE && !changed?
72
+ end
73
+
74
+ # only makes sense if this is a diff
75
+ def changed?
76
+ @changed
77
+ end
78
+
79
+ # only makes sense if this is a diff
80
+ def removed?
81
+ branch == BASE
82
+ end
83
+
84
+ def sort_key
85
+ line
54
86
  end
55
87
 
56
88
  def eql?(other)
57
- @attrs['beginline'].eql?(other.attrs['beginline']) &&
58
- @attrs['rule'].eql?(other.attrs['rule']) &&
59
- @text.eql?(other.text)
89
+ rule_name.eql?(other.rule_name) &&
90
+ line.eql?(other.line) &&
91
+ fname.eql?(other.fname) &&
92
+ message.eql?(other.message)
60
93
  end
61
94
 
62
95
  def hash
63
- [@attrs['beginline'], @attrs['rule'], @text].hash
96
+ [line, rule_name, message].hash
97
+ end
98
+
99
+ def to_liquid
100
+ {
101
+ 'branch' => branch,
102
+ 'changed' => changed?
103
+ }
64
104
  end
65
105
  end
66
106
  end
@@ -3,6 +3,8 @@
3
3
  module PmdTester
4
4
  # This class represents all the information about the project
5
5
  class Project
6
+ include PmdTesterUtils
7
+
6
8
  REPOSITORIES_PATH = 'target/repositories'
7
9
 
8
10
  attr_reader :name
@@ -13,15 +15,17 @@ module PmdTester
13
15
  attr_reader :exclude_pattern
14
16
  attr_accessor :report_diff
15
17
  # key: pmd branch name as String => value: local path of pmd report
18
+ attr_reader :build_command
19
+ attr_reader :auxclasspath_command
20
+ # stores the auxclasspath calculated after cloning/preparing the project
21
+ attr_accessor :auxclasspath
16
22
 
17
23
  def initialize(project)
18
24
  @name = project.at_xpath('name').text
19
25
  @type = project.at_xpath('type').text
20
26
  @connection = project.at_xpath('connection').text
21
27
 
22
- @tag = 'master'
23
- tag_element = project.at_xpath('tag')
24
- @tag = tag_element.text unless tag_element.nil?
28
+ @tag = project.at_xpath('tag')&.text || 'master'
25
29
 
26
30
  webview_url_element = project.at_xpath('webview-url')
27
31
  @webview_url = default_webview_url
@@ -32,6 +36,9 @@ module PmdTester
32
36
  @exclude_pattern.push(ep.text)
33
37
  end
34
38
 
39
+ @build_command = project.at_xpath('build-command')&.text
40
+ @auxclasspath_command = project.at_xpath('auxclasspath-command')&.text
41
+
35
42
  @report_diff = nil
36
43
  end
37
44
 
@@ -58,6 +65,10 @@ module PmdTester
58
65
  file_path.gsub(%r{/#{local_source_path}}, @name)
59
66
  end
60
67
 
68
+ def get_local_path(file_path)
69
+ file_path.sub(%r{/#{local_source_path}/}, '')
70
+ end
71
+
61
72
  def get_pmd_report_path(branch_name)
62
73
  if branch_name.nil?
63
74
  nil
@@ -74,6 +85,14 @@ module PmdTester
74
85
  end
75
86
  end
76
87
 
88
+ def get_config_path(branch_name)
89
+ if branch_name.nil?
90
+ nil
91
+ else
92
+ "#{get_project_target_dir(branch_name)}/config.xml"
93
+ end
94
+ end
95
+
77
96
  def get_project_target_dir(branch_name)
78
97
  branch_filename = PmdBranchDetail.branch_filename(branch_name)
79
98
  dir = "target/reports/#{branch_filename}/#{@name}"
@@ -85,26 +104,12 @@ module PmdTester
85
104
  "#{REPOSITORIES_PATH}/#{@name}"
86
105
  end
87
106
 
88
- def target_diff_report_path
89
- dir = "target/reports/diff/#{@name}"
90
- FileUtils.mkdir_p(dir) unless File.directory?(dir)
91
- dir
92
- end
93
-
94
- def diff_report_index_path
95
- "#{target_diff_report_path}/index.html"
96
- end
97
-
98
- def diff_report_index_ref_path
99
- "./#{name}/index.html"
100
- end
101
-
102
- def diffs_exist?
103
- @report_diff.diffs_exist?
104
- end
105
-
106
- def introduce_new_errors?
107
- @report_diff.introduce_new_errors?
107
+ def compute_report_diff(base_branch, patch_branch, filter_set)
108
+ self.report_diff = build_report_diff(get_pmd_report_path(base_branch),
109
+ get_pmd_report_path(patch_branch),
110
+ get_report_info_path(base_branch),
111
+ get_report_info_path(patch_branch),
112
+ filter_set)
108
113
  end
109
114
  end
110
115
  end
@@ -1,111 +1,235 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PmdTester
4
+ # A bunch of counters to summarize differences
5
+ class RunningDiffCounters
6
+ attr_accessor :changed, :new, :removed, :patch_total, :base_total
7
+
8
+ def initialize(base_total)
9
+ @base_total = base_total
10
+ @patch_total = 0
11
+
12
+ @new = @removed = @changed = 0
13
+ end
14
+
15
+ def changed_total
16
+ new + removed + changed
17
+ end
18
+
19
+ def merge!(other)
20
+ self.changed += other.changed
21
+ self.new += other.new
22
+ self.removed += other.removed
23
+ self.base_total += other.base_total
24
+ self.patch_total += other.patch_total
25
+ end
26
+
27
+ def to_h
28
+ {
29
+ changed: changed,
30
+ new: new,
31
+ removed: removed,
32
+ base_total: base_total,
33
+ patch_total: patch_total
34
+ }
35
+ end
36
+
37
+ def to_s
38
+ "RunningDiffCounters[#{to_h}]"
39
+ end
40
+ end
41
+
42
+ # Simple info about a rule, collected by the report xml parser
43
+ class RuleInfo
44
+ attr_reader :name, :info_url
45
+
46
+ def initialize(name, info_url)
47
+ @name = name
48
+ @info_url = info_url
49
+ end
50
+ end
51
+
52
+ # A full report, created by the report XML parser,
53
+ # can be diffed with another report into a ReportDiff
54
+ class Report
55
+ attr_reader :violations_by_file,
56
+ :errors_by_file,
57
+ :configerrors_by_rule,
58
+ :exec_time,
59
+ :timestamp,
60
+ :infos_by_rule
61
+
62
+ def initialize(report_document: nil,
63
+ exec_time: 0,
64
+ timestamp: '0')
65
+ initialize_empty
66
+ initialize_with_report_document report_document unless report_document.nil?
67
+ @exec_time = exec_time
68
+ @timestamp = timestamp
69
+ end
70
+
71
+ def self.empty
72
+ new
73
+ end
74
+
75
+ private
76
+
77
+ def initialize_with_report_document(report_document)
78
+ @violations_by_file = report_document.violations
79
+ @errors_by_file = report_document.errors
80
+ @configerrors_by_rule = report_document.configerrors
81
+ @infos_by_rule = report_document.infos_by_rules
82
+
83
+ PmdTester.logger.debug("Loaded #{@violations_by_file.total_size} violations " \
84
+ "in #{@violations_by_file.num_files} files")
85
+ PmdTester.logger.debug("Loaded #{@errors_by_file.total_size} errors " \
86
+ "in #{@errors_by_file.num_files} files")
87
+ PmdTester.logger.debug("Loaded #{@configerrors_by_rule.size} config errors")
88
+ end
89
+
90
+ def initialize_empty
91
+ @violations_by_file = CollectionByFile.new
92
+ @errors_by_file = CollectionByFile.new
93
+ @configerrors_by_rule = {}
94
+ @infos_by_rule = {}
95
+ end
96
+ end
97
+
4
98
  # This class represents all the diff report information,
5
99
  # including the summary information of the original pmd reports,
6
100
  # as well as the specific information of the diff report.
7
101
  class ReportDiff
8
- attr_accessor :base_violations_size
9
- attr_accessor :patch_violations_size
10
- attr_accessor :violation_diffs_size
11
-
12
- attr_accessor :base_errors_size
13
- attr_accessor :patch_errors_size
14
- attr_accessor :error_diffs_size
102
+ include PmdTester
15
103
 
16
- attr_accessor :base_execution_time
17
- attr_accessor :patch_execution_time
18
- attr_accessor :diff_execution_time
104
+ attr_reader :error_counts
105
+ attr_reader :violation_counts
106
+ attr_reader :configerror_counts
19
107
 
20
- attr_accessor :base_timestamp
21
- attr_accessor :patch_timestamp
108
+ attr_accessor :violation_diffs_by_file
109
+ attr_accessor :error_diffs_by_file
110
+ attr_accessor :configerror_diffs_by_rule
22
111
 
23
- attr_accessor :violation_diffs
24
- attr_accessor :error_diffs
112
+ attr_accessor :rule_infos_union
113
+ attr_accessor :base_report
114
+ attr_accessor :patch_report
25
115
 
26
- def initialize
27
- @base_violations_size = 0
28
- @patch_violations_size = 0
29
- @violation_diffs_size = 0
116
+ def initialize(base_report:, patch_report:)
117
+ @violation_counts = RunningDiffCounters.new(base_report.violations_by_file.total_size)
118
+ @error_counts = RunningDiffCounters.new(base_report.errors_by_file.total_size)
119
+ @configerror_counts = RunningDiffCounters.new(base_report.configerrors_by_rule.values.flatten.length)
30
120
 
31
- @base_errors_size = 0
32
- @patch_errors_size = 0
33
- @error_diffs_size = 0
121
+ @violation_diffs_by_file = {}
122
+ @error_diffs_by_file = {}
123
+ @configerror_diffs_by_rule = {}
34
124
 
35
- @base_execution_time = 0
36
- @patch_execution_time = 0
37
- @diff_execution_time = 0
125
+ @rule_infos_union = base_report.infos_by_rule.dup
126
+ @base_report = base_report
127
+ @patch_report = patch_report
38
128
 
39
- @base_timestamp = ''
40
- @patch_timestamp = ''
41
-
42
- @violation_diffs = {}
43
- @error_diffs = {}
129
+ @violation_diffs_by_rule = {}
130
+ diff_with(patch_report)
44
131
  end
45
132
 
46
- def diffs_exist?
47
- !error_diffs_size.zero? || !violation_diffs_size.zero?
133
+ def rule_summaries
134
+ @violation_diffs_by_rule.map do |(rule, counters)|
135
+ {
136
+ 'name' => rule,
137
+ 'info_url' => @rule_infos_union[rule].info_url,
138
+ **counters.to_h.transform_keys(&:to_s)
139
+ }
140
+ end
48
141
  end
49
142
 
50
- def calculate_violations(base_violations, patch_violations)
51
- @base_violations_size = base_violations.violations_size
52
- @patch_violations_size = patch_violations.violations_size
53
- violation_diffs = build_diffs(base_violations.violations, patch_violations.violations)
54
- @violation_diffs = violation_diffs
55
- @violation_diffs_size = get_diffs_size(violation_diffs)
56
- end
143
+ private
144
+
145
+ def diff_with(patch_report)
146
+ @violation_counts.patch_total = patch_report.violations_by_file.total_size
147
+ @error_counts.patch_total = patch_report.errors_by_file.total_size
148
+ @configerror_counts.patch_total = patch_report.configerrors_by_rule.values.flatten.length
149
+
150
+ @violation_diffs_by_file = build_diffs(@violation_counts, &:violations_by_file)
151
+ count(@violation_diffs_by_file) { |v| getvdiff(v.rule_name) } # record the diffs in the rule counter
152
+
153
+ @error_diffs_by_file = build_diffs(@error_counts, &:errors_by_file)
154
+ @configerror_diffs_by_rule = build_diffs(@configerror_counts, &:configerrors_by_rule)
57
155
 
58
- def calculate_errors(base_errors, patch_errors)
59
- @base_errors_size = base_errors.errors_size
60
- @patch_errors_size = patch_errors.errors_size
61
- error_diffs = build_diffs(base_errors.errors, patch_errors.errors)
62
- @error_diffs = error_diffs
63
- @error_diffs_size = get_diffs_size(error_diffs)
156
+ count_by_rule(@base_report.violations_by_file, base: true)
157
+ count_by_rule(@patch_report.violations_by_file, base: false)
158
+ self
64
159
  end
65
160
 
66
- def calculate_details(base_info, patch_info)
67
- base_details = PmdReportDetail.new
68
- base_details.load(base_info) unless base_info.nil?
69
- patch_details = PmdReportDetail.new
70
- patch_details.load(patch_info) unless patch_info.nil?
161
+ def record_rule_info(violation)
162
+ return if @rule_infos_union.key?(violation.rule_name)
71
163
 
72
- @base_execution_time = base_details.format_execution_time
73
- @patch_execution_time = patch_details.format_execution_time
74
- @diff_execution_time =
75
- PmdReportDetail.convert_seconds(base_details.execution_time -
76
- patch_details.execution_time)
164
+ @rule_infos_union[violation.rule_name] = RuleInfo.new(violation.rule_name, violation.info_url)
165
+ end
166
+
167
+ def getvdiff(rule_name)
168
+ @violation_diffs_by_rule.fetch(rule_name) do |_|
169
+ @violation_diffs_by_rule[rule_name] = RunningDiffCounters.new(0)
170
+ end
171
+ end
77
172
 
78
- @base_timestamp = base_details.timestamp
79
- @patch_timestamp = patch_details.timestamp
80
- [base_details, patch_details]
173
+ def count_by_rule(violations_h, base:)
174
+ violations_h.each_value do |v|
175
+ record_rule_info(v)
176
+ rule_diff = getvdiff(v.rule_name)
177
+ if base
178
+ rule_diff.base_total += 1
179
+ else
180
+ rule_diff.patch_total += 1
181
+ end
182
+ end
81
183
  end
82
184
 
83
- def build_diffs(base_hash, patch_hash)
84
- diffs = base_hash.merge(patch_hash) do |_key, base_value, patch_value|
185
+ def build_diffs(counters, &getter)
186
+ base_hash = getter.yield(@base_report)
187
+ patch_hash = getter.yield(@patch_report)
188
+ # Keys are filenames
189
+ # Values are lists of violations/errors
190
+ diffs = base_hash.to_h.merge(patch_hash.to_h) do |_key, base_value, patch_value|
191
+ # make the difference of values
85
192
  (base_value | patch_value) - (base_value & patch_value)
86
193
  end
87
194
 
88
195
  diffs.delete_if do |_key, value|
89
196
  value.empty?
90
197
  end
198
+
199
+ merge_changed_items(diffs)
200
+ count(diffs) { |_| counters }
201
+ diffs
91
202
  end
92
203
 
93
- def get_diffs_size(diffs_hash)
94
- size = 0
95
- diffs_hash.keys.each do |key|
96
- size += diffs_hash[key].size
204
+ # @param diff_h a hash { filename => list[violation]}, containing those that changed
205
+ # in case of config errors it's a hash { rulename => list[configerror] }
206
+ def merge_changed_items(diff_h)
207
+ diff_h.each do |fname, different|
208
+ different.sort_by!(&:sort_key)
209
+ diff_h[fname] = different.delete_if do |v|
210
+ v.branch == BASE &&
211
+ # try_merge will set v2.changed = true if it succeeds
212
+ different.any? { |v2| v2.try_merge?(v) }
213
+ end
97
214
  end
98
- size
99
215
  end
100
216
 
101
- def introduce_new_errors?
102
- @error_diffs.values.each do |pmd_errors|
103
- pmd_errors.each do |pmd_error|
104
- return true if pmd_error.branch.eql?('patch')
217
+ def count(item_h)
218
+ item_h = { '' => item_h } if item_h.is_a?(Array)
219
+
220
+ item_h.each do |_k, items|
221
+ items.each do |item|
222
+ counter = yield item
223
+
224
+ if item.changed?
225
+ counter.changed += 1
226
+ elsif item.branch.eql?(BASE)
227
+ counter.removed += 1
228
+ else
229
+ counter.new += 1
230
+ end
105
231
  end
106
232
  end
107
-
108
- false
109
233
  end
110
234
  end
111
235
  end