danger-dangermattic 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.buildkite/gem-push.sh +15 -0
  3. data/.buildkite/pipeline.yml +69 -0
  4. data/.bundle/config +2 -0
  5. data/.github/workflows/reusable-check-labels-on-issues.yml +91 -0
  6. data/.github/workflows/reusable-run-danger.yml +54 -0
  7. data/.gitignore +30 -0
  8. data/.rubocop.yml +67 -0
  9. data/.ruby-version +1 -0
  10. data/.yardopts +7 -0
  11. data/CHANGELOG.md +7 -0
  12. data/Gemfile +5 -0
  13. data/Gemfile.lock +191 -0
  14. data/Guardfile +21 -0
  15. data/LICENSE +373 -0
  16. data/README.md +68 -0
  17. data/Rakefile +24 -0
  18. data/danger-dangermattic.gemspec +58 -0
  19. data/lib/danger_dangermattic.rb +3 -0
  20. data/lib/danger_plugin.rb +4 -0
  21. data/lib/dangermattic/gem_version.rb +5 -0
  22. data/lib/dangermattic/plugins/android_release_checker.rb +50 -0
  23. data/lib/dangermattic/plugins/android_strings_checker.rb +31 -0
  24. data/lib/dangermattic/plugins/android_unit_test_checker.rb +187 -0
  25. data/lib/dangermattic/plugins/common/common_release_checker.rb +113 -0
  26. data/lib/dangermattic/plugins/common/git_utils.rb +166 -0
  27. data/lib/dangermattic/plugins/common/github_utils.rb +68 -0
  28. data/lib/dangermattic/plugins/common/reporter.rb +38 -0
  29. data/lib/dangermattic/plugins/ios_release_checker.rb +106 -0
  30. data/lib/dangermattic/plugins/labels_checker.rb +74 -0
  31. data/lib/dangermattic/plugins/manifest_pr_checker.rb +106 -0
  32. data/lib/dangermattic/plugins/milestone_checker.rb +98 -0
  33. data/lib/dangermattic/plugins/podfile_checker.rb +122 -0
  34. data/lib/dangermattic/plugins/pr_size_checker.rb +125 -0
  35. data/lib/dangermattic/plugins/tracks_checker.rb +72 -0
  36. data/lib/dangermattic/plugins/view_changes_checker.rb +46 -0
  37. data/spec/android_release_checker_spec.rb +93 -0
  38. data/spec/android_strings_checker_spec.rb +185 -0
  39. data/spec/android_unit_test_checker_spec.rb +343 -0
  40. data/spec/common_release_checker_spec.rb +70 -0
  41. data/spec/fixtures/android_unit_test_checker/Abc.java +7 -0
  42. data/spec/fixtures/android_unit_test_checker/AbcFeatureConfig.java +7 -0
  43. data/spec/fixtures/android_unit_test_checker/Abcdef.kt +5 -0
  44. data/spec/fixtures/android_unit_test_checker/AbcdefgViewHelper.java +7 -0
  45. data/spec/fixtures/android_unit_test_checker/AnotherViewHelper.kt +7 -0
  46. data/spec/fixtures/android_unit_test_checker/MyNewClass.java +7 -0
  47. data/spec/fixtures/android_unit_test_checker/Polygon.kt +3 -0
  48. data/spec/fixtures/android_unit_test_checker/Shape.kt +10 -0
  49. data/spec/fixtures/android_unit_test_checker/TestsINeedThem.java +5 -0
  50. data/spec/fixtures/android_unit_test_checker/TestsINeedThem.kt +7 -0
  51. data/spec/fixtures/android_unit_test_checker/TestsINeedThem2.kt +12 -0
  52. data/spec/fixtures/android_unit_test_checker/src/android/java/org/activities/MyActivity.kt +7 -0
  53. data/spec/fixtures/android_unit_test_checker/src/android/java/org/activities/MyJavaActivity.java +7 -0
  54. data/spec/fixtures/android_unit_test_checker/src/android/java/org/fragments/MyFragment.kt +6 -0
  55. data/spec/fixtures/android_unit_test_checker/src/android/java/org/fragments/MyNewJavaFragment.java +7 -0
  56. data/spec/fixtures/android_unit_test_checker/src/android/java/org/module/MyModule.java +13 -0
  57. data/spec/fixtures/android_unit_test_checker/src/android/java/org/view/ActionCardViewHolder.kt +22 -0
  58. data/spec/fixtures/android_unit_test_checker/src/android/java/org/view/MyRecyclerView.java +7 -0
  59. data/spec/fixtures/android_unit_test_checker/src/androidTest/java/org/test/AbcTests.java +5 -0
  60. data/spec/fixtures/android_unit_test_checker/src/androidTest/java/org/test/AnotherTestClass.java +7 -0
  61. data/spec/fixtures/android_unit_test_checker/src/androidTest/java/org/test/PolygonTest.kt +4 -0
  62. data/spec/fixtures/android_unit_test_checker/src/androidTest/java/org/test/TestMyNewClass.java +9 -0
  63. data/spec/fixtures/android_unit_test_checker/src/androidTest/java/org/test/ToolTest.kt +5 -0
  64. data/spec/fixtures/android_unit_test_checker/src/main/java/org/wordpress/android/widgets/NestedWebView.kt +32 -0
  65. data/spec/fixtures/android_unit_test_checker/src/main/java/org/wordpress/util/config/BloggingPromptsFeatureConfig.kt +23 -0
  66. data/spec/fixtures/android_unit_test_checker/src/test/java/org/test/TestsINeedThem.java +9 -0
  67. data/spec/github_utils_spec.rb +110 -0
  68. data/spec/ios_release_checker_spec.rb +191 -0
  69. data/spec/labels_checker_spec.rb +169 -0
  70. data/spec/manifest_pr_checker_spec.rb +140 -0
  71. data/spec/milestone_checker_spec.rb +222 -0
  72. data/spec/podfile_checker_spec.rb +595 -0
  73. data/spec/pr_size_checker_spec.rb +250 -0
  74. data/spec/spec_helper.rb +115 -0
  75. data/spec/tracks_checker_spec.rb +156 -0
  76. data/spec/view_changes_checker_spec.rb +168 -0
  77. 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