gitlab-dangerfiles 0.1.0 → 0.6.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/danger/helper.rb +155 -113
  3. data/lib/danger/roulette.rb +68 -36
  4. data/lib/gitlab/dangerfiles.rb +0 -34
  5. data/lib/gitlab/dangerfiles/base_linter.rb +96 -0
  6. data/lib/gitlab/dangerfiles/commit_linter.rb +25 -103
  7. data/lib/gitlab/dangerfiles/merge_request_linter.rb +30 -0
  8. data/lib/gitlab/dangerfiles/teammate.rb +23 -9
  9. data/lib/gitlab/dangerfiles/title_linting.rb +38 -0
  10. data/lib/gitlab/dangerfiles/version.rb +1 -1
  11. data/lib/gitlab/dangerfiles/weightage.rb +10 -0
  12. data/lib/gitlab/dangerfiles/weightage/maintainers.rb +33 -0
  13. data/lib/gitlab/dangerfiles/weightage/reviewers.rb +65 -0
  14. metadata +9 -25
  15. data/lib/danger/changelog.rb +0 -39
  16. data/lib/danger/sidekiq_queues.rb +0 -37
  17. data/lib/gitlab/dangerfiles/bundle_size/Dangerfile +0 -38
  18. data/lib/gitlab/dangerfiles/ce_ee_vue_templates/Dangerfile +0 -56
  19. data/lib/gitlab/dangerfiles/changelog/Dangerfile +0 -90
  20. data/lib/gitlab/dangerfiles/changes_size/Dangerfile +0 -17
  21. data/lib/gitlab/dangerfiles/commit_messages/Dangerfile +0 -135
  22. data/lib/gitlab/dangerfiles/database/Dangerfile +0 -67
  23. data/lib/gitlab/dangerfiles/documentation/Dangerfile +0 -29
  24. data/lib/gitlab/dangerfiles/duplicate_yarn_dependencies/Dangerfile +0 -29
  25. data/lib/gitlab/dangerfiles/eslint/Dangerfile +0 -31
  26. data/lib/gitlab/dangerfiles/frozen_string/Dangerfile +0 -28
  27. data/lib/gitlab/dangerfiles/karma/Dangerfile +0 -51
  28. data/lib/gitlab/dangerfiles/metadata/Dangerfile +0 -50
  29. data/lib/gitlab/dangerfiles/popen.rb +0 -55
  30. data/lib/gitlab/dangerfiles/prettier/Dangerfile +0 -41
  31. data/lib/gitlab/dangerfiles/roulette/Dangerfile +0 -97
  32. data/lib/gitlab/dangerfiles/sidekiq_queues/Dangerfile +0 -27
  33. data/lib/gitlab/dangerfiles/specs/Dangerfile +0 -42
  34. data/lib/gitlab/dangerfiles/tasks.rb +0 -19
  35. data/lib/gitlab/dangerfiles/telemetry/Dangerfile +0 -32
  36. data/lib/gitlab/dangerfiles/utility_css/Dangerfile +0 -51
@@ -2,39 +2,5 @@ require "gitlab/dangerfiles/version"
2
2
 
3
3
  module Gitlab
4
4
  module Dangerfiles
5
- LOCAL_RULES ||= %w[
6
- changes_size
7
- commit_messages
8
- database
9
- documentation
10
- duplicate_yarn_dependencies
11
- eslint
12
- frozen_string
13
- karma
14
- prettier
15
- telemetry
16
- utility_css
17
- ].freeze
18
- CI_ONLY_RULES ||= %w[
19
- ce_ee_vue_templates
20
- changelog
21
- metadata
22
- roulette
23
- sidekiq_queues
24
- specs
25
- ].freeze
26
- MESSAGE_PREFIX = "==>".freeze
27
-
28
- def self.load_tasks
29
- require "gitlab/dangerfiles/tasks"
30
- end
31
-
32
- def self.local_warning_message
33
- "#{MESSAGE_PREFIX} Only the following Danger rules can be run locally: #{LOCAL_RULES.join(", ")}"
34
- end
35
-
36
- def self.success_message
37
- "#{MESSAGE_PREFIX} No Danger rule violations!"
38
- end
39
5
  end
