gitlab-dangerfiles 0.1.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 (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.gitlab-ci.yml +43 -0
  4. data/.gitlab/merge_request_templates/Release.md +35 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +7 -0
  7. data/Guardfile +70 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +43 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/fixtures/emojis/aliases.json +542 -0
  14. data/fixtures/emojis/digests.json +12553 -0
  15. data/gitlab-dangerfiles.gemspec +38 -0
  16. data/lib/danger/changelog.rb +39 -0
  17. data/lib/danger/helper.rb +260 -0
  18. data/lib/danger/roulette.rb +135 -0
  19. data/lib/danger/sidekiq_queues.rb +37 -0
  20. data/lib/gitlab-dangerfiles.rb +1 -0
  21. data/lib/gitlab/Dangerfile +1 -0
  22. data/lib/gitlab/dangerfiles.rb +40 -0
  23. data/lib/gitlab/dangerfiles/bundle_size/Dangerfile +38 -0
  24. data/lib/gitlab/dangerfiles/ce_ee_vue_templates/Dangerfile +56 -0
  25. data/lib/gitlab/dangerfiles/changelog/Dangerfile +90 -0
  26. data/lib/gitlab/dangerfiles/changes_size/Dangerfile +17 -0
  27. data/lib/gitlab/dangerfiles/commit_linter.rb +226 -0
  28. data/lib/gitlab/dangerfiles/commit_messages/Dangerfile +135 -0
  29. data/lib/gitlab/dangerfiles/database/Dangerfile +67 -0
  30. data/lib/gitlab/dangerfiles/documentation/Dangerfile +29 -0
  31. data/lib/gitlab/dangerfiles/duplicate_yarn_dependencies/Dangerfile +29 -0
  32. data/lib/gitlab/dangerfiles/emoji_checker.rb +45 -0
  33. data/lib/gitlab/dangerfiles/eslint/Dangerfile +31 -0
  34. data/lib/gitlab/dangerfiles/frozen_string/Dangerfile +28 -0
  35. data/lib/gitlab/dangerfiles/karma/Dangerfile +51 -0
  36. data/lib/gitlab/dangerfiles/metadata/Dangerfile +50 -0
  37. data/lib/gitlab/dangerfiles/popen.rb +55 -0
  38. data/lib/gitlab/dangerfiles/prettier/Dangerfile +41 -0
  39. data/lib/gitlab/dangerfiles/roulette/Dangerfile +97 -0
  40. data/lib/gitlab/dangerfiles/sidekiq_queues/Dangerfile +27 -0
  41. data/lib/gitlab/dangerfiles/specs/Dangerfile +42 -0
  42. data/lib/gitlab/dangerfiles/tasks.rb +19 -0
  43. data/lib/gitlab/dangerfiles/teammate.rb +106 -0
  44. data/lib/gitlab/dangerfiles/telemetry/Dangerfile +32 -0
  45. data/lib/gitlab/dangerfiles/utility_css/Dangerfile +51 -0
  46. data/lib/gitlab/dangerfiles/version.rb +5 -0
  47. metadata +191 -0
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative File.expand_path("../../danger/commit_linter", __dir__)
4
+
5
+ COMMIT_MESSAGE_GUIDELINES = "https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#commit-messages-guidelines"
6
+ MORE_INFO = "For more information, take a look at our [Commit message guidelines](#{COMMIT_MESSAGE_GUIDELINES})."
7
+ THE_DANGER_JOB_TEXT = "the `danger-review` job"
8
+ MAX_COMMITS_COUNT = 10
9
+ MAX_COMMITS_COUNT_EXCEEDED_MESSAGE = <<~MSG
10
+ This merge request includes more than %<max_commits_count>d commits. Each commit should meet the following criteria:
11
+
12
+ 1. Have a well-written commit message.
13
+ 1. Has all tests passing when used on its own (e.g. when using git checkout SHA).
14
+ 1. Can be reverted on its own without also requiring the revert of commit that came before it.
15
+ 1. Is small enough that it can be reviewed in isolation in under 30 minutes or so.
16
+
17
+ If this merge request contains commits that do not meet this criteria and/or contains intermediate work, please rebase these commits into a smaller number of commits or split this merge request into multiple smaller merge requests.
18
+ MSG
19
+
20
+ def fail_commit(commit, message, more_info: true)
21
+ self.fail(build_message(commit, message, more_info: more_info))
22
+ end
23
+
24
+ def warn_commit(commit, message, more_info: true)
25
+ self.warn(build_message(commit, message, more_info: more_info))
26
+ end
27
+
28
+ def build_message(commit, message, more_info: true)
29
+ [message].tap do |full_message|
30
+ full_message << ". #{MORE_INFO}" if more_info
31
+ full_message.unshift("#{commit.sha}: ") if commit.sha
32
+ end.join
33
+ end
34
+
35
+ def squash_mr?
36
+ helper.ci? ? gitlab.mr_json['squash'] : false
37
+ end
38
+
39
+ def wip_mr?
40
+ helper.ci? ? gitlab.mr_json['work_in_progress'] : false
41
+ end
42
+
43
+ def danger_job_link
44
+ helper.ci? ? "[#{THE_DANGER_JOB_TEXT}](#{ENV['CI_JOB_URL']})" : THE_DANGER_JOB_TEXT
45
+ end
46
+
47
+ # Perform various checks against commits. We're not using
48
+ # https://github.com/jonallured/danger-commit_lint because its output is not
49
+ # very helpful, and it doesn't offer the means of ignoring merge commits.
50
+ def lint_commit(commit)
51
+ linter = Gitlab::Dangerfiles::CommitLinter.new(commit)
52
+
53
+ # For now we'll ignore merge commits, as getting rid of those is a problem
54
+ # separate from enforcing good commit messages.
55
+ return linter if linter.merge?
56
+
57
+ # We ignore revert commits as they are well structured by Git already
58
+ return linter if linter.revert?
59
+
60
+ # If MR is set to squash, we ignore fixup commits
61
+ return linter if linter.fixup? && squash_mr?
62
+
63
+ if linter.fixup?
64
+ msg = "Squash or fixup commits must be squashed before merge, or enable squash merge option and re-run #{danger_job_link}."
65
+ if wip_mr? || squash_mr?
66
+ warn_commit(commit, msg, more_info: false)
67
+ else
68
+ fail_commit(commit, msg, more_info: false)
69
+ end
70
+
71
+ # Makes no sense to process other rules for fixup commits, they trigger just more noise
72
+ return linter
73
+ end
74
+
75
+ # Fail if a suggestion commit is used and squash is not enabled
76
+ if linter.suggestion?
77
+ unless squash_mr?
78
+ fail_commit(commit, "If you are applying suggestions, enable squash in the merge request and re-run #{danger_job_link}.", more_info: false)
79
+ end
80
+
81
+ return linter
82
+ end
83
+
84
+ linter.lint
85
+ end
86
+
87
+ def lint_mr_title(mr_title)
88
+ commit = Struct.new(:message, :sha).new(mr_title)
89
+
90
+ Gitlab::Dangerfiles::CommitLinter.new(commit).lint_subject("merge request title")
91
+ end
92
+
93
+ def count_non_fixup_commits(commit_linters)
94
+ commit_linters.count { |commit_linter| !commit_linter.fixup? }
95
+ end
96
+
97
+ def lint_commits(commits)
98
+ commit_linters = commits.map { |commit| lint_commit(commit) }
99
+ failed_commit_linters = commit_linters.select { |commit_linter| commit_linter.failed? }
100
+ warn_or_fail_commits(failed_commit_linters, default_to_fail: !squash_mr?)
101
+
102
+ if count_non_fixup_commits(commit_linters) > MAX_COMMITS_COUNT
103
+ self.warn(format(MAX_COMMITS_COUNT_EXCEEDED_MESSAGE, max_commits_count: MAX_COMMITS_COUNT))
104
+ end
105
+
106
+ if squash_mr?
107
+ multi_line_commit_linter = commit_linters.detect { |commit_linter| !commit_linter.merge? && commit_linter.multi_line? }
108
+
109
+ if multi_line_commit_linter && multi_line_commit_linter.failed?
110
+ warn_or_fail_commits(multi_line_commit_linter)
111
+ else
112
+ title_linter = lint_mr_title(gitlab.mr_json['title'])
113
+ if title_linter.failed?
114
+ warn_or_fail_commits(title_linter)
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ def warn_or_fail_commits(failed_linters, default_to_fail: true)
121
+ level = default_to_fail ? :fail : :warn
122
+
123
+ Array(failed_linters).each do |linter|
124
+ linter.problems.each do |problem_key, problem_desc|
125
+ case problem_key
126
+ when :subject_above_warning
127
+ warn_commit(linter.commit, problem_desc)
128
+ else
129
+ self.__send__("#{level}_commit", linter.commit, problem_desc) # rubocop:disable GitlabSecurity/PublicSend
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ lint_commits(git.commits)
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ SCHEMA_NOT_UPDATED_MESSAGE_SHORT = <<~MSG
4
+ New %<migrations>s added but %<schema>s wasn't updated.
5
+ MSG
6
+
7
+ SCHEMA_NOT_UPDATED_MESSAGE_FULL = <<~MSG
8
+ **#{SCHEMA_NOT_UPDATED_MESSAGE_SHORT}**
9
+
10
+ Usually, when adding new %<migrations>s, %<schema>s should be
11
+ updated too (unless the migration isn't changing the DB schema
12
+ and isn't the most recent one).
13
+ MSG
14
+
15
+ DB_MESSAGE = <<~MSG
16
+ This merge request requires a database review. To make sure these
17
+ changes are reviewed, take the following steps:
18
+
19
+ 1. Ensure the merge request has ~database and ~"database::review pending" labels.
20
+ If the merge request modifies database files, Danger will do this for you.
21
+ 1. Prepare your MR for database review according to the
22
+ [docs](https://docs.gitlab.com/ee/development/database_review.html#how-to-prepare-the-merge-request-for-a-database-review).
23
+ 1. Assign and mention the database reviewer suggested by Reviewer Roulette.
24
+ MSG
25
+
26
+ DB_FILES_MESSAGE = <<~MSG
27
+ The following files require a review from the Database team:
28
+ MSG
29
+
30
+ DATABASE_APPROVED_LABEL = 'database::approved'
31
+
32
+ non_geo_db_schema_updated = !git.modified_files.grep(%r{\Adb/structure\.sql}).empty?
33
+ geo_db_schema_updated = !git.modified_files.grep(%r{\Aee/db/geo/schema\.rb}).empty?
34
+
35
+ non_geo_migration_created = !git.added_files.grep(%r{\A(db/(post_)?migrate)/}).empty?
36
+ geo_migration_created = !git.added_files.grep(%r{\Aee/db/geo/(post_)?migrate/}).empty?
37
+
38
+ format_str = helper.ci? ? SCHEMA_NOT_UPDATED_MESSAGE_FULL : SCHEMA_NOT_UPDATED_MESSAGE_SHORT
39
+
40
+ if non_geo_migration_created && !non_geo_db_schema_updated
41
+ warn format(format_str, migrations: 'migrations', schema: helper.html_link("db/structure.sql"))
42
+ end
43
+
44
+ if geo_migration_created && !geo_db_schema_updated
45
+ warn format(format_str, migrations: 'Geo migrations', schema: helper.html_link("ee/db/geo/schema.rb"))
46
+ end
47
+
48
+ return unless helper.ci?
49
+ return if gitlab.mr_labels.include?(DATABASE_APPROVED_LABEL)
50
+
51
+ db_paths_to_review = helper.changes_by_category[:database]
52
+
53
+ if gitlab.mr_labels.include?('database') || db_paths_to_review.any?
54
+ message 'This merge request adds or changes files that require a ' \
55
+ 'review from the [Database team](https://gitlab.com/groups/gl-database/-/group_members).'
56
+
57
+ markdown(DB_MESSAGE)
58
+ markdown(DB_FILES_MESSAGE + helper.markdown_list(db_paths_to_review)) if db_paths_to_review.any?
59
+
60
+ database_labels = helper.missing_database_labels(gitlab.mr_labels)
61
+
62
+ if database_labels.any?
63
+ gitlab.api.update_merge_request(gitlab.mr_json['project_id'],
64
+ gitlab.mr_json['iid'],
65
+ labels: (gitlab.mr_labels + database_labels).join(','))
66
+ end
67
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ docs_paths_to_review = helper.changes_by_category[:docs]
4
+
5
+ return if docs_paths_to_review.empty?
6
+
7
+ message 'This merge request adds or changes files that require a review ' \
8
+ 'from the Technical Writing team.'
9
+
10
+ return unless helper.ci?
11
+
12
+ markdown(<<~MARKDOWN)
13
+ ## Documentation review
14
+
15
+ The following files require a review from a technical writer:
16
+
17
+ * #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
18
+
19
+ The review does not need to block merging this merge request. See the:
20
+
21
+ - [Technical Writers assignments](https://about.gitlab.com/handbook/engineering/technical-writing/#designated-technical-writers) for the appropriate technical writer for this review.
22
+ - [Documentation workflows](https://docs.gitlab.com/ee/development/documentation/workflow.html) for information on when to assign a merge request for review.
23
+ MARKDOWN
24
+
25
+ unless gitlab.mr_labels.include?('documentation')
26
+ gitlab.api.update_merge_request(gitlab.mr_json['project_id'],
27
+ gitlab.mr_json['iid'],
28
+ labels: (gitlab.mr_labels + ['documentation']).join(','))
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ return unless helper.all_changed_files.include?('yarn.lock')
4
+
5
+ duplicate = `node_modules/.bin/yarn-deduplicate --list --strategy fewer yarn.lock`
6
+ .split(/$/)
7
+ .map(&:strip)
8
+ .reject(&:empty?)
9
+
10
+ return if duplicate.empty?
11
+
12
+ warn 'This merge request has introduced duplicated yarn dependencies.'
13
+
14
+ if helper.ci?
15
+ markdown(<<~MARKDOWN)
16
+ ## Duplicate yarn dependencies
17
+
18
+ The following dependencies should be de-duplicated:
19
+
20
+ * #{duplicate.map { |path| "`#{path}`" }.join("\n* ")}
21
+
22
+ Please run the following command and commit the changes to `yarn.lock`:
23
+
24
+ ```
25
+ node_modules/.bin/yarn-deduplicate --strategy fewer yarn.lock \\
26
+ && yarn install
27
+ ```
28
+ MARKDOWN
29
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Gitlab
6
+ module Dangerfiles
7
+ class EmojiChecker
8
+ DIGESTS = File.expand_path("../../../fixtures/emojis/digests.json", __dir__)
9
+ ALIASES = File.expand_path("../../../fixtures/emojis/aliases.json", __dir__)
10
+
11
+ # A regex that indicates a piece of text _might_ include an Emoji. The regex
12
+ # alone is not enough, as we'd match `:foo:bar:baz`. Instead, we use this
13
+ # regex to save us from having to check for all possible emoji names when we
14
+ # know one definitely is not included.
15
+ LIKELY_EMOJI = /:[\+a-z0-9_\-]+:/.freeze
16
+
17
+ UNICODE_EMOJI_REGEX = %r{(
18
+ [\u{1F300}-\u{1F5FF}] |
19
+ [\u{1F1E6}-\u{1F1FF}] |
20
+ [\u{2700}-\u{27BF}] |
21
+ [\u{1F900}-\u{1F9FF}] |
22
+ [\u{1F600}-\u{1F64F}] |
23
+ [\u{1F680}-\u{1F6FF}] |
24
+ [\u{2600}-\u{26FF}]
25
+ )}x.freeze
26
+
27
+ def initialize
28
+ names = JSON.parse(File.read(DIGESTS)).keys +
29
+ JSON.parse(File.read(ALIASES)).keys
30
+
31
+ @emoji = names.map { |name| ":#{name}:" }
32
+ end
33
+
34
+ def includes_text_emoji?(text)
35
+ return false unless text.match?(LIKELY_EMOJI)
36
+
37
+ @emoji.any? { |emoji| text.include?(emoji) }
38
+ end
39
+
40
+ def includes_unicode_emoji?(text)
41
+ text.match?(UNICODE_EMOJI_REGEX)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ def get_eslint_files(files)
4
+ files.select do |file|
5
+ file.end_with?('.js', '.vue') &&
6
+ File.read(file).include?('/* eslint-disable')
7
+ end
8
+ end
9
+
10
+ eslint_candidates = get_eslint_files(helper.all_changed_files)
11
+
12
+ return if eslint_candidates.empty?
13
+
14
+ warn 'This merge request changed files with disabled eslint rules. Please consider fixing them.'
15
+
16
+ if helper.ci?
17
+ markdown(<<~MARKDOWN)
18
+ ## Disabled eslint rules
19
+
20
+ The following files have disabled `eslint` rules. Please consider fixing them:
21
+
22
+ * #{eslint_candidates.map { |path| "`#{path}`" }.join("\n* ")}
23
+
24
+ Run the following command for more details
25
+
26
+ ```
27
+ node_modules/.bin/eslint --report-unused-disable-directives --no-inline-config \\
28
+ #{eslint_candidates.map { |path| " '#{path}'" }.join(" \\\n")}
29
+ ```
30
+ MARKDOWN
31
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ FILE_EXTENSION = ".rb"
4
+ MAGIC_COMMENT = "# frozen_string_literal: true"
5
+
6
+ def get_files_with_no_magic_comment(files)
7
+ files.select do |file|
8
+ file.end_with?(FILE_EXTENSION) &&
9
+ !File.open(file, &:gets)&.start_with?(MAGIC_COMMENT)
10
+ end
11
+ end
12
+
13
+ files_to_fix = get_files_with_no_magic_comment(git.added_files)
14
+
15
+ if files_to_fix.any?
16
+ warn 'This merge request adds files that do not enforce frozen string literal. ' \
17
+ 'See https://gitlab.com/gitlab-org/gitlab-foss/issues/47424 for more information.'
18
+
19
+ if helper.ci?
20
+ markdown(<<~MARKDOWN)
21
+ ## Enable Frozen String Literal
22
+
23
+ The following files should have `#{MAGIC_COMMENT}` on the first line:
24
+
25
+ * #{files_to_fix.map { |path| "`#{path}`" }.join("\n* ")}
26
+ MARKDOWN
27
+ end
28
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:disable Style/SignalException
3
+
4
+ def get_karma_files(files)
5
+ files.select do |file|
6
+ file.start_with?('ee/spec/javascripts', 'spec/javascripts') &&
7
+ file.end_with?('_spec.js') &&
8
+ !file.end_with?('browser_spec.js')
9
+ end
10
+ end
11
+
12
+ new_karma_files = get_karma_files(git.added_files.to_a)
13
+
14
+ unless new_karma_files.empty?
15
+
16
+ if helper.ci?
17
+ markdown(<<~MARKDOWN)
18
+ ## New karma spec file
19
+
20
+ New frontend specs ([except `browser_specs`](https://gitlab.com/gitlab-org/gitlab/blob/3b6fe2f1077eedb0b8aff02a7350234f0b7dc4f9/spec/javascripts/lib/utils/browser_spec.js#L2)) should be
21
+ [written in jest](https://docs.gitlab.com/ee/development/testing_guide/frontend_testing.html#jest).
22
+
23
+ You have created the following tests, please migrate them over to jest:
24
+
25
+ * #{new_karma_files.map { |path| "`#{path}`" }.join("\n* ")}
26
+ MARKDOWN
27
+ end
28
+
29
+ fail "You have created a new karma spec file"
30
+
31
+ end
32
+
33
+ changed_karma_files = get_karma_files(helper.all_changed_files) - new_karma_files
34
+
35
+ return if changed_karma_files.empty?
36
+
37
+ warn 'You have edited karma spec files. Please consider migrating them to jest.'
38
+
39
+ if helper.ci?
40
+ markdown(<<~MARKDOWN)
41
+ ## Edited karma files
42
+
43
+ You have edited the following karma spec files. Please consider migrating them to jest:
44
+
45
+ * #{changed_karma_files.map { |path| "`#{path}`" }.join("\n* ")}
46
+
47
+ In order to align with our Iteration value, migration can also be done as a follow-up.
48
+
49
+ For more information: [Jestodus epic](https://gitlab.com/groups/gitlab-org/-/epics/895)
50
+ MARKDOWN
51
+ end
@@ -0,0 +1,50 @@
1
+ # rubocop:disable Style/SignalException
2
+
3
+ THROUGHPUT_LABELS = [
4
+ 'Community contribution',
5
+ 'security',
6
+ 'bug',
7
+ 'backstage', # To be removed by https://gitlab.com/gitlab-org/gitlab/-/issues/222360.
8
+ 'feature',
9
+ 'feature::addition',
10
+ 'feature::maintenance',
11
+ 'tooling',
12
+ 'tooling::pipelines',
13
+ 'tooling::workflow',
14
+ 'documentation'
15
+ ].freeze
16
+
17
+ if gitlab.mr_body.size < 5
18
+ fail "Please provide a proper merge request description."
19
+ end
20
+
21
+ if gitlab.mr_labels.empty?
22
+ fail "Please add labels to this merge request."
23
+ end
24
+
25
+ if (THROUGHPUT_LABELS & gitlab.mr_labels).empty?
26
+ warn 'Please add a [throughput label](https://about.gitlab.com/handbook/engineering/management/throughput/#implementation) to this merge request.'
27
+ end
28
+
29
+ unless gitlab.mr_json["assignee"]
30
+ warn "This merge request does not have any assignee yet. Setting an assignee clarifies who needs to take action on the merge request at any given time."
31
+ end
32
+
33
+ has_milestone = !gitlab.mr_json["milestone"].nil?
34
+
35
+ unless has_milestone
36
+ warn "This merge request does not refer to an existing milestone.", sticky: false
37
+ end
38
+
39
+ has_pick_into_stable_label = gitlab.mr_labels.find { |label| label.start_with?('Pick into') }
40
+
41
+ if gitlab.branch_for_base != "master" && !has_pick_into_stable_label && !helper.security_mr?
42
+ warn "Most of the time, merge requests should target `master`. Otherwise, please set the relevant `Pick into X.Y` label."
43
+ end
44
+
45
+ if gitlab.mr_json['title'].length > 72
46
+ warn 'The title of this merge request is longer than 72 characters and ' \
47
+ 'would violate our commit message rules when using the Squash on Merge ' \
48
+ 'feature. Please consider adjusting the title, or rebase the ' \
49
+ "commits manually and don't use Squash on Merge."
50
+ end