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
@@ -9,8 +9,9 @@ module PmdTester
|
|
9
9
|
# Attention: we only consider java rulesets now.
|
10
10
|
class RuleSetBuilder
|
11
11
|
include PmdTester
|
12
|
-
|
13
|
-
|
12
|
+
ALL_CATEGORIES = Set['bestpractices.xml', 'codestyle.xml', 'design.xml', 'documentation.xml',
|
13
|
+
'errorprone.xml', 'multithreading.xml', 'performance.xml',
|
14
|
+
'security.xml'].freeze
|
14
15
|
PATH_TO_PMD_JAVA_BASED_RULES =
|
15
16
|
'pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule'
|
16
17
|
PATH_TO_PMD_XPATH_BASED_RULES = 'pmd-java/src/main/resources/category/java'
|
@@ -25,19 +26,39 @@ module PmdTester
|
|
25
26
|
|
26
27
|
def build
|
27
28
|
filenames = diff_filenames
|
28
|
-
|
29
|
-
output_filter_set(
|
30
|
-
build_config_file(
|
31
|
-
logger.debug "Dynamic configuration: #{[
|
32
|
-
|
29
|
+
rule_refs = get_rule_refs(filenames)
|
30
|
+
output_filter_set(rule_refs)
|
31
|
+
build_config_file(rule_refs)
|
32
|
+
logger.debug "Dynamic configuration: #{[rule_refs]}"
|
33
|
+
rule_refs
|
33
34
|
end
|
34
35
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
def calculate_filter_set
|
37
|
+
output_filter_set(ALL_CATEGORIES)
|
38
|
+
end
|
39
|
+
|
40
|
+
def output_filter_set(rule_refs)
|
41
|
+
if rule_refs == ALL_CATEGORIES
|
42
|
+
if @options.mode == Options::ONLINE
|
43
|
+
@options.filter_set = Set[]
|
44
|
+
doc = File.open(@options.patch_config) { |f| Nokogiri::XML(f) }
|
45
|
+
rules = doc.css('ruleset rule')
|
46
|
+
rules.each do |r|
|
47
|
+
ref = r.attributes['ref'].content
|
48
|
+
ref.delete_prefix!('category/java/')
|
49
|
+
@options.filter_set.add(ref)
|
50
|
+
end
|
51
|
+
|
52
|
+
logger.debug "Using filter based on patch config #{@options.patch_config}: " \
|
53
|
+
"#{@options.filter_set}"
|
54
|
+
else
|
55
|
+
# if `rule_refs` contains all categories, then no need to filter the baseline
|
56
|
+
logger.debug 'No filter when comparing patch to baseline'
|
57
|
+
@options.filter_set = nil
|
58
|
+
end
|
39
59
|
else
|
40
|
-
|
60
|
+
logger.debug "Filter is now #{rule_refs}"
|
61
|
+
@options.filter_set = rule_refs
|
41
62
|
end
|
42
63
|
end
|
43
64
|
|
@@ -53,58 +74,86 @@ module PmdTester
|
|
53
74
|
filenames.split("\n")
|
54
75
|
end
|
55
76
|
|
56
|
-
def
|
57
|
-
|
77
|
+
def get_rule_refs(filenames)
|
78
|
+
categories, rules = determine_categories_rules(filenames)
|
79
|
+
logger.debug "Categories: #{categories}"
|
80
|
+
logger.debug "Rules: #{rules}"
|
81
|
+
|
82
|
+
# filter out all individual rules that are already covered by a complete category
|
83
|
+
categories.each do |cat|
|
84
|
+
rules.delete_if { |e| e.start_with?(cat) }
|
85
|
+
end
|
86
|
+
|
87
|
+
refs = Set[]
|
88
|
+
refs.merge(categories)
|
89
|
+
refs.merge(rules)
|
90
|
+
refs
|
91
|
+
end
|
92
|
+
|
93
|
+
def determine_categories_rules(filenames)
|
94
|
+
categories = Set[]
|
95
|
+
rules = Set[]
|
58
96
|
filenames.each do |filename|
|
59
|
-
match_data =
|
60
|
-
|
61
|
-
|
97
|
+
match_data = check_single_filename(filename)
|
98
|
+
|
99
|
+
unless match_data.nil?
|
100
|
+
if match_data.size == 2
|
101
|
+
categories.add("#{match_data[1]}.xml")
|
102
|
+
else
|
103
|
+
rules.add("#{match_data[1]}.xml/#{match_data[2]}")
|
104
|
+
end
|
62
105
|
end
|
63
106
|
|
64
|
-
|
65
|
-
nil
|
66
|
-
else
|
67
|
-
match_data[1]
|
68
|
-
end
|
107
|
+
next unless match_data.nil?
|
69
108
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
rule_sets.add(category)
|
75
|
-
end
|
109
|
+
logger.debug "Change doesn't match specific rule/category - enable all rules"
|
110
|
+
categories = ALL_CATEGORIES
|
111
|
+
rules.clear
|
112
|
+
break
|
76
113
|
end
|
77
|
-
|
114
|
+
[categories, rules]
|
115
|
+
end
|
116
|
+
|
117
|
+
def check_single_filename(filename)
|
118
|
+
logger.debug "Checking #{filename}"
|
119
|
+
match_data = %r{#{PATH_TO_PMD_JAVA_BASED_RULES}/([^/]+)/([^/]+)Rule.java}.match(filename)
|
120
|
+
match_data = %r{#{PATH_TO_PMD_XPATH_BASED_RULES}/([^/]+).xml}.match(filename) if match_data.nil?
|
121
|
+
logger.debug "Matches: #{match_data.inspect}"
|
122
|
+
match_data
|
78
123
|
end
|
79
124
|
|
80
|
-
def build_config_file(
|
81
|
-
if
|
125
|
+
def build_config_file(rule_refs)
|
126
|
+
if rule_refs.empty?
|
82
127
|
logger.info NO_JAVA_RULES_CHANGED_MESSAGE
|
83
128
|
return
|
84
129
|
end
|
85
130
|
|
86
|
-
|
87
|
-
|
88
|
-
|
131
|
+
if rule_refs == ALL_CATEGORIES
|
132
|
+
logger.debug 'All rules are used. Not generating a dynamic ruleset.'
|
133
|
+
logger.debug "Using the configured/default ruleset base_config=#{@options.base_config} "\
|
134
|
+
"patch_config=#{@options.patch_config}"
|
135
|
+
return
|
89
136
|
end
|
90
137
|
|
91
|
-
|
92
|
-
description.content = 'The ruleset generated by PmdTester dynamically'
|
93
|
-
|
94
|
-
write_dynamic_file(doc)
|
138
|
+
write_dynamic_file(rule_refs)
|
95
139
|
end
|
96
140
|
|
97
|
-
def
|
98
|
-
|
99
|
-
|
141
|
+
def write_dynamic_file(rule_refs)
|
142
|
+
logger.debug "Generating dynamic configuration for: #{[rule_refs]}"
|
143
|
+
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
144
|
+
xml.ruleset('xmlns' => 'http://pmd.sourceforge.net/ruleset/2.0.0',
|
145
|
+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
|
146
|
+
'xsi:schemaLocation' => 'http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd',
|
147
|
+
'name' => 'Dynamic PmdTester Ruleset') do
|
148
|
+
xml.description 'The ruleset generated by PmdTester dynamically'
|
149
|
+
rule_refs.each do |entry|
|
150
|
+
xml.rule('ref' => "category/java/#{entry}")
|
151
|
+
end
|
152
|
+
end
|
100
153
|
end
|
101
|
-
|
102
|
-
false
|
103
|
-
end
|
104
|
-
|
105
|
-
def write_dynamic_file(doc)
|
154
|
+
doc = builder.to_xml(indent: 4, encoding: 'UTF-8')
|
106
155
|
File.open(PATH_TO_DYNAMIC_CONFIG, 'w') do |x|
|
107
|
-
x << doc.
|
156
|
+
x << doc.gsub(/\n\s+\n/, "\n")
|
108
157
|
end
|
109
158
|
@options.base_config = PATH_TO_DYNAMIC_CONFIG
|
110
159
|
@options.patch_config = PATH_TO_DYNAMIC_CONFIG
|
@@ -6,16 +6,16 @@ module PmdTester
|
|
6
6
|
# Helper class that provides a simple progress logging
|
7
7
|
class SimpleProgressLogger
|
8
8
|
include PmdTester
|
9
|
-
def initialize(
|
10
|
-
@
|
9
|
+
def initialize(task_name)
|
10
|
+
@task_name = task_name
|
11
11
|
end
|
12
12
|
|
13
13
|
def start
|
14
|
-
logger.info "Starting
|
14
|
+
logger.info "Starting #{@task_name}"
|
15
15
|
message_counter = 1
|
16
16
|
@scheduler = Rufus::Scheduler.new
|
17
17
|
@scheduler.every '2m' do
|
18
|
-
logger.info "Still
|
18
|
+
logger.info "Still #{@task_name} (#{message_counter})..."
|
19
19
|
message_counter += 1
|
20
20
|
end
|
21
21
|
end
|
@@ -2,159 +2,91 @@
|
|
2
2
|
|
3
3
|
module PmdTester
|
4
4
|
# Building summary report to show the details about projects and pmd branchs
|
5
|
-
class SummaryReportBuilder
|
5
|
+
class SummaryReportBuilder
|
6
6
|
include PmdTester
|
7
|
-
|
8
|
-
|
9
|
-
PATCH_CONFIG_PATH = 'target/reports/diff/patch_config.xml'
|
10
|
-
INDEX_PATH = 'target/reports/diff/index.html'
|
11
|
-
|
12
|
-
def build(projects, base_name, patch_name)
|
13
|
-
@projects = projects
|
14
|
-
@base_details = get_branch_details(base_name)
|
15
|
-
@patch_details = get_branch_details(patch_name)
|
16
|
-
|
17
|
-
FileUtils.mkdir_p(REPORT_DIR) unless File.directory?(REPORT_DIR)
|
18
|
-
index = File.new(INDEX_PATH, 'w')
|
19
|
-
|
20
|
-
html_report = build_html_report('Summary report')
|
21
|
-
copy_css(REPORT_DIR)
|
22
|
-
|
23
|
-
index.puts html_report
|
24
|
-
index.close
|
25
|
-
|
26
|
-
logger.info 'Built summary report successfully!'
|
27
|
-
end
|
28
|
-
|
29
|
-
def get_branch_details(branch_name)
|
30
|
-
details = PmdBranchDetail.new(branch_name)
|
31
|
-
details.load
|
32
|
-
end
|
7
|
+
include LiquidRenderer
|
8
|
+
include ProjectHasher
|
33
9
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
10
|
+
REPORT_DIR = 'target/reports/diff'
|
11
|
+
BASE_CONFIG_NAME = 'base_config.xml'
|
12
|
+
PATCH_CONFIG_NAME = 'patch_config.xml'
|
38
13
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
build_branch_details_table(doc)
|
14
|
+
def write_all_projects(projects, base_details, patch_details)
|
15
|
+
projects.each do |project|
|
16
|
+
process_project(project, "#{REPORT_DIR}/#{project.name}")
|
43
17
|
end
|
44
|
-
|
18
|
+
logger.info 'Built all difference reports successfully!'
|
45
19
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
20
|
+
FileUtils.mkdir_p(REPORT_DIR)
|
21
|
+
write_structure(REPORT_DIR)
|
22
|
+
copy_configs(REPORT_DIR, base_details, patch_details)
|
23
|
+
write_index(REPORT_DIR, base_details, patch_details, projects)
|
24
|
+
logger.info "Built summary report successfully in #{REPORT_DIR}!"
|
51
25
|
end
|
52
26
|
|
53
|
-
|
54
|
-
doc.thead do
|
55
|
-
doc.tr do
|
56
|
-
doc.th 'Item'
|
57
|
-
doc.th 'base'
|
58
|
-
doc.th 'patch'
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
27
|
+
private
|
62
28
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
@patch_details.branch_name)
|
67
|
-
build_branch_table_row(doc, 'branch last commit sha', @base_details.branch_last_sha,
|
68
|
-
@patch_details.branch_last_sha)
|
69
|
-
build_branch_table_row(doc, 'branch last commit message', @base_details.branch_last_message,
|
70
|
-
@patch_details.branch_last_message)
|
71
|
-
build_branch_table_row(doc, 'total execution time', @base_details.format_execution_time,
|
72
|
-
@patch_details.format_execution_time)
|
73
|
-
build_branch_table_row(doc, 'jdk version', @base_details.jdk_version,
|
74
|
-
@patch_details.jdk_version)
|
75
|
-
build_branch_table_row(doc, 'language', @base_details.language,
|
76
|
-
@patch_details.language)
|
77
|
-
build_branch_config_table_row(doc)
|
78
|
-
end
|
29
|
+
def process_project(project, dir)
|
30
|
+
logger.info "Rendering #{project.name}..."
|
31
|
+
LiquidProjectRenderer.new.write_project_index(project, dir)
|
79
32
|
end
|
80
33
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
copy_branch_config_file(base_config_src_path, BASE_CONFIG_PATH)
|
86
|
-
doc.td(class: 'a') do
|
87
|
-
doc.a(href: './base_config.xml') { doc.text 'base config' }
|
88
|
-
end
|
89
|
-
patch_config_stc_path = @patch_details.target_branch_config_path
|
90
|
-
FileUtils.cp(patch_config_stc_path, PATCH_CONFIG_PATH)
|
91
|
-
doc.td(class: 'b') do
|
92
|
-
doc.a(href: './patch_config.xml') { doc.text 'patch config' }
|
93
|
-
end
|
94
|
-
end
|
34
|
+
def write_structure(target_root)
|
35
|
+
logger.info 'Copying resources...'
|
36
|
+
copy_resource('css', target_root)
|
37
|
+
copy_resource('js', target_root)
|
95
38
|
end
|
96
39
|
|
97
|
-
def
|
98
|
-
|
40
|
+
def copy_configs(target_root, base_details, patch_details)
|
41
|
+
copy_file(base_details.target_branch_config_path, "#{target_root}/#{BASE_CONFIG_NAME}")
|
42
|
+
copy_file(patch_details.target_branch_config_path, "#{target_root}/#{PATCH_CONFIG_NAME}")
|
99
43
|
end
|
100
44
|
|
101
|
-
def
|
102
|
-
|
103
|
-
doc.td(class: 'c') { doc.text item }
|
104
|
-
doc.td(class: 'a') { doc.text base }
|
105
|
-
doc.td(class: 'b') { doc.text patch }
|
106
|
-
end
|
45
|
+
def copy_file(src, dest)
|
46
|
+
FileUtils.cp(src, dest) if File.exist?(src)
|
107
47
|
end
|
108
48
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
112
|
-
|
49
|
+
def write_index(target_root, base_details, patch_details, projects)
|
50
|
+
projects = projects.map do |p|
|
51
|
+
{
|
52
|
+
'name' => p.name,
|
53
|
+
'tag' => p.tag,
|
54
|
+
'report_url' => "./#{p.name}/index.html",
|
55
|
+
**report_diff_to_h(p.report_diff)
|
56
|
+
}
|
113
57
|
end
|
114
|
-
end
|
115
58
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
59
|
+
env = {
|
60
|
+
'comparison_url' => create_comparison_url(base_details, patch_details),
|
61
|
+
'base' => to_liquid(base_details, BASE_CONFIG_NAME),
|
62
|
+
'patch' => to_liquid(patch_details, PATCH_CONFIG_NAME),
|
63
|
+
'projects' => projects
|
64
|
+
}
|
65
|
+
logger.info 'Writing /index.html...'
|
66
|
+
render_and_write('project_index.html', "#{target_root}/index.html", env)
|
121
67
|
end
|
122
68
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
doc.th 'project branch/tag'
|
128
|
-
doc.th 'removed|new errors size'
|
129
|
-
doc.th 'removed|new violations size'
|
130
|
-
doc.th 'removed|new configerrors size'
|
131
|
-
end
|
132
|
-
end
|
69
|
+
def create_comparison_url(base_details, patch_details)
|
70
|
+
base = CGI.escape(base_details.branch_name)
|
71
|
+
patch = CGI.escape(patch_details.branch_last_sha)
|
72
|
+
"https://github.com/pmd/pmd/compare/#{base}...#{patch}"
|
133
73
|
end
|
134
74
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
end
|
151
|
-
doc.td do
|
152
|
-
build_table_content_for(doc, project.removed_configerrors_size,
|
153
|
-
project.new_configerrors_size)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
75
|
+
def to_liquid(details, config_name)
|
76
|
+
{
|
77
|
+
'tree_url' => "https://github.com/pmd/pmd/tree/#{CGI.escape(details.branch_last_sha)}",
|
78
|
+
'name' => details.branch_name,
|
79
|
+
'tip' => {
|
80
|
+
'sha' => details.branch_last_sha,
|
81
|
+
'message' => details.branch_last_message
|
82
|
+
},
|
83
|
+
'timestamp' => details.timestamp,
|
84
|
+
'execution_time' => PmdReportDetail.convert_seconds(details.execution_time),
|
85
|
+
'jdk_info' => details.jdk_version,
|
86
|
+
'locale' => details.language,
|
87
|
+
'config_url' => config_name,
|
88
|
+
'pr_number' => details.pull_request
|
89
|
+
}
|
158
90
|
end
|
159
91
|
end
|
160
92
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PmdTester
|
4
|
+
# A collection of things, grouped by file.
|
5
|
+
#
|
6
|
+
# (Note: this replaces PmdErrors and PmdViolations)
|
7
|
+
class CollectionByFile
|
8
|
+
def initialize
|
9
|
+
# a hash of filename -> [list of items]
|
10
|
+
@hash = Hash.new([])
|
11
|
+
@total = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_all(filename, values)
|
15
|
+
return if values.empty?
|
16
|
+
|
17
|
+
if @hash.key?(filename)
|
18
|
+
@hash[filename].concat(values)
|
19
|
+
else
|
20
|
+
@hash[filename] = values
|
21
|
+
end
|
22
|
+
@total += values.size
|
23
|
+
end
|
24
|
+
|
25
|
+
def total_size
|
26
|
+
@total
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_files
|
30
|
+
@hash.keys
|
31
|
+
end
|
32
|
+
|
33
|
+
def num_files
|
34
|
+
@hash.size
|
35
|
+
end
|
36
|
+
|
37
|
+
def all_values
|
38
|
+
@hash.values.flatten
|
39
|
+
end
|
40
|
+
|
41
|
+
def each_value(&block)
|
42
|
+
@hash.each_value do |vs|
|
43
|
+
vs.each(&block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](fname)
|
48
|
+
@hash[fname]
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_h
|
52
|
+
@hash
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|