40
6
  end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "title_linting"
4
+
5
+ module Gitlab
6
+ module Dangerfiles
7
+ class BaseLinter
8
+ MIN_SUBJECT_WORDS_COUNT = 3
9
+ MAX_LINE_LENGTH = 72
10
+
11
+ attr_reader :commit, :problems
12
+
13
+ def self.problems_mapping
14
+ {
15
+ subject_too_short: "The %s must contain at least #{MIN_SUBJECT_WORDS_COUNT} words",
16
+ subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
17
+ subject_starts_with_lowercase: "The %s must start with a capital letter",
18
+ subject_ends_with_a_period: "The %s must not end with a period",
19
+ }
20
+ end
21
+
22
+ def self.subject_description
23
+ "commit subject"
24
+ end
25
+
26
+ def initialize(commit)
27
+ @commit = commit
28
+ @problems = {}
29
+ end
30
+
31
+ def failed?
32
+ problems.any?
33
+ end
34
+
35
+ def add_problem(problem_key, *args)
36
+ @problems[problem_key] = sprintf(self.class.problems_mapping[problem_key], *args)
37
+ end
38
+
39
+ def lint_subject
40
+ if subject_too_short?
41
+ add_problem(:subject_too_short, self.class.subject_description)
42
+ end
43
+
44
+ if subject_too_long?
45
+ add_problem(:subject_too_long, self.class.subject_description)
46
+ end
47
+
48
+ if subject_starts_with_lowercase?
49
+ add_problem(:subject_starts_with_lowercase, self.class.subject_description)
50
+ end
51
+
52
+ if subject_ends_with_a_period?
53
+ add_problem(:subject_ends_with_a_period, self.class.subject_description)
54
+ end
55
+
56
+ self
57
+ end
58
+
59
+ private
60
+
61
+ def subject
62
+ TitleLinting.remove_draft_flag(message_parts[0])
63
+ end
64
+
65
+ def subject_too_short?
66
+ subject.split(" ").length < MIN_SUBJECT_WORDS_COUNT
67
+ end
68
+
69
+ def subject_too_long?
70
+ line_too_long?(subject)
71
+ end
72
+
73
+ def line_too_long?(line)
74
+ line.length > MAX_LINE_LENGTH
75
+ end
76
+
77
+ def subject_starts_with_lowercase?
78
+ return false if ("A".."Z").cover?(subject[0])
79
+
80
+ first_char = subject.sub(/\A(\[.+\]|\w+:)\s/, "")[0]
81
+ first_char_downcased = first_char.downcase
82
+ return true unless ("a".."z").cover?(first_char_downcased)
83
+
84
+ first_char.downcase == first_char
85
+ end
86
+
87
+ def subject_ends_with_a_period?
88
+ subject.end_with?(".")
89
+ end
90
+
91
+ def message_parts
92
+ @message_parts ||= commit.message.split("\n", 3)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -1,40 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- emoji_checker_path = File.expand_path("emoji_checker", __dir__)
4
- defined?(Rails) ? require_dependency(emoji_checker_path) : require_relative(emoji_checker_path)
3
+ require_relative "emoji_checker"
5
4
 
6
5
  module Gitlab
7
6
  module Dangerfiles
8
7
  class CommitLinter
9
- MIN_SUBJECT_WORDS_COUNT = 3
10
- MAX_LINE_LENGTH = 72
11
- MAX_CHANGED_FILES_IN_COMMIT = 3
12
8
  MAX_CHANGED_LINES_IN_COMMIT = 30
