gitlab-styles 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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.gitlab-ci.yml +13 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/default.yml +1229 -0
- data/gitlab-styles.gemspec +29 -0
- data/lib/gitlab/styles.rb +6 -0
- data/lib/gitlab/styles/rubocop.rb +30 -0
- data/lib/gitlab/styles/rubocop/cop/active_record_dependent.rb +30 -0
- data/lib/gitlab/styles/rubocop/cop/active_record_serialize.rb +22 -0
- data/lib/gitlab/styles/rubocop/cop/custom_error_class.rb +68 -0
- data/lib/gitlab/styles/rubocop/cop/gem_fetcher.rb +41 -0
- data/lib/gitlab/styles/rubocop/cop/in_batches.rb +20 -0
- data/lib/gitlab/styles/rubocop/cop/migration/add_column.rb +56 -0
- data/lib/gitlab/styles/rubocop/cop/migration/add_column_with_default_to_large_table.rb +59 -0
- data/lib/gitlab/styles/rubocop/cop/migration/add_concurrent_foreign_key.rb +31 -0
- data/lib/gitlab/styles/rubocop/cop/migration/add_concurrent_index.rb +38 -0
- data/lib/gitlab/styles/rubocop/cop/migration/add_index.rb +52 -0
- data/lib/gitlab/styles/rubocop/cop/migration/add_timestamps.rb +29 -0
- data/lib/gitlab/styles/rubocop/cop/migration/datetime.rb +40 -0
- data/lib/gitlab/styles/rubocop/cop/migration/hash_index.rb +55 -0
- data/lib/gitlab/styles/rubocop/cop/migration/remove_concurrent_index.rb +33 -0
- data/lib/gitlab/styles/rubocop/cop/migration/remove_index.rb +30 -0
- data/lib/gitlab/styles/rubocop/cop/migration/reversible_add_column_with_default.rb +39 -0
- data/lib/gitlab/styles/rubocop/cop/migration/safer_boolean_column.rb +98 -0
- data/lib/gitlab/styles/rubocop/cop/migration/timestamps.rb +31 -0
- data/lib/gitlab/styles/rubocop/cop/migration/update_column_in_batches.rb +45 -0
- data/lib/gitlab/styles/rubocop/cop/polymorphic_associations.rb +27 -0
- data/lib/gitlab/styles/rubocop/cop/project_path_helper.rb +55 -0
- data/lib/gitlab/styles/rubocop/cop/redirect_with_status.rb +48 -0
- data/lib/gitlab/styles/rubocop/cop/rspec/single_line_hook.rb +42 -0
- data/lib/gitlab/styles/rubocop/migration_helpers.rb +15 -0
- data/lib/gitlab/styles/rubocop/model_helpers.rb +15 -0
- data/lib/gitlab/styles/version.rb +5 -0
- metadata +169 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative '../../migration_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
module Migration
|
8
|
+
# Cop that checks if indexes are added in a concurrent manner.
|
9
|
+
class AddIndex < RuboCop::Cop::Cop
|
10
|
+
include MigrationHelpers
|
11
|
+
|
12
|
+
MSG = '`add_index` requires downtime, use `add_concurrent_index` instead'.freeze
|
13
|
+
|
14
|
+
def on_def(node)
|
15
|
+
return unless in_migration?(node)
|
16
|
+
|
17
|
+
new_tables = []
|
18
|
+
|
19
|
+
node.each_descendant(:send) do |send_node|
|
20
|
+
first_arg = first_argument(send_node)
|
21
|
+
|
22
|
+
# The first argument of "create_table" / "add_index" is the table
|
23
|
+
# name.
|
24
|
+
new_tables << first_arg if create_table?(send_node)
|
25
|
+
|
26
|
+
next if method_name(send_node) != :add_index
|
27
|
+
|
28
|
+
# Using "add_index" is fine for newly created tables as there's no
|
29
|
+
# data in these tables yet.
|
30
|
+
next if new_tables.include?(first_arg)
|
31
|
+
|
32
|
+
add_offense(send_node, :selector)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_table?(node)
|
37
|
+
method_name(node) == :create_table
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_name(node)
|
41
|
+
node.children[1]
|
42
|
+
end
|
43
|
+
|
44
|
+
def first_argument(node)
|
45
|
+
node.children[2] ? node.children[0] : nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative '../../migration_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
module Migration
|
8
|
+
# Cop that checks if 'add_timestamps' method is called with timezone information.
|
9
|
+
class AddTimestamps < RuboCop::Cop::Cop
|
10
|
+
include MigrationHelpers
|
11
|
+
|
12
|
+
MSG = 'Do not use `add_timestamps`, use `add_timestamps_with_timezone` instead'.freeze
|
13
|
+
|
14
|
+
# Check methods.
|
15
|
+
def on_send(node)
|
16
|
+
return unless in_migration?(node)
|
17
|
+
|
18
|
+
add_offense(node, :selector) if method_name(node) == :add_timestamps
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_name(node)
|
22
|
+
node.children[1]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../../migration_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
module Migration
|
8
|
+
# Cop that checks if datetime data type is added with timezone information.
|
9
|
+
class Datetime < RuboCop::Cop::Cop
|
10
|
+
include MigrationHelpers
|
11
|
+
|
12
|
+
MSG = 'Do not use the `datetime` data type, use `datetime_with_timezone` instead'.freeze
|
13
|
+
|
14
|
+
# Check methods in table creation.
|
15
|
+
def on_def(node)
|
16
|
+
return unless in_migration?(node)
|
17
|
+
|
18
|
+
node.each_descendant(:send) do |send_node|
|
19
|
+
add_offense(send_node, :selector) if method_name(send_node) == :datetime
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check methods.
|
24
|
+
def on_send(node)
|
25
|
+
return unless in_migration?(node)
|
26
|
+
|
27
|
+
node.each_descendant do |descendant|
|
28
|
+
add_offense(node, :expression) if descendant.type == :sym && descendant.children.last == :datetime
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def method_name(node)
|
33
|
+
node.children[1]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'set'
|
2
|
+
require_relative '../../migration_helpers'
|
3
|
+
|
4
|
+
module Gitlab
|
5
|
+
module Styles
|
6
|
+
module Rubocop
|
7
|
+
module Cop
|
8
|
+
module Migration
|
9
|
+
# Cop that prevents the use of hash indexes in database migrations
|
10
|
+
class HashIndex < RuboCop::Cop::Cop
|
11
|
+
include MigrationHelpers
|
12
|
+
|
13
|
+
MSG = 'hash indexes should be avoided at all costs since they are not ' \
|
14
|
+
'recorded in the PostgreSQL WAL, you should use a btree index instead'.freeze
|
15
|
+
|
16
|
+
NAMES = Set.new([:add_index, :index, :add_concurrent_index]).freeze
|
17
|
+
|
18
|
+
def on_send(node)
|
19
|
+
return unless in_migration?(node)
|
20
|
+
|
21
|
+
name = node.children[1]
|
22
|
+
|
23
|
+
return unless NAMES.include?(name)
|
24
|
+
|
25
|
+
opts = node.children.last
|
26
|
+
|
27
|
+
return unless opts&.type == :hash
|
28
|
+
|
29
|
+
opts.each_node(:pair) do |pair|
|
30
|
+
next unless hash_key_type(pair) == :sym &&
|
31
|
+
hash_key_name(pair) == :using
|
32
|
+
|
33
|
+
if hash_key_value(pair).to_s == 'hash'
|
34
|
+
add_offense(pair, :expression)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def hash_key_type(pair)
|
40
|
+
pair.children[0].type
|
41
|
+
end
|
42
|
+
|
43
|
+
def hash_key_name(pair)
|
44
|
+
pair.children[0].children[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
def hash_key_value(pair)
|
48
|
+
pair.children[1].children[0]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative '../../migration_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
module Migration
|
8
|
+
# Cop that checks if `remove_concurrent_index` is used with `up`/`down` methods
|
9
|
+
# and not `change`.
|
10
|
+
class RemoveConcurrentIndex < RuboCop::Cop::Cop
|
11
|
+
include MigrationHelpers
|
12
|
+
|
13
|
+
MSG = '`remove_concurrent_index` is not reversible so you must manually define ' \
|
14
|
+
'the `up` and `down` methods in your migration class, using `add_concurrent_index` in `down`'.freeze
|
15
|
+
|
16
|
+
def on_send(node)
|
17
|
+
return unless in_migration?(node)
|
18
|
+
return unless node.children[1] == :remove_concurrent_index
|
19
|
+
|
20
|
+
node.each_ancestor(:def) do |def_node|
|
21
|
+
add_offense(def_node, :name) if method_name(def_node) == :change
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_name(node)
|
26
|
+
node.children[0]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative '../../migration_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
module Migration
|
8
|
+
# Cop that checks if indexes are removed in a concurrent manner.
|
9
|
+
class RemoveIndex < RuboCop::Cop::Cop
|
10
|
+
include MigrationHelpers
|
11
|
+
|
12
|
+
MSG = '`remove_index` requires downtime, use `remove_concurrent_index` instead'.freeze
|
13
|
+
|
14
|
+
def on_def(node)
|
15
|
+
return unless in_migration?(node)
|
16
|
+
|
17
|
+
node.each_descendant(:send) do |send_node|
|
18
|
+
add_offense(send_node, :selector) if method_name(send_node) == :remove_index
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_name(node)
|
23
|
+
node.children[1]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative '../../migration_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
module Migration
|
8
|
+
# Cop that checks if `add_column_with_default` is used with `up`/`down` methods
|
9
|
+
# and not `change`.
|
10
|
+
class ReversibleAddColumnWithDefault < RuboCop::Cop::Cop
|
11
|
+
include MigrationHelpers
|
12
|
+
|
13
|
+
def_node_matcher :add_column_with_default?, <<~PATTERN
|
14
|
+
(send nil :add_column_with_default $...)
|
15
|
+
PATTERN
|
16
|
+
|
17
|
+
def_node_matcher :defines_change?, <<~PATTERN
|
18
|
+
(def :change ...)
|
19
|
+
PATTERN
|
20
|
+
|
21
|
+
MSG = '`add_column_with_default` is not reversible so you must manually define ' \
|
22
|
+
'the `up` and `down` methods in your migration class, using `remove_column` in `down`'.freeze
|
23
|
+
|
24
|
+
def on_send(node)
|
25
|
+
return unless in_migration?(node)
|
26
|
+
return unless add_column_with_default?(node)
|
27
|
+
|
28
|
+
node.each_ancestor(:def) do |def_node|
|
29
|
+
next unless defines_change?(def_node)
|
30
|
+
|
31
|
+
add_offense(def_node, :name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require_relative '../../migration_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
module Migration
|
8
|
+
# This cop requires a default value and disallows nulls for boolean
|
9
|
+
# columns on small tables.
|
10
|
+
#
|
11
|
+
# In general, this prevents 3-state-booleans.
|
12
|
+
# https://robots.thoughtbot.com/avoid-the-threestate-boolean-problem
|
13
|
+
#
|
14
|
+
# In particular, for the `application_settings` table, this ensures that
|
15
|
+
# upgraded installations get a proper default for the new boolean setting.
|
16
|
+
# A developer might otherwise mistakenly assume that a value in
|
17
|
+
# `ApplicationSetting.defaults` is sufficient.
|
18
|
+
#
|
19
|
+
# See https://gitlab.com/gitlab-org/gitlab-ee/issues/2750 for more
|
20
|
+
# information.
|
21
|
+
class SaferBooleanColumn < RuboCop::Cop::Cop
|
22
|
+
include MigrationHelpers
|
23
|
+
|
24
|
+
DEFAULT_OFFENSE = 'Boolean columns on the `%s` table should have a default. You may wish to use `add_column_with_default`.'.freeze
|
25
|
+
NULL_OFFENSE = 'Boolean columns on the `%s` table should disallow nulls.'.freeze
|
26
|
+
DEFAULT_AND_NULL_OFFENSE = 'Boolean columns on the `%s` table should have a default and should disallow nulls. You may wish to use `add_column_with_default`.'.freeze
|
27
|
+
|
28
|
+
SMALL_TABLES = %i[
|
29
|
+
application_settings
|
30
|
+
].freeze
|
31
|
+
|
32
|
+
def_node_matcher :add_column?, <<~PATTERN
|
33
|
+
(send nil :add_column $...)
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def on_send(node)
|
37
|
+
return unless in_migration?(node)
|
38
|
+
|
39
|
+
matched = add_column?(node)
|
40
|
+
|
41
|
+
return unless matched
|
42
|
+
|
43
|
+
table, _, type = matched.to_a.take(3).map(&:children).map(&:first)
|
44
|
+
opts = matched[3]
|
45
|
+
|
46
|
+
return unless SMALL_TABLES.include?(table) && type == :boolean
|
47
|
+
|
48
|
+
no_default = no_default?(opts)
|
49
|
+
nulls_allowed = nulls_allowed?(opts)
|
50
|
+
|
51
|
+
offense = if no_default && nulls_allowed
|
52
|
+
DEFAULT_AND_NULL_OFFENSE
|
53
|
+
elsif no_default
|
54
|
+
DEFAULT_OFFENSE
|
55
|
+
elsif nulls_allowed
|
56
|
+
NULL_OFFENSE
|
57
|
+
end
|
58
|
+
|
59
|
+
add_offense(node, :expression, format(offense, table)) if offense
|
60
|
+
end
|
61
|
+
|
62
|
+
def no_default?(opts)
|
63
|
+
return true unless opts
|
64
|
+
|
65
|
+
each_hash_node_pair(opts) do |key, value|
|
66
|
+
return value == 'nil' if key == :default
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def nulls_allowed?(opts)
|
71
|
+
return true unless opts
|
72
|
+
|
73
|
+
each_hash_node_pair(opts) do |key, value|
|
74
|
+
return value != 'false' if key == :null
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def each_hash_node_pair(hash_node, &block)
|
79
|
+
hash_node.each_node(:pair) do |pair|
|
80
|
+
key = hash_pair_key(pair)
|
81
|
+
value = hash_pair_value(pair)
|
82
|
+
yield(key, value)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def hash_pair_key(pair)
|
87
|
+
pair.children[0].children[0]
|
88
|
+
end
|
89
|
+
|
90
|
+
def hash_pair_value(pair)
|
91
|
+
pair.children[1].source
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative '../../migration_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
module Migration
|
8
|
+
# Cop that checks if 'timestamps' method is called with timezone information.
|
9
|
+
class Timestamps < RuboCop::Cop::Cop
|
10
|
+
include MigrationHelpers
|
11
|
+
|
12
|
+
MSG = 'Do not use `timestamps`, use `timestamps_with_timezone` instead'.freeze
|
13
|
+
|
14
|
+
# Check methods in table creation.
|
15
|
+
def on_def(node)
|
16
|
+
return unless in_migration?(node)
|
17
|
+
|
18
|
+
node.each_descendant(:send) do |send_node|
|
19
|
+
add_offense(send_node, :selector) if method_name(send_node) == :timestamps
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_name(node)
|
24
|
+
node.children[1]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative '../../migration_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
module Migration
|
8
|
+
# Cop that checks if a spec file exists for any migration using
|
9
|
+
# `update_column_in_batches`.
|
10
|
+
class UpdateColumnInBatches < RuboCop::Cop::Cop
|
11
|
+
include MigrationHelpers
|
12
|
+
|
13
|
+
MSG = 'Migration running `update_column_in_batches` must have a spec file at' \
|
14
|
+
' `%s`.'.freeze
|
15
|
+
|
16
|
+
def on_send(node)
|
17
|
+
return unless in_migration?(node)
|
18
|
+
return unless node.children[1] == :update_column_in_batches
|
19
|
+
|
20
|
+
spec_path = spec_filename(node)
|
21
|
+
|
22
|
+
add_offense(node, :expression, format(MSG, spec_path)) unless File.exist?(File.expand_path(spec_path, rails_root))
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def spec_filename(node)
|
28
|
+
source_name = node.location.expression.source_buffer.name
|
29
|
+
path = Pathname.new(source_name).relative_path_from(rails_root)
|
30
|
+
dirname = File.dirname(path)
|
31
|
+
.sub(%r{\Adb/(migrate|post_migrate)}, 'spec/migrations')
|
32
|
+
filename = File.basename(source_name, '.rb').sub(/\A\d+_/, '')
|
33
|
+
|
34
|
+
File.join(dirname, "#{filename}_spec.rb")
|
35
|
+
end
|
36
|
+
|
37
|
+
def rails_root
|
38
|
+
Pathname.new(File.expand_path('../../..', __dir__))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|