danger-dangermattic 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.buildkite/gem-push.sh +15 -0
- data/.buildkite/pipeline.yml +69 -0
- data/.bundle/config +2 -0
- data/.github/workflows/reusable-check-labels-on-issues.yml +91 -0
- data/.github/workflows/reusable-run-danger.yml +54 -0
- data/.gitignore +30 -0
- data/.rubocop.yml +67 -0
- data/.ruby-version +1 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +191 -0
- data/Guardfile +21 -0
- data/LICENSE +373 -0
- data/README.md +68 -0
- data/Rakefile +24 -0
- data/danger-dangermattic.gemspec +58 -0
- data/lib/danger_dangermattic.rb +3 -0
- data/lib/danger_plugin.rb +4 -0
- data/lib/dangermattic/gem_version.rb +5 -0
- data/lib/dangermattic/plugins/android_release_checker.rb +50 -0
- data/lib/dangermattic/plugins/android_strings_checker.rb +31 -0
- data/lib/dangermattic/plugins/android_unit_test_checker.rb +187 -0
- data/lib/dangermattic/plugins/common/common_release_checker.rb +113 -0
- data/lib/dangermattic/plugins/common/git_utils.rb +166 -0
- data/lib/dangermattic/plugins/common/github_utils.rb +68 -0
- data/lib/dangermattic/plugins/common/reporter.rb +38 -0
- data/lib/dangermattic/plugins/ios_release_checker.rb +106 -0
- data/lib/dangermattic/plugins/labels_checker.rb +74 -0
- data/lib/dangermattic/plugins/manifest_pr_checker.rb +106 -0
- data/lib/dangermattic/plugins/milestone_checker.rb +98 -0
- data/lib/dangermattic/plugins/podfile_checker.rb +122 -0
- data/lib/dangermattic/plugins/pr_size_checker.rb +125 -0
- data/lib/dangermattic/plugins/tracks_checker.rb +72 -0
- data/lib/dangermattic/plugins/view_changes_checker.rb +46 -0
- data/spec/android_release_checker_spec.rb +93 -0
- data/spec/android_strings_checker_spec.rb +185 -0
- data/spec/android_unit_test_checker_spec.rb +343 -0
- data/spec/common_release_checker_spec.rb +70 -0
- data/spec/fixtures/android_unit_test_checker/Abc.java +7 -0
- data/spec/fixtures/android_unit_test_checker/AbcFeatureConfig.java +7 -0
- data/spec/fixtures/android_unit_test_checker/Abcdef.kt +5 -0
- data/spec/fixtures/android_unit_test_checker/AbcdefgViewHelper.java +7 -0
- data/spec/fixtures/android_unit_test_checker/AnotherViewHelper.kt +7 -0
- data/spec/fixtures/android_unit_test_checker/MyNewClass.java +7 -0
- data/spec/fixtures/android_unit_test_checker/Polygon.kt +3 -0
- data/spec/fixtures/android_unit_test_checker/Shape.kt +10 -0
- data/spec/fixtures/android_unit_test_checker/TestsINeedThem.java +5 -0
- data/spec/fixtures/android_unit_test_checker/TestsINeedThem.kt +7 -0
- data/spec/fixtures/android_unit_test_checker/TestsINeedThem2.kt +12 -0
- data/spec/fixtures/android_unit_test_checker/src/android/java/org/activities/MyActivity.kt +7 -0
- data/spec/fixtures/android_unit_test_checker/src/android/java/org/activities/MyJavaActivity.java +7 -0
- data/spec/fixtures/android_unit_test_checker/src/android/java/org/fragments/MyFragment.kt +6 -0
- data/spec/fixtures/android_unit_test_checker/src/android/java/org/fragments/MyNewJavaFragment.java +7 -0
- data/spec/fixtures/android_unit_test_checker/src/android/java/org/module/MyModule.java +13 -0
- data/spec/fixtures/android_unit_test_checker/src/android/java/org/view/ActionCardViewHolder.kt +22 -0
- data/spec/fixtures/android_unit_test_checker/src/android/java/org/view/MyRecyclerView.java +7 -0
- data/spec/fixtures/android_unit_test_checker/src/androidTest/java/org/test/AbcTests.java +5 -0
- data/spec/fixtures/android_unit_test_checker/src/androidTest/java/org/test/AnotherTestClass.java +7 -0
- data/spec/fixtures/android_unit_test_checker/src/androidTest/java/org/test/PolygonTest.kt +4 -0
- data/spec/fixtures/android_unit_test_checker/src/androidTest/java/org/test/TestMyNewClass.java +9 -0
- data/spec/fixtures/android_unit_test_checker/src/androidTest/java/org/test/ToolTest.kt +5 -0
- data/spec/fixtures/android_unit_test_checker/src/main/java/org/wordpress/android/widgets/NestedWebView.kt +32 -0
- data/spec/fixtures/android_unit_test_checker/src/main/java/org/wordpress/util/config/BloggingPromptsFeatureConfig.kt +23 -0
- data/spec/fixtures/android_unit_test_checker/src/test/java/org/test/TestsINeedThem.java +9 -0
- data/spec/github_utils_spec.rb +110 -0
- data/spec/ios_release_checker_spec.rb +191 -0
- data/spec/labels_checker_spec.rb +169 -0
- data/spec/manifest_pr_checker_spec.rb +140 -0
- data/spec/milestone_checker_spec.rb +222 -0
- data/spec/podfile_checker_spec.rb +595 -0
- data/spec/pr_size_checker_spec.rb +250 -0
- data/spec/spec_helper.rb +115 -0
- data/spec/tracks_checker_spec.rb +156 -0
- data/spec/view_changes_checker_spec.rb +168 -0
- metadata +341 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Danger
|
4
|
+
# Plugin for checking labels associated with a pull request.
|
5
|
+
#
|
6
|
+
# @example Checking for specific labels and generating warnings/errors:
|
7
|
+
# labels_checker.check(
|
8
|
+
# do_not_merge_labels: ['Do Not Merge'],
|
9
|
+
# required_labels: ['Bug', 'Enhancement'],
|
10
|
+
# required_labels_error: 'Please ensure the PR has labels "Bug" or "Enhancement".',
|
11
|
+
# recommended_labels: ['Documentation'],
|
12
|
+
# recommended_labels_warning: 'Consider adding the "Documentation" label for better tracking.'
|
13
|
+
# )
|
14
|
+
#
|
15
|
+
# @see Automattic/dangermattic
|
16
|
+
# @tags github, process
|
17
|
+
#
|
18
|
+
class LabelsChecker < Plugin
|
19
|
+
# Checks if a PR is missing labels or is marked with labels for not merging.
|
20
|
+
# If recommended labels are missing, the plugin will emit a warning. If a required label is missing, or the PR
|
21
|
+
# has a label indicating that the PR should not be merged, an error will be emitted, preventing the final merge.
|
22
|
+
#
|
23
|
+
# @param do_not_merge_labels [Array<String>] The possible labels indicating that a merge should not be allowed.
|
24
|
+
# @param required_labels [Array<Regexp>] The list of Regular Expressions describing all the type of labels that are *required* on PR (e.g. `[/^feature:/, `/^type:/]` or `bug|bugfix-exemption`).
|
25
|
+
# Defaults to an empty array if not provided.
|
26
|
+
# @param required_labels_error [String] The error message displayed if the required labels are not present.
|
27
|
+
# Defaults to a generic message that includes the missing label's regexes.
|
28
|
+
# @param recommended_labels [Array<Regexp>] The list of Regular Expressions describing all the type of labels that we want a PR to have,
|
29
|
+
# with a warning if it doesn't (e.g. `[/^feature:/, `/^type:/]` or `bug|bugfix-exemption`).
|
30
|
+
# Defaults to an empty array if not provided.
|
31
|
+
# @param recommended_labels_warning [String] The warning message displayed if the recommended labels are not present.
|
32
|
+
# Defaults to a generic message that includes the missing label's regexes.
|
33
|
+
#
|
34
|
+
# @note Tip: if you want to require or recommend "at least one label", you can use
|
35
|
+
# an array of a single empty regex `[//]` to match "a label with any name".
|
36
|
+
#
|
37
|
+
# @return [void]
|
38
|
+
def check(do_not_merge_labels: [], required_labels: [], required_labels_error: nil, recommended_labels: [], recommended_labels_warning: nil)
|
39
|
+
github_labels = danger.github.pr_labels
|
40
|
+
|
41
|
+
# A PR shouldn't be merged with the 'DO NOT MERGE' label
|
42
|
+
found_do_not_merge_labels = github_labels.select do |github_label|
|
43
|
+
do_not_merge_labels&.any? { |label| github_label.casecmp?(label) }
|
44
|
+
end
|
45
|
+
|
46
|
+
failure("This PR is tagged with #{markdown_list_string(found_do_not_merge_labels)} label(s).") unless found_do_not_merge_labels.empty?
|
47
|
+
|
48
|
+
# fail if a PR is missing any of the required labels
|
49
|
+
check_missing_labels(labels: github_labels, expected_labels: required_labels, report_on_missing: :error, custom_message: required_labels_error)
|
50
|
+
|
51
|
+
# warn if a PR is missing any of the recommended labels
|
52
|
+
check_missing_labels(labels: github_labels, expected_labels: recommended_labels, report_on_missing: :warning, custom_message: recommended_labels_warning)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def check_missing_labels(labels:, expected_labels:, report_on_missing:, custom_message: nil)
|
58
|
+
missing_expected_labels = expected_labels.reject do |required_label|
|
59
|
+
labels.any? { |label| label =~ required_label }
|
60
|
+
end
|
61
|
+
|
62
|
+
return if missing_expected_labels.empty?
|
63
|
+
|
64
|
+
missing_labels_list = missing_expected_labels.map(&:source)
|
65
|
+
message = custom_message || "PR is missing label(s) matching: #{markdown_list_string(missing_labels_list)}"
|
66
|
+
|
67
|
+
reporter.report(message: message, type: report_on_missing)
|
68
|
+
end
|
69
|
+
|
70
|
+
def markdown_list_string(items)
|
71
|
+
items.map { |item| "`#{item}`" }.join(', ')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Danger
|
4
|
+
# Plugin to check if the a lock file (Gemfile.lock, Podfile.lock, Package.resolved) was updated when changing a manifest
|
5
|
+
# file (Gemfile, Podfile, Package.swift) in a PR.
|
6
|
+
#
|
7
|
+
# @example Running manifest / lock checks
|
8
|
+
#
|
9
|
+
# # Check all manifest files (Gemfile, Podfile, Package.swift) have a corresponding lock change
|
10
|
+
# checker.check_all_manifest_lock_updated
|
11
|
+
#
|
12
|
+
# @example Gemfile check
|
13
|
+
#
|
14
|
+
# # Check if the Gemfile and the Gemfile.lock are both updated
|
15
|
+
# checker.check_gemfile_lock_updated
|
16
|
+
#
|
17
|
+
# @example Podfile check
|
18
|
+
#
|
19
|
+
# # Check if the Podfile and the Podfile.lock are both updated
|
20
|
+
# checker.check_podfile_lock_updated
|
21
|
+
#
|
22
|
+
# @example Package.swift check
|
23
|
+
#
|
24
|
+
# # Check if the Package.swift and the Package.resolved are both updated
|
25
|
+
# checker.check_swift_package_resolved_updated
|
26
|
+
#
|
27
|
+
# @see Automattic/dangermattic
|
28
|
+
# @tags ios, android
|
29
|
+
#
|
30
|
+
class ManifestPRChecker < Plugin
|
31
|
+
MESSAGE = '`%s` was changed without updating its corresponding `%s`. %s.'
|
32
|
+
|
33
|
+
# Performs all the checks, asserting that changes on `Gemfile`, `Podfile` and `Package.swift` must have corresponding
|
34
|
+
# lock file changes.
|
35
|
+
#
|
36
|
+
# @param report_type [Symbol] (optional) The type of report for the message. Types: :error, :warning (default), :message.
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
def check_all_manifest_lock_updated(report_type: :warning)
|
40
|
+
check_gemfile_lock_updated(report_type: report_type)
|
41
|
+
check_podfile_lock_updated(report_type: report_type)
|
42
|
+
check_swift_package_resolved_updated(report_type: report_type)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Check if the `Gemfile` file was modified without a corresponding `Gemfile.lock` update
|
46
|
+
#
|
47
|
+
# @param report_type [Symbol] (optional) The type of report for the message. Types: :error, :warning (default), :message.
|
48
|
+
#
|
49
|
+
#
|
50
|
+
# @return [void]
|
51
|
+
def check_gemfile_lock_updated(report_type: :warning)
|
52
|
+
check_manifest_lock_updated(
|
53
|
+
file_name: 'Gemfile',
|
54
|
+
lock_file_name: 'Gemfile.lock',
|
55
|
+
instruction: 'Please run `bundle install` or `bundle update <updated_gem>`',
|
56
|
+
report_type: report_type
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Check if the `Podfile` file was modified without a corresponding `Podfile.lock` update
|
61
|
+
#
|
62
|
+
# @param report_type [Symbol] (optional) The type of report for the message. Types: :error, :warning (default), :message.
|
63
|
+
#
|
64
|
+
#
|
65
|
+
# @return [void]
|
66
|
+
def check_podfile_lock_updated(report_type: :warning)
|
67
|
+
check_manifest_lock_updated(
|
68
|
+
file_name: 'Podfile',
|
69
|
+
lock_file_name: 'Podfile.lock',
|
70
|
+
instruction: 'Please run `bundle exec pod install`',
|
71
|
+
report_type: report_type
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Check if the `Package.swift` file was modified without a corresponding `Package.resolved` update
|
76
|
+
#
|
77
|
+
# @param report_type [Symbol] (optional) The type of report for the message. Types: :error, :warning (default), :message.
|
78
|
+
#
|
79
|
+
#
|
80
|
+
# @return [void]
|
81
|
+
def check_swift_package_resolved_updated(report_type: :warning)
|
82
|
+
check_manifest_lock_updated(
|
83
|
+
file_name: 'Package.swift',
|
84
|
+
lock_file_name: 'Package.resolved',
|
85
|
+
instruction: 'Please resolve the Swift packages in Xcode',
|
86
|
+
report_type: report_type
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def check_manifest_lock_updated(file_name:, lock_file_name:, instruction:, report_type: :warning)
|
93
|
+
# Find all the modified manifest files
|
94
|
+
manifest_modified_files = git.modified_files.select { |f| File.basename(f) == file_name }
|
95
|
+
|
96
|
+
# For each manifest file, check if the corresponding lockfile (in the same dir) was also modified
|
97
|
+
manifest_modified_files.each do |manifest_file|
|
98
|
+
lockfile_modified = git.modified_files.any? { |f| File.dirname(f) == File.dirname(manifest_file) && File.basename(f) == lock_file_name }
|
99
|
+
next if lockfile_modified
|
100
|
+
|
101
|
+
message = format(MESSAGE, manifest_file, lock_file_name, instruction)
|
102
|
+
reporter.report(message: message, type: report_type)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Danger
|
4
|
+
# This plugin checks whether a pull request is assigned to a milestone and whether the milestone's due date is approaching.
|
5
|
+
#
|
6
|
+
# @example Check if a milestone is set
|
7
|
+
#
|
8
|
+
# # Check if PR is assigned to a milestone
|
9
|
+
# checker.check_milestone_set
|
10
|
+
#
|
11
|
+
# # Check if PR is assigned to a milestone, reporting an error if that's not the case
|
12
|
+
# checker.check_milestone_set(report_type: :error)
|
13
|
+
#
|
14
|
+
# @example Run a milestone check
|
15
|
+
#
|
16
|
+
# # Check if milestone due date is approaching, reporting a warning if the milestone is in less than 5 days
|
17
|
+
# checker.check_milestone_due_date(days_before_due: 5)
|
18
|
+
#
|
19
|
+
# @example Run a milestone check with custom parameters
|
20
|
+
#
|
21
|
+
# # Check if milestone due date is within 3 days, reporting an error if the due date has passed and in case there's no milestone set
|
22
|
+
# checker.check_milestone_due_date(days_before_due: 3, report_type: :error, report_type_if_no_milestone: :error)
|
23
|
+
#
|
24
|
+
# @example Run a milestone check with a custom milestone behaviour parameter
|
25
|
+
#
|
26
|
+
# # Check if milestone due date is approaching and don't report anything if no milestone is assigned
|
27
|
+
# checker.check_milestone_due_date(report_type_if_no_milestone: :none)
|
28
|
+
#
|
29
|
+
# @see Automattic/dangermattic
|
30
|
+
# @tags milestone, github, process
|
31
|
+
#
|
32
|
+
class MilestoneChecker < Plugin
|
33
|
+
# Checks if the pull request is assigned to a milestone.
|
34
|
+
#
|
35
|
+
# @return [void]
|
36
|
+
def check_milestone_set(report_type: :warning)
|
37
|
+
return unless milestone.nil?
|
38
|
+
|
39
|
+
message = 'PR is not assigned to a milestone.'
|
40
|
+
reporter.report(message: message, type: report_type)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Checks if the pull request's milestone is due to finish within a certain number of days.
|
44
|
+
#
|
45
|
+
# @param days_before_due [Integer] Number of days before the milestone due date for the check to apply.
|
46
|
+
# @param report_type [Symbol] (optional) The type of message for when the PR is has passed over the `days_before_due` threshold. Types: :error, :warning (default), :message.
|
47
|
+
# @param report_type_if_no_milestone [Symbol] The type of message for when the PR is not assigned to a milestone. Types: :error, :warning (default), :message. You can also pass :none to not leave a message when there is no milestone.
|
48
|
+
#
|
49
|
+
# @return [void]
|
50
|
+
def check_milestone_due_date(days_before_due:, report_type: :warning, report_type_if_no_milestone: :warning)
|
51
|
+
if milestone.nil?
|
52
|
+
check_milestone_set(report_type: report_type_if_no_milestone)
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
return unless pr_state != 'closed' && milestone_due_date
|
57
|
+
|
58
|
+
today = DateTime.now
|
59
|
+
|
60
|
+
seconds_threshold = days_before_due * 24 * 60 * 60
|
61
|
+
time_before_due_date = milestone_due_date.to_time.to_i - today.to_time.to_i
|
62
|
+
return unless time_before_due_date <= seconds_threshold
|
63
|
+
|
64
|
+
message_text = "This PR is assigned to the milestone [#{milestone_title}](#{milestone_url}). "
|
65
|
+
message_text += if time_before_due_date.positive?
|
66
|
+
"This milestone is due in less than #{days_before_due} days.\n" \
|
67
|
+
'Please make sure to get it merged by then or assign it to a milestone with a later deadline.'
|
68
|
+
else
|
69
|
+
"The due date for this milestone has already passed.\n" \
|
70
|
+
'Please assign it to a milestone with a later deadline or check whether the release for this milestone has already been finished.'
|
71
|
+
end
|
72
|
+
|
73
|
+
reporter.report(message: message_text, type: report_type)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def milestone
|
79
|
+
github.pr_json['milestone']
|
80
|
+
end
|
81
|
+
|
82
|
+
def milestone_due_date
|
83
|
+
milestone['due_on']
|
84
|
+
end
|
85
|
+
|
86
|
+
def milestone_title
|
87
|
+
milestone['title']
|
88
|
+
end
|
89
|
+
|
90
|
+
def milestone_url
|
91
|
+
milestone['html_url']
|
92
|
+
end
|
93
|
+
|
94
|
+
def pr_state
|
95
|
+
github.pr_json['state']
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Danger
|
6
|
+
# Plugin for checking that the Podfile.lock doesn't contain direct commit references.
|
7
|
+
#
|
8
|
+
# @example Checking a Podfile for commit references:
|
9
|
+
# podfile_checker.check_podfile_does_not_have_commit_references(podfile_lock_path: './aLib/Podfile.lock')
|
10
|
+
#
|
11
|
+
# @example Checking Git diffs for Podfile.lock commit references:
|
12
|
+
# podfile_checker.check_podfile_diff_does_not_have_commit_references
|
13
|
+
#
|
14
|
+
# @see Automattic/dangermattic
|
15
|
+
# @tags android, localization
|
16
|
+
#
|
17
|
+
class PodfileChecker < Plugin
|
18
|
+
PODFILE_LOCK = 'Podfile.lock'
|
19
|
+
PODFILE_LOCK_DEPENDENCIES_ENTRY = 'DEPENDENCIES'
|
20
|
+
DEFAULT_PODFILE_LOCK_PATH = './Podfile.lock'
|
21
|
+
|
22
|
+
# Check if the Podfile.lock contains any references to commit hashes and raise a failure if it does.
|
23
|
+
#
|
24
|
+
# @param podfile_lock_path [String] (optional) The path to the Podfile.lock file.
|
25
|
+
# Defaults to the `DEFAULT_PODFILE_LOCK_PATH` constant if not provided.
|
26
|
+
# @param report_type [Symbol] (optional) The type of report for the message. Types: :error (default), :warning, :message.
|
27
|
+
#
|
28
|
+
# @example Checking the default Podfile.lock:
|
29
|
+
# check_podfile_does_not_have_commit_references
|
30
|
+
#
|
31
|
+
# @example Checking a custom Podfile.lock at a specific path:
|
32
|
+
# check_podfile_does_not_have_commit_references(podfile_lock_path: '/path/to/Podfile.lock')
|
33
|
+
#
|
34
|
+
# @return [void]
|
35
|
+
def check_podfile_does_not_have_commit_references(podfile_lock_path: DEFAULT_PODFILE_LOCK_PATH, report_type: :error)
|
36
|
+
check_podfile_does_not_match(
|
37
|
+
regexp: COMMIT_REFERENCE_REGEXP,
|
38
|
+
podfile_lock_path: podfile_lock_path,
|
39
|
+
match_found_message_generator: ->(matches) { "Podfile reference(s) to a commit hash:\n```#{matches.join("\n")}```" },
|
40
|
+
report_type: report_type
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Check for Podfile references to commit hashes in the Podfile.lock in a pull request.
|
45
|
+
#
|
46
|
+
# @param report_type [Symbol] (optional) The type of report for the message. Types: :error, :warning (default), :message.
|
47
|
+
#
|
48
|
+
# @return [void]
|
49
|
+
def check_podfile_diff_does_not_have_commit_references(report_type: :warning)
|
50
|
+
warning_message = 'This PR adds a Podfile reference to a commit hash:'
|
51
|
+
check_podfile_diff_entries_do_not_match(regexp: COMMIT_REFERENCE_REGEXP, match_found_message: warning_message, report_type: report_type)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Check if the Podfile.lock contains any references to branches and raise a failure if it does.
|
55
|
+
#
|
56
|
+
# @param podfile_lock_path [String] (optional) The path to the Podfile.lock file.
|
57
|
+
# Defaults to the `DEFAULT_PODFILE_LOCK_PATH` constant if not provided.
|
58
|
+
# @param report_type [Symbol] (optional) The type of report for the message. Types: :error (default), :warning, :message.
|
59
|
+
#
|
60
|
+
# @example Checking the default Podfile.lock:
|
61
|
+
# check_podfile_does_not_have_branch_references
|
62
|
+
#
|
63
|
+
# @example Checking a custom Podfile.lock at a specific path:
|
64
|
+
# check_podfile_does_not_have_branch_references(podfile_lock_path: '/path/to/Podfile.lock')
|
65
|
+
#
|
66
|
+
# @return [void]
|
67
|
+
def check_podfile_does_not_have_branch_references(podfile_lock_path: DEFAULT_PODFILE_LOCK_PATH, report_type: :error)
|
68
|
+
check_podfile_does_not_match(
|
69
|
+
regexp: BRANCH_REFERENCE_REGEXP,
|
70
|
+
podfile_lock_path: podfile_lock_path,
|
71
|
+
match_found_message_generator: ->(matches) { "Podfile reference(s) to a branch:\n```#{matches.join("\n")}```" },
|
72
|
+
report_type: report_type
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Check for Podfile references to branches in the Podfile.lock in a pull request.
|
77
|
+
#
|
78
|
+
# @param report_type [Symbol] (optional) The type of report for the message. Types: :error, :warning (default), :message.
|
79
|
+
#
|
80
|
+
# @return [void]
|
81
|
+
def check_podfile_diff_does_not_have_branch_references(report_type: :warning)
|
82
|
+
warning_message = 'This PR adds a Podfile reference to a branch:'
|
83
|
+
check_podfile_diff_entries_do_not_match(regexp: BRANCH_REFERENCE_REGEXP, match_found_message: warning_message, report_type: report_type)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
COMMIT_REFERENCE_REGEXP = /\(from `\S+`, commit `\S+`\)/
|
89
|
+
BRANCH_REFERENCE_REGEXP = /\(from `\S+`, branch `\S+`\)/
|
90
|
+
|
91
|
+
def check_podfile_does_not_match(
|
92
|
+
regexp:,
|
93
|
+
podfile_lock_path:,
|
94
|
+
match_found_message_generator: ->(matches) { "Matches found in:\n#{matches.join("\n")}" },
|
95
|
+
report_type: :error
|
96
|
+
)
|
97
|
+
podfile_lock_contents = File.read(podfile_lock_path)
|
98
|
+
podfile_lock_data = YAML.load(podfile_lock_contents)
|
99
|
+
|
100
|
+
commit_references = []
|
101
|
+
podfile_lock_dependencies = podfile_lock_data[PODFILE_LOCK_DEPENDENCIES_ENTRY]
|
102
|
+
podfile_lock_dependencies&.each do |dependency|
|
103
|
+
commit_references << dependency if dependency.match?(regexp)
|
104
|
+
end
|
105
|
+
|
106
|
+
return if commit_references.empty?
|
107
|
+
|
108
|
+
message = match_found_message_generator.call(commit_references)
|
109
|
+
reporter.report(message: message, type: report_type)
|
110
|
+
end
|
111
|
+
|
112
|
+
def check_podfile_diff_entries_do_not_match(regexp:, match_found_message:, report_type:)
|
113
|
+
git_utils.check_added_diff_lines(
|
114
|
+
# Notice the lockfile name is not configurable because we check the basename from the files in the diff and one cannot change the name of the lockfile CocoaPods generates.
|
115
|
+
file_selector: ->(path) { File.basename(path) == PODFILE_LOCK },
|
116
|
+
line_matcher: ->(line) { line.match?(regexp) },
|
117
|
+
message: match_found_message,
|
118
|
+
report_type: report_type
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Danger
|
4
|
+
# Plugin to check the size of a Pull Request content and text body.
|
5
|
+
#
|
6
|
+
# @example Running a PR diff size check with default parameters
|
7
|
+
#
|
8
|
+
# # Check the total size of changes in the PR using the default parameters, reporting a warning if the PR is larger than 500
|
9
|
+
# pr_size_checker.check_diff_size(max_size: 500)
|
10
|
+
#
|
11
|
+
# @example Running a PR diff size check customizing the size, message and type of report
|
12
|
+
#
|
13
|
+
# # Check the total size of changes in the PR, reporting an error if the diff is larger than 1000 using the specified message
|
14
|
+
# pr_size_checker.check_diff_size(max_size: 1000, message: 'PR too large, 1000 is the max!!', report_type: :error)
|
15
|
+
#
|
16
|
+
# @example Running a PR diff size check on the specified files in part of the diff
|
17
|
+
#
|
18
|
+
# # Check the size of insertions in the files selected by the file_selector
|
19
|
+
# pr_size_checker.check_diff_size(file_selector: ->(file) { file.include?('/java/test/') }, type: :insertions)
|
20
|
+
#
|
21
|
+
# @example Running a PR description length check
|
22
|
+
#
|
23
|
+
# # Check the PR Body using the default parameters, reporting a warning if the PR is smaller than 10 characters
|
24
|
+
# pr_size_checker.check_pr_body(min_length: 10)
|
25
|
+
#
|
26
|
+
# @example Running a PR description length check with custom parameters
|
27
|
+
#
|
28
|
+
# # Check if the minimum length of the PR body is smaller than 20 characters, reporting an error using a custom error message
|
29
|
+
# pr_size_checker.check_pr_body(min_length: 20, message: 'Add a better description, 20 chars at least!!', report_type: :error)
|
30
|
+
#
|
31
|
+
# @see Automattic/dangermattic
|
32
|
+
# @tags github, pull request, process
|
33
|
+
#
|
34
|
+
class PRSizeChecker < Plugin
|
35
|
+
DEFAULT_DIFF_SIZE_MESSAGE_FORMAT = 'This PR is larger than %d lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.'
|
36
|
+
DEFAULT_MIN_PR_BODY_MESSAGE_FORMAT = 'The PR description appears very short, less than %d characters long. Please provide a summary of your changes in the PR description.'
|
37
|
+
|
38
|
+
# Check the size of the PR diff against a specified maximum size.
|
39
|
+
#
|
40
|
+
# @param max_size [Integer] The maximum allowed size for the diff.
|
41
|
+
# @param file_selector [Proc] Optional closure to filter the files in the diff to be used for size calculation.
|
42
|
+
# @param type [:insertions, :deletions, :all] The type of diff size to check. (default: :all)
|
43
|
+
# @param message [String] The message to display if the diff size exceeds the maximum. (default: DEFAULT_DIFF_SIZE_MESSAGE)
|
44
|
+
# @param report_type [Symbol] (optional) The type of report for the message. Types: :error, :warning (default), :message.
|
45
|
+
#
|
46
|
+
# @return [void]
|
47
|
+
def check_diff_size(max_size:, file_selector: nil, type: :all, message: format(DEFAULT_DIFF_SIZE_MESSAGE_FORMAT, max_size), report_type: :warning)
|
48
|
+
case type
|
49
|
+
when :insertions
|
50
|
+
reporter.report(message: message, type: report_type) if insertions_size(file_selector: file_selector) > max_size
|
51
|
+
when :deletions
|
52
|
+
reporter.report(message: message, type: report_type) if deletions_size(file_selector: file_selector) > max_size
|
53
|
+
when :all
|
54
|
+
reporter.report(message: message, type: report_type) if diff_size(file_selector: file_selector) > max_size
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Check the size of the Pull Request description (PR body) against a specified minimum size.
|
59
|
+
#
|
60
|
+
# @param min_length [Integer] The minimum allowed length for the PR body.
|
61
|
+
# @param message [String] The message to display if the length of the PR body is smaller than the minimum. (default: DEFAULT_MIN_PR_BODY_MESSAGE_FORMAT)
|
62
|
+
# @param report_type [Boolean] If true, fail the PR check when the PR body length is too small. (default: false)
|
63
|
+
#
|
64
|
+
# @return [void]
|
65
|
+
def check_pr_body(min_length:, message: format(DEFAULT_MIN_PR_BODY_MESSAGE_FORMAT, min_length), report_type: :warning)
|
66
|
+
return if danger.github.pr_body.length > min_length
|
67
|
+
|
68
|
+
reporter.report(message: message, type: report_type)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Calculate the total size of insertions in modified files that match the file selector.
|
72
|
+
#
|
73
|
+
# @param file_selector [Proc] Select the files to be used for the insertions calculation.
|
74
|
+
#
|
75
|
+
# @return [Integer] The total size of insertions in the selected modified files.
|
76
|
+
def insertions_size(file_selector: nil)
|
77
|
+
return danger.git.insertions unless file_selector
|
78
|
+
|
79
|
+
filtered_files = git_utils.all_changed_files.select(&file_selector)
|
80
|
+
|
81
|
+
filtered_files.sum do |file|
|
82
|
+
# stats for a file in the GitHub API might be nil, making `info_for_file()` crash
|
83
|
+
next 0 if danger.git.diff.stats[:files][file].nil?
|
84
|
+
|
85
|
+
danger.git.info_for_file(file)&.[](:insertions).to_i
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Calculate the total size of deletions in modified files that match the file selector.
|
90
|
+
#
|
91
|
+
# @param file_selector [Proc] Select the files to be used for the deletions calculation.
|
92
|
+
#
|
93
|
+
# @return [Integer] The total size of deletions in the selected modified files.
|
94
|
+
def deletions_size(file_selector: nil)
|
95
|
+
return danger.git.deletions unless file_selector
|
96
|
+
|
97
|
+
filtered_files = git_utils.all_changed_files.select(&file_selector)
|
98
|
+
|
99
|
+
filtered_files.sum do |file|
|
100
|
+
# stats for a file in the GitHub API might be nil, making `info_for_file()` crash
|
101
|
+
next 0 if danger.git.diff.stats[:files][file].nil?
|
102
|
+
|
103
|
+
danger.git.info_for_file(file)&.[](:deletions).to_i
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Calculate the total size of changes (insertions and deletions) in modified files that match the file selector.
|
108
|
+
#
|
109
|
+
# @param file_selector [Proc] Select the files to be used for the total insertions and deletions calculation.
|
110
|
+
#
|
111
|
+
# @return [Integer] The total size of changes in the selected modified files.
|
112
|
+
def diff_size(file_selector: nil)
|
113
|
+
return danger.git.lines_of_code unless file_selector
|
114
|
+
|
115
|
+
filtered_files = git_utils.all_changed_files.select(&file_selector)
|
116
|
+
|
117
|
+
filtered_files.sum do |file|
|
118
|
+
# stats for a file in the GitHub API might be nil, making `info_for_file()` crash
|
119
|
+
next 0 if danger.git.diff.stats[:files][file].nil?
|
120
|
+
|
121
|
+
danger.git.info_for_file(file)&.[](:deletions).to_i + danger.git.info_for_file(file)&.[](:insertions).to_i
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Danger
|
4
|
+
# Plugin for checking tracks-related changes and providing instructions in the PR if that's the case.
|
5
|
+
#
|
6
|
+
# @example Checking Tracks changes with default settings:
|
7
|
+
# tracks_checker.check_tracks_changes
|
8
|
+
#
|
9
|
+
# @example Checking Tracks changes with custom Tracks-related files and usage matchers:
|
10
|
+
# tracks_files = ['AnalyticsTracking.swift', 'AnalyticsHelper.swift']
|
11
|
+
# usage_matchers = [/AnalyticsHelper\.sendEvent/]
|
12
|
+
# tracks_checker.check_tracks_changes(tracks_files: tracks_files, tracks_usage_matchers: usage_matchers)
|
13
|
+
#
|
14
|
+
# @see Automattic/dangermattic
|
15
|
+
# @tags github, pull request, tracks, process
|
16
|
+
#
|
17
|
+
class TracksChecker < Plugin
|
18
|
+
TRACKS_PR_INSTRUCTIONS = <<~MESSAGE
|
19
|
+
This PR contains changes to Tracks-related logic. Please ensure (**author and reviewer**) the following are completed:
|
20
|
+
|
21
|
+
- The tracks events must be validated in the Tracks system.
|
22
|
+
- Verify the internal Tracks spreadsheet has also been updated.
|
23
|
+
- Please consider registering any new events.
|
24
|
+
MESSAGE
|
25
|
+
|
26
|
+
TRACKS_NO_LABEL_INSTRUCTION_FORMAT = "- The PR must be assigned the **%s** label.\n"
|
27
|
+
TRACKS_NO_LABEL_MESSAGE_FORMAT = 'Please ensure the PR has the `%s` label.'
|
28
|
+
|
29
|
+
# Checks the PR diff for changes in Tracks-related files and provides instructions if changes are detected
|
30
|
+
#
|
31
|
+
# @param tracks_files [Array<String>] List of Tracks-related file names to check
|
32
|
+
# @param tracks_usage_matchers [Array<Regexp>] List of regular expressions representing tracks usages to match the diff lines
|
33
|
+
# @param tracks_label [String] A label the check should validate the PR against
|
34
|
+
# @return [void]
|
35
|
+
def check_tracks_changes(tracks_files:, tracks_usage_matchers:, tracks_label:)
|
36
|
+
return unless changes_tracks_files?(tracks_files: tracks_files) || diff_has_tracks_changes?(tracks_usage_matchers: tracks_usage_matchers)
|
37
|
+
|
38
|
+
tracks_message = TRACKS_PR_INSTRUCTIONS
|
39
|
+
|
40
|
+
unless tracks_label.nil? || tracks_label.empty?
|
41
|
+
tracks_message += format(TRACKS_NO_LABEL_INSTRUCTION_FORMAT, tracks_label)
|
42
|
+
|
43
|
+
labels_checker.check(
|
44
|
+
do_not_merge_labels: [],
|
45
|
+
required_labels: [/#{Regexp.escape(tracks_label)}/],
|
46
|
+
required_labels_error: format(TRACKS_NO_LABEL_MESSAGE_FORMAT, tracks_label)
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Tracks-related changes detected: publishing instructions
|
51
|
+
message(tracks_message)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def changes_tracks_files?(tracks_files:)
|
57
|
+
git_utils.all_changed_files.any? do |file|
|
58
|
+
tracks_files.any? { |tracks_file| File.basename(file) == File.basename(tracks_file) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def diff_has_tracks_changes?(tracks_usage_matchers:)
|
63
|
+
matched_lines = git_utils.matching_lines_in_diff_files(
|
64
|
+
files: git_utils.all_changed_files,
|
65
|
+
line_matcher: ->(line) { tracks_usage_matchers.any? { |tracks_usage_match| line.match(tracks_usage_match) } },
|
66
|
+
change_type: nil
|
67
|
+
)
|
68
|
+
|
69
|
+
!matched_lines.empty?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Danger
|
4
|
+
# Plugin to make sure View file changes in a Pull Request will have accompanying screenshots in the PR description.
|
5
|
+
#
|
6
|
+
# @example Check if a PR changing views needs to have screenshots
|
7
|
+
#
|
8
|
+
# # If a PR has view changes, report a warning if there are no screenshots attached
|
9
|
+
# view_changes_checker.check
|
10
|
+
#
|
11
|
+
# @see Automattic/dangermattic
|
12
|
+
# @tags ios, android, swift, java, kotlin, screenshots
|
13
|
+
#
|
14
|
+
class ViewChangesChecker < Plugin
|
15
|
+
VIEW_EXTENSIONS_IOS = /(View|Button)\.(swift|m)$|\.xib$|\.storyboard$/
|
16
|
+
VIEW_EXTENSIONS_ANDROID = /(?i)(View|Button)\.(java|kt|xml)$/
|
17
|
+
|
18
|
+
MEDIA_IN_PR_BODY_PATTERNS = [
|
19
|
+
%r{https?://\S*\.(gif|jpg|jpeg|png|svg)},
|
20
|
+
%r{https?://\S*\.(mp4|avi|mov|mkv)},
|
21
|
+
%r{https?://\S*github\S+/\S+/assets/\d+/},
|
22
|
+
/!\[(.*?)\]\((.*?)\)/,
|
23
|
+
/<img\s+[^>]*src\s*=\s*[^>]*>/,
|
24
|
+
/<video\s+[^>]*src\s*=\s*[^>]*>/
|
25
|
+
].freeze
|
26
|
+
|
27
|
+
MESSAGE = 'View files have been modified, but no screenshot or video is included in the pull request. ' \
|
28
|
+
'Consider adding some for clarity.'
|
29
|
+
|
30
|
+
# Checks if view files have been modified and if a screenshot or video is included in the pull request body,
|
31
|
+
# displaying a warning if view files have been modified but no screenshot or video is included.
|
32
|
+
#
|
33
|
+
# @return [void]
|
34
|
+
def check
|
35
|
+
view_files_modified = git.modified_files.any? do |file|
|
36
|
+
VIEW_EXTENSIONS_IOS =~ file || VIEW_EXTENSIONS_ANDROID =~ file
|
37
|
+
end
|
38
|
+
|
39
|
+
pr_has_media = MEDIA_IN_PR_BODY_PATTERNS.any? do |pattern|
|
40
|
+
github.pr_body =~ pattern
|
41
|
+
end
|
42
|
+
|
43
|
+
warn(MESSAGE) if view_files_modified && !pr_has_media
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|