13
- SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(#|!|&|%)\d+\b}.freeze
14
- DEFAULT_SUBJECT_DESCRIPTION = "commit subject"
15
- WIP_PREFIX = "WIP: "
16
- PROBLEMS = {
17
- subject_too_short: "The %s must contain at least #{MIN_SUBJECT_WORDS_COUNT} words",
18
- subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
19
- subject_starts_with_lowercase: "The %s must start with a capital letter",
20
- subject_ends_with_a_period: "The %s must not end with a period",
21
- separator_missing: "The commit subject and body must be separated by a blank line",
22
- details_too_many_changes: "Commits that change #{MAX_CHANGED_LINES_IN_COMMIT} or more lines across " \
23
- "at least #{MAX_CHANGED_FILES_IN_COMMIT} files must describe these changes in the commit body",
24
- details_line_too_long: "The commit body should not contain more than #{MAX_LINE_LENGTH} characters per line",
25
- message_contains_text_emoji: "Avoid the use of Markdown Emoji such as `:+1:`. These add limited value " \
26
- "to the commit message, and are displayed as plain text outside of GitLab",
27
- message_contains_unicode_emoji: "Avoid the use of Unicode Emoji. These add no value to the commit " \
28
- "message, and may not be displayed properly everywhere",
29
- message_contains_short_reference: "Use full URLs instead of short references (`gitlab-org/gitlab#123` or " \
30
- "`!123`), as short references are displayed as plain text outside of GitLab",
31
- }.freeze
32
-
33
- attr_reader :commit, :problems
9
+ SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(?<!`)(#|!|&|%)\d+(?<!`)}.freeze
10
+
11
+ def self.problems_mapping
12
+ super.merge(
13
+ {
14
+ separator_missing: "The commit subject and body must be separated by a blank line",
15
+ details_too_many_changes: "Commits that change #{MAX_CHANGED_LINES_IN_COMMIT} or more lines across " \
16
+ "at least #{MAX_CHANGED_FILES_IN_COMMIT} files must describe these changes in the commit body",
17
+ details_line_too_long: "The commit body should not contain more than #{MAX_LINE_LENGTH} characters per line",
18
+ message_contains_text_emoji: "Avoid the use of Markdown Emoji such as `:+1:`. These add limited value " \
19
+ "to the commit message, and are displayed as plain text outside of GitLab",
20
+ message_contains_unicode_emoji: "Avoid the use of Unicode Emoji. These add no value to the commit " \
21
+ "message, and may not be displayed properly everywhere",
22
+ message_contains_short_reference: "Use full URLs instead of short references (`gitlab-org/gitlab#123` or " \
23
+ "`!123`), as short references are displayed as plain text outside of GitLab",
24
+ }
25
+ )
26
+ end
34
27
 
35
28
  def initialize(commit)
36
- @commit = commit
37
- @problems = {}
29
+ super
30
+
38
31
  @linted = false
39
32
  end
40
33
 
@@ -58,19 +51,11 @@ module Gitlab
58
51
  !details.nil? && !details.empty?
59
52
  end
60
53
 
61
- def failed?
62
- problems.any?
63
- end
64
-
65
- def add_problem(problem_key, *args)
66
- @problems[problem_key] = sprintf(PROBLEMS[problem_key], *args)
67
- end
68
-
69
- def lint(subject_description = "commit subject")
54
+ def lint
70
55
  return self if @linted
71
56
 
72
57
  @linted = true
73
- lint_subject(subject_description)
58
+ lint_subject
74
59
  lint_separator
75
60
  lint_details
76
61
  lint_message
@@ -78,26 +63,6 @@ module Gitlab
78
63
  self
79
64
  end
80
65
 
81
- def lint_subject(subject_description)
82
- if subject_too_short?
83
- add_problem(:subject_too_short, subject_description)
84
- end
85
-
86
- if subject_too_long?
87
- add_problem(:subject_too_long, subject_description)
88
- end
89
-
90
- if subject_starts_with_lowercase?
91
- add_problem(:subject_starts_with_lowercase, subject_description)
92
- end
93
-
94
- if subject_ends_with_a_period?
95
- add_problem(:subject_ends_with_a_period, subject_description)
96
- end
97
-
98
- self
99
- end
100
-
101
66
  private
102
67
 
103
68
  def lint_separator
@@ -114,15 +79,11 @@ module Gitlab
114
79
  end
115
80
 
116
81
  details&.each_line do |line|
