pmdtester 1.0.0.pre.beta3 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
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