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,29 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'gitlab/styles/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'gitlab-styles'
|
7
|
+
spec.version = Gitlab::Styles::VERSION
|
8
|
+
spec.authors = ['GitLab']
|
9
|
+
spec.email = ['remy@rymai.me']
|
10
|
+
|
11
|
+
spec.summary = 'GitLab style guides and shared style configs.'
|
12
|
+
spec.homepage = 'https://github.com/gitlab-org/gitlab-styles'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
spec.bindir = 'exe'
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency 'rubocop', '~> 0.49'
|
23
|
+
spec.add_dependency 'rubocop-rspec', '~> 1.15'
|
24
|
+
spec.add_dependency 'rubocop-gitlab-security', '~> 0.1.0'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.15'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
29
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'gitlab/styles/rubocop/cop/custom_error_class'
|
2
|
+
require 'gitlab/styles/rubocop/cop/gem_fetcher'
|
3
|
+
require 'gitlab/styles/rubocop/cop/active_record_serialize'
|
4
|
+
require 'gitlab/styles/rubocop/cop/redirect_with_status'
|
5
|
+
require 'gitlab/styles/rubocop/cop/polymorphic_associations'
|
6
|
+
require 'gitlab/styles/rubocop/cop/project_path_helper'
|
7
|
+
require 'gitlab/styles/rubocop/cop/active_record_dependent'
|
8
|
+
require 'gitlab/styles/rubocop/cop/in_batches'
|
9
|
+
require 'gitlab/styles/rubocop/cop/migration/add_column'
|
10
|
+
require 'gitlab/styles/rubocop/cop/migration/add_column_with_default_to_large_table'
|
11
|
+
require 'gitlab/styles/rubocop/cop/migration/add_concurrent_foreign_key'
|
12
|
+
require 'gitlab/styles/rubocop/cop/migration/add_concurrent_index'
|
13
|
+
require 'gitlab/styles/rubocop/cop/migration/add_index'
|
14
|
+
require 'gitlab/styles/rubocop/cop/migration/add_timestamps'
|
15
|
+
require 'gitlab/styles/rubocop/cop/migration/datetime'
|
16
|
+
require 'gitlab/styles/rubocop/cop/migration/safer_boolean_column'
|
17
|
+
require 'gitlab/styles/rubocop/cop/migration/hash_index'
|
18
|
+
require 'gitlab/styles/rubocop/cop/migration/remove_concurrent_index'
|
19
|
+
require 'gitlab/styles/rubocop/cop/migration/remove_index'
|
20
|
+
require 'gitlab/styles/rubocop/cop/migration/reversible_add_column_with_default'
|
21
|
+
require 'gitlab/styles/rubocop/cop/migration/timestamps'
|
22
|
+
require 'gitlab/styles/rubocop/cop/migration/update_column_in_batches'
|
23
|
+
require 'gitlab/styles/rubocop/cop/rspec/single_line_hook'
|
24
|
+
|
25
|
+
module Gitlab
|
26
|
+
module Styles
|
27
|
+
module Rubocop
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative '../model_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
# Cop that prevents the use of `dependent: ...` in ActiveRecord models.
|
8
|
+
class ActiveRecordDependent < RuboCop::Cop::Cop
|
9
|
+
include ModelHelpers
|
10
|
+
|
11
|
+
MSG = 'Do not use `dependent: to remove associated data, ' \
|
12
|
+
'use foreign keys with cascading deletes instead'.freeze
|
13
|
+
|
14
|
+
METHOD_NAMES = [:has_many, :has_one, :belongs_to].freeze
|
15
|
+
|
16
|
+
def on_send(node)
|
17
|
+
return unless in_model?(node)
|
18
|
+
return unless METHOD_NAMES.include?(node.children[1])
|
19
|
+
|
20
|
+
node.children.last.each_node(:pair) do |pair|
|
21
|
+
key_name = pair.children[0].children[0]
|
22
|
+
|
23
|
+
add_offense(pair, :expression) if key_name == :dependent
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative '../model_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
# Cop that prevents the use of `serialize` in ActiveRecord models.
|
8
|
+
class ActiveRecordSerialize < RuboCop::Cop::Cop
|
9
|
+
include ModelHelpers
|
10
|
+
|
11
|
+
MSG = 'Do not store serialized data in the database, use separate columns and/or tables instead'.freeze
|
12
|
+
|
13
|
+
def on_send(node)
|
14
|
+
return unless in_model?(node)
|
15
|
+
|
16
|
+
add_offense(node, :selector) if node.children[1] == :serialize
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Styles
|
3
|
+
module Rubocop
|
4
|
+
module Cop
|
5
|
+
# This cop makes sure that custom error classes, when empty, are declared
|
6
|
+
# with Class.new.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# class FooError < StandardError
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# # okish
|
14
|
+
# class FooError < StandardError; end
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# FooError = Class.new(StandardError)
|
18
|
+
class CustomErrorClass < RuboCop::Cop::Cop
|
19
|
+
MSG = 'Use `Class.new(SuperClass)` to define an empty custom error class.'.freeze
|
20
|
+
|
21
|
+
def on_class(node)
|
22
|
+
_klass, parent, body = node.children
|
23
|
+
|
24
|
+
return if body
|
25
|
+
|
26
|
+
parent_klass = class_name_from_node(parent)
|
27
|
+
|
28
|
+
return unless parent_klass && parent_klass.to_s.end_with?('Error')
|
29
|
+
|
30
|
+
add_offense(node, :expression)
|
31
|
+
end
|
32
|
+
|
33
|
+
def autocorrect(node)
|
34
|
+
klass, parent, _body = node.children
|
35
|
+
replacement = "#{class_name_from_node(klass)} = Class.new(#{class_name_from_node(parent)})"
|
36
|
+
|
37
|
+
lambda do |corrector|
|
38
|
+
corrector.replace(node.source_range, replacement)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# The nested constant `Foo::Bar::Baz` looks like:
|
45
|
+
#
|
46
|
+
# s(:const,
|
47
|
+
# s(:const,
|
48
|
+
# s(:const, nil, :Foo), :Bar), :Baz)
|
49
|
+
#
|
50
|
+
# So recurse through that to get the name as written in the source.
|
51
|
+
#
|
52
|
+
def class_name_from_node(node, suffix = nil)
|
53
|
+
return unless node&.type == :const
|
54
|
+
|
55
|
+
name = node.children[1].to_s
|
56
|
+
name = "#{name}::#{suffix}" if suffix
|
57
|
+
|
58
|
+
if node.children[0]
|
59
|
+
class_name_from_node(node.children[0], name)
|
60
|
+
else
|
61
|
+
name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Styles
|
3
|
+
module Rubocop
|
4
|
+
module Cop
|
5
|
+
# This cop prevents usage of the `git` and `github` arguments to `gem` in a
|
6
|
+
# `Gemfile` in order to avoid additional points of failure beyond
|
7
|
+
# rubygems.org.
|
8
|
+
class GemFetcher < RuboCop::Cop::Cop
|
9
|
+
MSG = 'Do not use gems from git repositories, only use gems from RubyGems.'.freeze
|
10
|
+
|
11
|
+
GIT_KEYS = [:git, :github].freeze
|
12
|
+
|
13
|
+
def on_send(node)
|
14
|
+
return unless gemfile?(node)
|
15
|
+
|
16
|
+
func_name = node.children[1]
|
17
|
+
return unless func_name == :gem
|
18
|
+
|
19
|
+
node.children.last.each_node(:pair) do |pair|
|
20
|
+
key_name = pair.children[0].children[0].to_sym
|
21
|
+
if GIT_KEYS.include?(key_name)
|
22
|
+
add_offense(node, pair.source_range, MSG)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def gemfile?(node)
|
30
|
+
node
|
31
|
+
.location
|
32
|
+
.expression
|
33
|
+
.source_buffer
|
34
|
+
.name
|
35
|
+
.end_with?("Gemfile")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative '../model_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
# Cop that prevents the use of `in_batches`
|
8
|
+
class InBatches < RuboCop::Cop::Cop
|
9
|
+
MSG = 'Do not use `in_batches`, use `each_batch` from the EachBatch module instead'.freeze
|
10
|
+
|
11
|
+
def on_send(node)
|
12
|
+
return unless node.children[1] == :in_batches
|
13
|
+
|
14
|
+
add_offense(node, :selector)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,56 @@
|
|
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 columns are added in a way that doesn't require
|
9
|
+
# downtime.
|
10
|
+
class AddColumn < RuboCop::Cop::Cop
|
11
|
+
include MigrationHelpers
|
12
|
+
|
13
|
+
WHITELISTED_TABLES = [:application_settings].freeze
|
14
|
+
|
15
|
+
MSG = '`add_column` with a default value requires downtime, ' \
|
16
|
+
'use `add_column_with_default` instead'.freeze
|
17
|
+
|
18
|
+
def on_send(node)
|
19
|
+
return unless in_migration?(node)
|
20
|
+
|
21
|
+
name = node.children[1]
|
22
|
+
|
23
|
+
return unless name == :add_column
|
24
|
+
|
25
|
+
# Ignore whitelisted tables.
|
26
|
+
return if table_whitelisted?(node.children[2])
|
27
|
+
|
28
|
+
opts = node.children.last
|
29
|
+
|
30
|
+
return unless opts&.type == :hash
|
31
|
+
|
32
|
+
opts.each_node(:pair) do |pair|
|
33
|
+
if hash_key_type(pair) == :sym && hash_key_name(pair) == :default
|
34
|
+
add_offense(node, :selector)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def table_whitelisted?(symbol)
|
40
|
+
symbol&.type == :sym &&
|
41
|
+
WHITELISTED_TABLES.include?(symbol.children[0])
|
42
|
+
end
|
43
|
+
|
44
|
+
def hash_key_type(pair)
|
45
|
+
pair.children[0].type
|
46
|
+
end
|
47
|
+
|
48
|
+
def hash_key_name(pair)
|
49
|
+
pair.children[0].children[0]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative '../../migration_helpers'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Styles
|
5
|
+
module Rubocop
|
6
|
+
module Cop
|
7
|
+
module Migration
|
8
|
+
# This cop checks for `add_column_with_default` on a table that's been
|
9
|
+
# explicitly blacklisted because of its size.
|
10
|
+
#
|
11
|
+
# Even though this helper performs the update in batches to avoid
|
12
|
+
# downtime, using it with tables with millions of rows still causes a
|
13
|
+
# significant delay in the deploy process and is best avoided.
|
14
|
+
#
|
15
|
+
# See https://gitlab.com/gitlab-com/infrastructure/issues/1602 for more
|
16
|
+
# information.
|
17
|
+
class AddColumnWithDefaultToLargeTable < RuboCop::Cop::Cop
|
18
|
+
include MigrationHelpers
|
19
|
+
|
20
|
+
MSG = 'Using `add_column_with_default` on the `%s` table will take a ' \
|
21
|
+
'long time to complete, and should be avoided unless absolutely ' \
|
22
|
+
'necessary'.freeze
|
23
|
+
|
24
|
+
LARGE_TABLES = %i[
|
25
|
+
ci_pipelines
|
26
|
+
ci_builds
|
27
|
+
events
|
28
|
+
issues
|
29
|
+
merge_request_diff_files
|
30
|
+
merge_request_diffs
|
31
|
+
merge_requests
|
32
|
+
namespaces
|
33
|
+
notes
|
34
|
+
projects
|
35
|
+
routes
|
36
|
+
users
|
37
|
+
].freeze
|
38
|
+
|
39
|
+
def_node_matcher :add_column_with_default?, <<~PATTERN
|
40
|
+
(send nil :add_column_with_default $(sym ...) ...)
|
41
|
+
PATTERN
|
42
|
+
|
43
|
+
def on_send(node)
|
44
|
+
return unless in_migration?(node)
|
45
|
+
|
46
|
+
matched = add_column_with_default?(node)
|
47
|
+
return unless matched
|
48
|
+
|
49
|
+
table = matched.to_a.first
|
50
|
+
return unless LARGE_TABLES.include?(table)
|
51
|
+
|
52
|
+
add_offense(node, :expression, format(MSG, table))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
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 `add_concurrent_foreign_key` is used instead of
|
9
|
+
# `add_foreign_key`.
|
10
|
+
class AddConcurrentForeignKey < RuboCop::Cop::Cop
|
11
|
+
include MigrationHelpers
|
12
|
+
|
13
|
+
MSG = '`add_foreign_key` requires downtime, use `add_concurrent_foreign_key` instead'.freeze
|
14
|
+
|
15
|
+
def on_send(node)
|
16
|
+
return unless in_migration?(node)
|
17
|
+
|
18
|
+
name = node.children[1]
|
19
|
+
|
20
|
+
add_offense(node, :selector) if name == :add_foreign_key
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_name(node)
|
24
|
+
node.children.first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,38 @@
|
|
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_concurrent_index` is used with `up`/`down` methods
|
9
|
+
# and not `change`.
|
10
|
+
class AddConcurrentIndex < RuboCop::Cop::Cop
|
11
|
+
include MigrationHelpers
|
12
|
+
|
13
|
+
MSG = '`add_concurrent_index` is not reversible so you must manually define ' \
|
14
|
+
'the `up` and `down` methods in your migration class, using `remove_concurrent_index` in `down`'.freeze
|
15
|
+
|
16
|
+
def on_send(node)
|
17
|
+
return unless in_migration?(node)
|
18
|
+
|
19
|
+
name = node.children[1]
|
20
|
+
|
21
|
+
return unless name == :add_concurrent_index
|
22
|
+
|
23
|
+
node.each_ancestor(:def) do |def_node|
|
24
|
+
next unless method_name(def_node) == :change
|
25
|
+
|
26
|
+
add_offense(def_node, :name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_name(node)
|
31
|
+
node.children.first
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|