117
- line = line.strip
118
-
119
- next unless line_too_long?(line)
120
-
121
- url_size = line.scan(%r((https?://\S+))).sum { |(url)| url.length } # rubocop:disable CodeReuse/ActiveRecord
82
+ line_without_urls = line.strip.gsub(%r{https?://\S+}, "")
122
83
 
123
84
  # If the line includes a URL, we'll allow it to exceed MAX_LINE_LENGTH characters, but
124
85
  # only if the line _without_ the URL does not exceed this limit.
125
- next unless line_too_long?(line.length - url_size)
86
+ next unless line_too_long?(line_without_urls)
126
87
 
127
88
  add_problem(:details_line_too_long)
128
89
  break
@@ -159,10 +120,6 @@ module Gitlab
159
120
  files_changed > MAX_CHANGED_FILES_IN_COMMIT && lines_changed > MAX_CHANGED_LINES_IN_COMMIT
160
121
  end
161
122
 
162
- def subject
163
- message_parts[0].delete_prefix(WIP_PREFIX)
164
- end
165
-
166
123
  def separator
167
124
  message_parts[1]
168
125
  end
@@ -171,37 +128,6 @@ module Gitlab
171
128
  message_parts[2]&.gsub(/^Signed-off-by.*$/, "")
172
129
  end
173
130
 
174
- def line_too_long?(line)
175
- case line
176
- when String
177
- line.length > MAX_LINE_LENGTH
178
- when Integer
179
- line > MAX_LINE_LENGTH
180
- else
181
- raise ArgumentError, "The line argument (#{line}) should be a String or an Integer! #{line.class} given."
182
- end
183
- end
184
-
185
- def subject_too_short?
186
- subject.split(" ").length < MIN_SUBJECT_WORDS_COUNT
187
- end
188
-
189
- def subject_too_long?
190
- line_too_long?(subject)
191
- end
192
-
193
- def subject_starts_with_lowercase?
194
- first_char = subject.sub(/\A\[.+\]\s/, "")[0]
195
- first_char_downcased = first_char.downcase
196
- return true unless ("a".."z").cover?(first_char_downcased)
197
-
198
- first_char.downcase == first_char
199
- end
200
-
201
- def subject_ends_with_a_period?
202
- subject.end_with?(".")
203
- end
204
-
205
131
  def message_contains_text_emoji?
206
132
  emoji_checker.includes_text_emoji?(commit.message)
207
133
  end
@@ -217,10 +143,6 @@ module Gitlab
217
143
  def emoji_checker
218
144
  @emoji_checker ||= Gitlab::Dangerfiles::EmojiChecker.new
219
145
  end
220
-
221
- def message_parts
222
- @message_parts ||= commit.message.split("\n", 3)
223
- end
224
146
  end
225
147
  end
226
148
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_linter"
4
+
5
+ module Gitlab
6
+ module Dangerfiles
7
+ class MergeRequestLinter < BaseLinter
8
+ alias_method :lint, :lint_subject
9
+
10
+ def self.subject_description
11
+ "merge request title"
12
+ end
13
+
14
+ def self.mr_run_options_regex
15
+ [
16
+ "RUN AS-IF-FOSS",
17
+ "UPDATE CACHE",
18
+ "RUN ALL RSPEC",
19
+ "SKIP RSPEC FAIL-FAST",
20
+ ].join("|")
21
+ end
22
+
23
+ private
24
+
25
+ def subject
26
+ super.gsub(/\[?(#{self.class.mr_run_options_regex})\]?/, "").strip
27
+ end
28
+ end
29
+ end
30
+ end
@@ -3,27 +3,42 @@
3
3
  module Gitlab
4
4
  module Dangerfiles
5
5
  class Teammate
6
- attr_reader :username, :name, :role, :projects, :available, :tz_offset_hours
6
+ attr_reader :options, :username, :name, :role, :projects, :available, :hungry, :reduced_capacity, :tz_offset_hours
7
7
 
8
8
  # The options data are produced by https://gitlab.com/gitlab-org/gitlab-roulette/-/blob/master/lib/team_member.rb
9
9
  def initialize(options = {})
10
+ @options = options
10
11
  @username = options["username"]
11
12
  @name = options["name"]
12
13
  @markdown_name = options["markdown_name"]
13
14
  @role = options["role"]
14
15
  @projects = options["projects"]
15
16
  @available = options["available"]
17
+ @hungry = options["hungry"]
18
+ @reduced_capacity = options["reduced_capacity"]
16
19
  @tz_offset_hours = options["tz_offset_hours"]
17
20
  end
18
21
 
22
+ def to_h
23
+ options
24
+ end
25
+
26
+ def ==(other)
27
+ return false unless other.respond_to?(:username)
28
+
29
+ other.username == username
30
+ end
31
+
19
32
  def in_project?(name)
20
33
  projects&.has_key?(name)
21
34
  end
22
35
 
23
- # Traintainers also count as reviewers
36
+ def any_capability?(project, category)
37
+ capabilities(project).any? { |capability| capability.end_with?(category.to_s) }
38
+ end
39
+
24
40
  def reviewer?(project, category, labels)
25
- has_capability?(project, category, :reviewer, labels) ||
26
- traintainer?(project, category, labels)
41
+ has_capability?(project, category, :reviewer, labels)
27
42
  end
28
43
 
29
44
  def traintainer?(project, category, labels)
@@ -34,9 +49,7 @@ module Gitlab
34
49
  has_capability?(project, category, :maintainer, labels)
35
50
  end
36
51
 
37
- def markdown_name(timezone_experiment: false, author: nil)
38
- return @markdown_name unless timezone_experiment
39
-
52
+ def markdown_name(author: nil)
40
53
  "#{@markdown_name} (#{utc_offset_text(author)})"
41
54
  end
42
55
 
@@ -68,9 +81,9 @@ module Gitlab
68
81
 
69
82
  def offset_diff_compared_to_author(author)
70
83
  diff = floored_offset_hours - author.floored_offset_hours
71
- return "same timezone as `@#{author.username}`" if diff.zero?
84
+ return "same timezone as `@#{author.username}`" if diff == 0
72
85
 
73
- ahead_or_behind = diff < 0 ? "behind" : "ahead"
86
+ ahead_or_behind = diff < 0 ? "behind" : "ahead of"
74
87
  pluralized_hours = pluralize(diff.abs, "hour", "hours")
75
88
 
76
89
  "#{pluralized_hours} #{ahead_or_behind} `@#{author.username}`"
@@ -85,6 +98,7 @@ module Gitlab
85
98
  when :engineering_productivity
86
99
  return false unless role[/Engineering Productivity/]
87
100
  return true if kind == :reviewer
101
+ return true if capabilities(project).include?("#{kind} engineering_productivity")
88
102
 
89
103
  capabilities(project).include?("#{kind} backend")
90
104
  else
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module Dangerfiles
5
+ module TitleLinting
6
+ DRAFT_REGEX = /\A*#{Regexp.union(/(?i)(\[WIP\]\s*|WIP:\s*|WIP$)/, /(?i)(\[draft\]|\(draft\)|draft:|draft\s\-\s|draft$)/)}+\s*/i.freeze
7
+ CHERRY_PICK_REGEX = /cherry[\s-]*pick/i.freeze
8
+ RUN_ALL_RSPEC_REGEX = /RUN ALL RSPEC/i.freeze
9
+ RUN_AS_IF_FOSS_REGEX = /RUN AS-IF-FOSS/i.freeze
10
+
11
+ module_function
12
+
13
+ def sanitize_mr_title(title)
14
+ remove_draft_flag(title).gsub(/`/, '\\\`')
15
+ end
16
+
17
+ def remove_draft_flag(title)
18
+ title.gsub(DRAFT_REGEX, "")
19
+ end
20
+
21
+ def has_draft_flag?(title)
22
+ DRAFT_REGEX.match?(title)
23
+ end
24
+
25
+ def has_cherry_pick_flag?(title)
26
+ CHERRY_PICK_REGEX.match?(title)
27
+ end
28
+
29
+ def has_run_all_rspec_flag?(title)
30
+ RUN_ALL_RSPEC_REGEX.match?(title)
31
+ end
32
+
33
+ def has_run_as_if_foss_flag?(title)
34
+ RUN_AS_IF_FOSS_REGEX.match?(title)
35
+ end
36
+ end
37
+ end
38
+ end