pmdtester 1.0.0 → 1.2.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.
- checksums.yaml +4 -4
- data/.ci/build.sh +99 -0
- data/.ci/inc/fetch_ci_scripts.bash +19 -0
- data/.ci/manual-integration-tests.sh +37 -0
- data/.github/workflows/build.yml +55 -0
- data/.github/workflows/manual-integration-tests.yml +43 -0
- data/.gitignore +9 -0
- data/.hoerc +1 -1
- data/.rubocop.yml +9 -2
- data/.ruby-version +1 -0
- data/History.md +79 -0
- data/Manifest.txt +28 -9
- data/README.rdoc +59 -33
- data/Rakefile +7 -5
- data/config/all-java.xml +1 -1
- data/config/design.xml +1 -1
- data/config/project-list.xml +8 -7
- data/config/projectlist_1_0_0.xsd +2 -1
- data/config/projectlist_1_1_0.xsd +31 -0
- data/config/projectlist_1_2_0.xsd +39 -0
- data/lib/pmdtester.rb +8 -7
- data/lib/pmdtester/builders/liquid_renderer.rb +130 -0
- data/lib/pmdtester/builders/pmd_report_builder.rb +107 -79
- data/lib/pmdtester/builders/project_builder.rb +105 -0
- data/lib/pmdtester/builders/project_hasher.rb +128 -0
- data/lib/pmdtester/builders/rule_set_builder.rb +96 -47
- data/lib/pmdtester/builders/simple_progress_logger.rb +4 -4
- data/lib/pmdtester/builders/summary_report_builder.rb +63 -131
- data/lib/pmdtester/collection_by_file.rb +55 -0
- data/lib/pmdtester/parsers/options.rb +24 -0
- data/lib/pmdtester/parsers/pmd_report_document.rb +72 -28
- data/lib/pmdtester/parsers/projects_parser.rb +2 -4
- data/lib/pmdtester/pmd_branch_detail.rb +35 -19
- data/lib/pmdtester/pmd_configerror.rb +23 -24
- data/lib/pmdtester/pmd_error.rb +34 -34
- data/lib/pmdtester/pmd_report_detail.rb +10 -13
- data/lib/pmdtester/pmd_tester_utils.rb +58 -0
- data/lib/pmdtester/pmd_violation.rb +66 -28
- data/lib/pmdtester/project.rb +42 -56
- data/lib/pmdtester/report_diff.rb +203 -109
- data/lib/pmdtester/resource_locator.rb +4 -0
- data/lib/pmdtester/runner.rb +67 -64
- data/pmdtester.gemspec +28 -37
- data/resources/_includes/diff_pill_row.html +6 -0
- data/resources/css/bootstrap.min.css +7 -0
- data/resources/css/datatables.min.css +36 -0
- data/resources/css/pmd-tester.css +132 -0
- data/resources/js/bootstrap.min.js +7 -0
- data/resources/js/code-snippets.js +73 -0
- data/resources/js/datatables.min.js +726 -0
- data/resources/js/jquery-3.2.1.slim.min.js +4 -0
- data/resources/js/jquery.min.js +2 -0
- data/resources/js/popper.min.js +5 -0
- data/resources/js/project-report.js +137 -0
- data/resources/project_diff_report.html +214 -0
- data/resources/project_index.html +113 -0
- data/resources/project_pmd_report.html +186 -0
- metadata +73 -25
- data/.travis.yml +0 -40
- data/lib/pmdtester/builders/diff_builder.rb +0 -31
- data/lib/pmdtester/builders/diff_report/configerrors.rb +0 -50
- data/lib/pmdtester/builders/diff_report/errors.rb +0 -71
- data/lib/pmdtester/builders/diff_report/violations.rb +0 -77
- data/lib/pmdtester/builders/diff_report_builder.rb +0 -99
- data/lib/pmdtester/builders/html_report_builder.rb +0 -56
- data/resources/css/maven-base.css +0 -155
- data/resources/css/maven-theme.css +0 -171
@@ -7,12 +7,12 @@ module PmdTester
|
|
7
7
|
class PmdReportDetail
|
8
8
|
attr_accessor :execution_time
|
9
9
|
attr_accessor :timestamp
|
10
|
-
|
10
|
+
attr_accessor :working_dir
|
11
11
|
|
12
|
-
def initialize
|
13
|
-
@execution_time =
|
14
|
-
@timestamp =
|
15
|
-
@working_dir =
|
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
|
-
|
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
|
-
|
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,58 @@
|
|
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
|
+
file: report_file,
|
33
|
+
|
34
|
+
timestamp: report_details.timestamp,
|
35
|
+
exec_time: report_details.execution_time
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Fill the report_diff field of every project
|
40
|
+
def compute_project_diffs(projects, base_branch, patch_branch, filter_set = nil)
|
41
|
+
projects.each do |project|
|
42
|
+
logger.info "Preparing report for #{project.name}"
|
43
|
+
logger.info " with filter #{filter_set}" unless filter_set.nil?
|
44
|
+
project.compute_report_diff(base_branch, patch_branch, filter_set)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Build the diff reports and write them all
|
49
|
+
def build_html_reports(projects, base_branch_details, patch_branch_details, filter_set = nil)
|
50
|
+
compute_project_diffs(projects, base_branch_details.branch_name, patch_branch_details.branch_name,
|
51
|
+
filter_set)
|
52
|
+
|
53
|
+
SummaryReportBuilder.new.write_all_projects(projects,
|
54
|
+
base_branch_details,
|
55
|
+
patch_branch_details)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -1,25 +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
|
-
return if violations.empty?
|
17
|
-
|
18
|
-
@violations.store(filename, violations)
|
19
|
-
@violations_size += violations.size
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
4
|
# This class represents a 'violation' element of Pmd xml report
|
24
5
|
# and which pmd branch the 'violation' is from
|
25
6
|
class PmdViolation
|
@@ -46,23 +27,80 @@ module PmdTester
|
|
46
27
|
# </xs:simpleContent>
|
47
28
|
# </xs:complexType>
|
48
29
|
|
49
|
-
attr_reader :
|
50
|
-
attr_accessor :
|
30
|
+
attr_reader :fname, :info_url, :line, :old_line, :old_message, :rule_name, :ruleset_name
|
31
|
+
attr_accessor :message
|
51
32
|
|
52
|
-
|
53
|
-
|
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:)
|
54
36
|
@branch = branch
|
55
|
-
@
|
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
|
56
86
|
end
|
57
87
|
|
58
88
|
def eql?(other)
|
59
|
-
|
60
|
-
|
61
|
-
|
89
|
+
rule_name.eql?(other.rule_name) &&
|
90
|
+
line.eql?(other.line) &&
|
91
|
+
fname.eql?(other.fname) &&
|
92
|
+
message.eql?(other.message)
|
62
93
|
end
|
63
94
|
|
64
95
|
def hash
|
65
|
-
[
|
96
|
+
[line, rule_name, message].hash
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_liquid
|
100
|
+
{
|
101
|
+
'branch' => branch,
|
102
|
+
'changed' => changed?
|
103
|
+
}
|
66
104
|
end
|
67
105
|
end
|
68
106
|
end
|
data/lib/pmdtester/project.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'pathname'
|
4
|
+
|
3
5
|
module PmdTester
|
4
6
|
# This class represents all the information about the project
|
5
7
|
class Project
|
8
|
+
include PmdTesterUtils
|
9
|
+
|
6
10
|
REPOSITORIES_PATH = 'target/repositories'
|
7
11
|
|
8
12
|
attr_reader :name
|
@@ -10,29 +14,36 @@ module PmdTester
|
|
10
14
|
attr_reader :connection
|
11
15
|
attr_reader :webview_url
|
12
16
|
attr_reader :tag
|
13
|
-
attr_reader :
|
17
|
+
attr_reader :exclude_patterns
|
18
|
+
attr_reader :src_subpath
|
14
19
|
attr_accessor :report_diff
|
15
20
|
# key: pmd branch name as String => value: local path of pmd report
|
21
|
+
attr_reader :build_command
|
22
|
+
attr_reader :auxclasspath_command
|
23
|
+
# stores the auxclasspath calculated after cloning/preparing the project
|
24
|
+
attr_accessor :auxclasspath
|
16
25
|
|
17
|
-
def initialize(project)
|
26
|
+
def initialize(project) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
18
27
|
@name = project.at_xpath('name').text
|
19
28
|
@type = project.at_xpath('type').text
|
20
29
|
@connection = project.at_xpath('connection').text
|
21
30
|
|
22
|
-
@tag = 'master'
|
23
|
-
tag_element = project.at_xpath('tag')
|
24
|
-
@tag = tag_element.text unless tag_element.nil?
|
31
|
+
@tag = project.at_xpath('tag')&.text || 'master'
|
25
32
|
|
26
33
|
webview_url_element = project.at_xpath('webview-url')
|
27
34
|
@webview_url = default_webview_url
|
28
35
|
@webview_url = webview_url_element.text unless webview_url_element.nil?
|
29
36
|
|
30
|
-
@
|
37
|
+
@src_subpath = project.at_xpath('src-subpath')&.text || '.'
|
38
|
+
@exclude_patterns = []
|
31
39
|
project.xpath('exclude-pattern').each do |ep|
|
32
|
-
@
|
40
|
+
@exclude_patterns.push(ep.text)
|
33
41
|
end
|
34
42
|
|
35
|
-
@
|
43
|
+
@build_command = project.at_xpath('build-command')&.text
|
44
|
+
@auxclasspath_command = project.at_xpath('auxclasspath-command')&.text
|
45
|
+
|
46
|
+
@report_diff = nil
|
36
47
|
end
|
37
48
|
|
38
49
|
# Generate the default webview url for the projects
|
@@ -49,13 +60,17 @@ module PmdTester
|
|
49
60
|
# Change the file path from 'LOCAL_DIR/SOURCE_CODE_PATH' to
|
50
61
|
# 'WEB_VIEW_URL/SOURCE_CODE_PATH'
|
51
62
|
def get_webview_url(file_path)
|
52
|
-
file_path.gsub(%r{/#{
|
63
|
+
file_path.gsub(%r{/#{clone_root_path}}, @webview_url)
|
53
64
|
end
|
54
65
|
|
55
66
|
# Change the file path from 'LOCAL_DIR/SOURCE_CODE_PATH' to
|
56
67
|
# 'PROJECT_NAME/SOURCE_CODE_PATH'
|
57
68
|
def get_path_inside_project(file_path)
|
58
|
-
file_path.gsub(%r{/#{
|
69
|
+
file_path.gsub(%r{/#{clone_root_path}}, @name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_local_path(file_path)
|
73
|
+
file_path.sub(%r{/#{clone_root_path}/}, '')
|
59
74
|
end
|
60
75
|
|
61
76
|
def get_pmd_report_path(branch_name)
|
@@ -82,61 +97,32 @@ module PmdTester
|
|
82
97
|
end
|
83
98
|
end
|
84
99
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
100
|
+
##
|
101
|
+
# Path to the sources to analyze (below or equal to clone_root_path)
|
102
|
+
def local_source_path
|
103
|
+
# normalize path
|
104
|
+
Pathname.new("#{clone_root_path}/#{src_subpath}").cleanpath
|
90
105
|
end
|
91
106
|
|
92
|
-
|
107
|
+
##
|
108
|
+
# Path to the clone directory
|
109
|
+
def clone_root_path
|
93
110
|
"#{REPOSITORIES_PATH}/#{@name}"
|
94
111
|
end
|
95
112
|
|
96
|
-
def
|
97
|
-
|
113
|
+
def get_project_target_dir(branch_name)
|
114
|
+
branch_filename = PmdBranchDetail.branch_filename(branch_name)
|
115
|
+
dir = "target/reports/#{branch_filename}/#{@name}"
|
98
116
|
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
99
117
|
dir
|
100
118
|
end
|
101
119
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
109
|
-
|
110
|
-
def diffs_exist?
|
111
|
-
@report_diff.nil? ? false : @report_diff.diffs_exist?
|
112
|
-
end
|
113
|
-
|
114
|
-
def introduce_new_errors?
|
115
|
-
@report_diff.nil? ? false : @report_diff.introduce_new_errors?
|
116
|
-
end
|
117
|
-
|
118
|
-
def removed_errors_size
|
119
|
-
@report_diff.removed_errors_size
|
120
|
-
end
|
121
|
-
|
122
|
-
def new_errors_size
|
123
|
-
@report_diff.new_errors_size
|
124
|
-
end
|
125
|
-
|
126
|
-
def removed_violations_size
|
127
|
-
@report_diff.removed_violations_size
|
128
|
-
end
|
129
|
-
|
130
|
-
def new_violations_size
|
131
|
-
@report_diff.new_violations_size
|
132
|
-
end
|
133
|
-
|
134
|
-
def removed_configerrors_size
|
135
|
-
@report_diff.removed_configerrors_size
|
136
|
-
end
|
137
|
-
|
138
|
-
def new_configerrors_size
|
139
|
-
@report_diff.new_configerrors_size
|
120
|
+
def compute_report_diff(base_branch, patch_branch, filter_set)
|
121
|
+
self.report_diff = build_report_diff(get_pmd_report_path(base_branch),
|
122
|
+
get_pmd_report_path(patch_branch),
|
123
|
+
get_report_info_path(base_branch),
|
124
|
+
get_report_info_path(patch_branch),
|
125
|
+
filter_set)
|
140
126
|
end
|
141
127
|
end
|
142
128
|
end
|
@@ -1,157 +1,251 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module PmdTester
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
class ReportDiff
|
8
|
-
include PmdTester
|
9
|
-
|
10
|
-
attr_accessor :base_violations_size
|
11
|
-
attr_accessor :patch_violations_size
|
12
|
-
attr_accessor :new_violations_size
|
13
|
-
attr_accessor :removed_violations_size
|
14
|
-
attr_accessor :violation_diffs_size
|
4
|
+
# A bunch of counters to summarize differences
|
5
|
+
class RunningDiffCounters
|
6
|
+
attr_accessor :changed, :new, :removed, :patch_total, :base_total
|
15
7
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
attr_accessor :removed_errors_size
|
20
|
-
attr_accessor :error_diffs_size
|
8
|
+
def initialize(base_total)
|
9
|
+
@base_total = base_total
|
10
|
+
@patch_total = 0
|
21
11
|
|
22
|
-
|
23
|
-
|
24
|
-
attr_accessor :new_configerrors_size
|
25
|
-
attr_accessor :removed_configerrors_size
|
26
|
-
attr_accessor :configerrors_diffs_size
|
27
|
-
|
28
|
-
attr_accessor :base_execution_time
|
29
|
-
attr_accessor :patch_execution_time
|
30
|
-
attr_accessor :diff_execution_time
|
12
|
+
@new = @removed = @changed = 0
|
13
|
+
end
|
31
14
|
|
32
|
-
|
33
|
-
|
15
|
+
def changed_total
|
16
|
+
new + removed + changed
|
17
|
+
end
|
34
18
|
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
38
26
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
43
36
|
|
44
|
-
|
45
|
-
|
46
|
-
|
37
|
+
def to_s
|
38
|
+
"RunningDiffCounters[#{to_h}]"
|
39
|
+
end
|
40
|
+
end
|
47
41
|
|
48
|
-
|
49
|
-
|
42
|
+
# Simple info about a rule, collected by the report xml parser
|
43
|
+
class RuleInfo
|
44
|
+
attr_reader :name, :info_url
|
50
45
|
|
51
|
-
|
52
|
-
@
|
53
|
-
@
|
46
|
+
def initialize(name, info_url)
|
47
|
+
@name = name
|
48
|
+
@info_url = info_url
|
54
49
|
end
|
50
|
+
end
|
55
51
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
+
:file
|
61
|
+
|
62
|
+
def initialize(report_document: nil,
|
63
|
+
file: '',
|
64
|
+
exec_time: 0,
|
65
|
+
timestamp: '0')
|
66
|
+
initialize_empty
|
67
|
+
initialize_with_report_document report_document unless report_document.nil?
|
68
|
+
@exec_time = exec_time
|
69
|
+
@timestamp = timestamp
|
70
|
+
@file = file
|
62
71
|
end
|
63
72
|
|
64
|
-
def
|
65
|
-
|
66
|
-
@patch_errors_size = 0
|
67
|
-
@new_errors_size = 0
|
68
|
-
@removed_errors_size = 0
|
69
|
-
@error_diffs_size = 0
|
73
|
+
def self.empty
|
74
|
+
new
|
70
75
|
end
|
71
76
|
|
72
|
-
def
|
73
|
-
|
74
|
-
@
|
75
|
-
|
76
|
-
|
77
|
-
|
77
|
+
def rule_summaries
|
78
|
+
summary = {}
|
79
|
+
@violations_by_file.each_value do |violation|
|
80
|
+
unless summary.key?(violation.rule_name)
|
81
|
+
summary[violation.rule_name] = {
|
82
|
+
'name' => violation.rule_name,
|
83
|
+
'info_url' => violation.info_url,
|
84
|
+
'count' => 0
|
85
|
+
}
|
86
|
+
end
|
87
|
+
summary[violation.rule_name]['count'] += 1
|
88
|
+
end
|
89
|
+
|
90
|
+
summary.values
|
78
91
|
end
|
79
92
|
|
80
|
-
|
81
|
-
|
93
|
+
private
|
94
|
+
|
95
|
+
def initialize_with_report_document(report_document)
|
96
|
+
@violations_by_file = report_document.violations
|
97
|
+
@errors_by_file = report_document.errors
|
98
|
+
@configerrors_by_rule = report_document.configerrors
|
99
|
+
|
100
|
+
PmdTester.logger.debug("Loaded #{@violations_by_file.total_size} violations " \
|
101
|
+
"in #{@violations_by_file.num_files} files")
|
102
|
+
PmdTester.logger.debug("Loaded #{@errors_by_file.total_size} errors " \
|
103
|
+
"in #{@errors_by_file.num_files} files")
|
104
|
+
PmdTester.logger.debug("Loaded #{@configerrors_by_rule.size} config errors")
|
82
105
|
end
|
83
106
|
|
84
|
-
def
|
85
|
-
|
107
|
+
def initialize_empty
|
108
|
+
@violations_by_file = CollectionByFile.new
|
109
|
+
@errors_by_file = CollectionByFile.new
|
110
|
+
@configerrors_by_rule = {}
|
86
111
|
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# This class represents all the diff report information,
|
115
|
+
# including the summary information of the original pmd reports,
|
116
|
+
# as well as the specific information of the diff report.
|
117
|
+
class ReportDiff
|
118
|
+
include PmdTester
|
119
|
+
|
120
|
+
attr_reader :error_counts
|
121
|
+
attr_reader :violation_counts
|
122
|
+
attr_reader :configerror_counts
|
123
|
+
|
124
|
+
attr_accessor :violation_diffs_by_file
|
125
|
+
attr_accessor :error_diffs_by_file
|
126
|
+
attr_accessor :configerror_diffs_by_rule
|
127
|
+
|
128
|
+
attr_accessor :rule_infos_union
|
129
|
+
attr_accessor :base_report
|
130
|
+
attr_accessor :patch_report
|
131
|
+
|
132
|
+
def initialize(base_report:, patch_report:)
|
133
|
+
@violation_counts = RunningDiffCounters.new(base_report.violations_by_file.total_size)
|
134
|
+
@error_counts = RunningDiffCounters.new(base_report.errors_by_file.total_size)
|
135
|
+
@configerror_counts = RunningDiffCounters.new(base_report.configerrors_by_rule.values.flatten.length)
|
87
136
|
|
88
|
-
|
89
|
-
@
|
90
|
-
@
|
91
|
-
|
92
|
-
@
|
93
|
-
@
|
94
|
-
@
|
137
|
+
@violation_diffs_by_file = {}
|
138
|
+
@error_diffs_by_file = {}
|
139
|
+
@configerror_diffs_by_rule = {}
|
140
|
+
|
141
|
+
@rule_infos_union = {}
|
142
|
+
@base_report = base_report
|
143
|
+
@patch_report = patch_report
|
144
|
+
|
145
|
+
@violation_diffs_by_rule = {}
|
146
|
+
diff_with(patch_report)
|
95
147
|
end
|
96
148
|
|
97
|
-
def
|
98
|
-
@
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
149
|
+
def rule_summaries
|
150
|
+
@violation_diffs_by_rule.map do |(rule, counters)|
|
151
|
+
{
|
152
|
+
'name' => rule,
|
153
|
+
'info_url' => @rule_infos_union[rule].info_url,
|
154
|
+
**counters.to_h.transform_keys(&:to_s)
|
155
|
+
}
|
156
|
+
end
|
104
157
|
end
|
105
158
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
@
|
111
|
-
@
|
112
|
-
|
159
|
+
private
|
160
|
+
|
161
|
+
def diff_with(patch_report)
|
162
|
+
@violation_counts.patch_total = patch_report.violations_by_file.total_size
|
163
|
+
@error_counts.patch_total = patch_report.errors_by_file.total_size
|
164
|
+
@configerror_counts.patch_total = patch_report.configerrors_by_rule.values.flatten.length
|
165
|
+
|
166
|
+
@violation_diffs_by_file = build_diffs(@violation_counts, &:violations_by_file)
|
167
|
+
count(@violation_diffs_by_file) { |v| getvdiff(v.rule_name) } # record the diffs in the rule counter
|
168
|
+
|
169
|
+
@error_diffs_by_file = build_diffs(@error_counts, &:errors_by_file)
|
170
|
+
@configerror_diffs_by_rule = build_diffs(@configerror_counts, &:configerrors_by_rule)
|
171
|
+
|
172
|
+
count_by_rule(@base_report.violations_by_file, base: true)
|
173
|
+
count_by_rule(@patch_report.violations_by_file, base: false)
|
174
|
+
self
|
113
175
|
end
|
114
176
|
|
115
|
-
def
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
177
|
+
def record_rule_info(violation)
|
178
|
+
return if @rule_infos_union.key?(violation.rule_name)
|
179
|
+
|
180
|
+
@rule_infos_union[violation.rule_name] = RuleInfo.new(violation.rule_name, violation.info_url)
|
181
|
+
end
|
120
182
|
|
121
|
-
|
122
|
-
@
|
123
|
-
|
124
|
-
|
125
|
-
|
183
|
+
def getvdiff(rule_name)
|
184
|
+
@violation_diffs_by_rule.fetch(rule_name) do |_|
|
185
|
+
@violation_diffs_by_rule[rule_name] = RunningDiffCounters.new(0)
|
186
|
+
end
|
187
|
+
end
|
126
188
|
|
127
|
-
|
128
|
-
|
129
|
-
|
189
|
+
def count_by_rule(violations_h, base:)
|
190
|
+
violations_h.each_value do |v|
|
191
|
+
record_rule_info(v)
|
192
|
+
rule_diff = getvdiff(v.rule_name)
|
193
|
+
if base
|
194
|
+
rule_diff.base_total += 1
|
195
|
+
else
|
196
|
+
rule_diff.patch_total += 1
|
197
|
+
end
|
198
|
+
end
|
130
199
|
end
|
131
200
|
|
132
|
-
def build_diffs(
|
133
|
-
|
201
|
+
def build_diffs(counters, &getter)
|
202
|
+
base_hash = getter.yield(@base_report)
|
203
|
+
patch_hash = getter.yield(@patch_report)
|
204
|
+
# Keys are filenames
|
205
|
+
# Values are lists of violations/errors
|
206
|
+
diffs = base_hash.to_h.merge(patch_hash.to_h) do |_key, base_value, patch_value|
|
207
|
+
# make the difference of values
|
134
208
|
(base_value | patch_value) - (base_value & patch_value)
|
135
209
|
end
|
136
210
|
|
137
211
|
diffs.delete_if do |_key, value|
|
138
212
|
value.empty?
|
139
213
|
end
|
214
|
+
|
215
|
+
merge_changed_items(diffs)
|
216
|
+
count(diffs) { |_| counters }
|
217
|
+
diffs
|
140
218
|
end
|
141
219
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
220
|
+
# @param diff_h a hash { filename => list[violation]}, containing those that changed
|
221
|
+
# in case of config errors it's a hash { rulename => list[configerror] }
|
222
|
+
def merge_changed_items(diff_h)
|
223
|
+
diff_h.each do |fname, different|
|
224
|
+
different.sort_by!(&:sort_key)
|
225
|
+
diff_h[fname] = different.delete_if do |v|
|
226
|
+
v.branch == BASE &&
|
227
|
+
# try_merge will set v2.changed = true if it succeeds
|
228
|
+
different.any? { |v2| v2.try_merge?(v) }
|
148
229
|
end
|
149
230
|
end
|
150
|
-
[new_size, removed_size]
|
151
231
|
end
|
152
232
|
|
153
|
-
def
|
154
|
-
|
233
|
+
def count(item_h)
|
234
|
+
item_h = { '' => item_h } if item_h.is_a?(Array)
|
235
|
+
|
236
|
+
item_h.each do |_k, items|
|
237
|
+
items.each do |item|
|
238
|
+
counter = yield item
|
239
|
+
|
240
|
+
if item.changed?
|
241
|
+
counter.changed += 1
|
242
|
+
elsif item.branch.eql?(BASE)
|
243
|
+
counter.removed += 1
|
244
|
+
else
|
245
|
+
counter.new += 1
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
155
249
|
end
|
156
250
|
end
|
157
251
|
end
|