danger-dangermattic 1.0.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 +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
|