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.
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