gitlab-styles 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.gitlab-ci.yml +13 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +4 -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/default.yml +1229 -0
  14. data/gitlab-styles.gemspec +29 -0
  15. data/lib/gitlab/styles.rb +6 -0
  16. data/lib/gitlab/styles/rubocop.rb +30 -0
  17. data/lib/gitlab/styles/rubocop/cop/active_record_dependent.rb +30 -0
  18. data/lib/gitlab/styles/rubocop/cop/active_record_serialize.rb +22 -0
  19. data/lib/gitlab/styles/rubocop/cop/custom_error_class.rb +68 -0
  20. data/lib/gitlab/styles/rubocop/cop/gem_fetcher.rb +41 -0
  21. data/lib/gitlab/styles/rubocop/cop/in_batches.rb +20 -0
  22. data/lib/gitlab/styles/rubocop/cop/migration/add_column.rb +56 -0
  23. data/lib/gitlab/styles/rubocop/cop/migration/add_column_with_default_to_large_table.rb +59 -0
  24. data/lib/gitlab/styles/rubocop/cop/migration/add_concurrent_foreign_key.rb +31 -0
  25. data/lib/gitlab/styles/rubocop/cop/migration/add_concurrent_index.rb +38 -0
  26. data/lib/gitlab/styles/rubocop/cop/migration/add_index.rb +52 -0
  27. data/lib/gitlab/styles/rubocop/cop/migration/add_timestamps.rb +29 -0
  28. data/lib/gitlab/styles/rubocop/cop/migration/datetime.rb +40 -0
  29. data/lib/gitlab/styles/rubocop/cop/migration/hash_index.rb +55 -0
  30. data/lib/gitlab/styles/rubocop/cop/migration/remove_concurrent_index.rb +33 -0
  31. data/lib/gitlab/styles/rubocop/cop/migration/remove_index.rb +30 -0
  32. data/lib/gitlab/styles/rubocop/cop/migration/reversible_add_column_with_default.rb +39 -0
  33. data/lib/gitlab/styles/rubocop/cop/migration/safer_boolean_column.rb +98 -0
  34. data/lib/gitlab/styles/rubocop/cop/migration/timestamps.rb +31 -0
  35. data/lib/gitlab/styles/rubocop/cop/migration/update_column_in_batches.rb +45 -0
  36. data/lib/gitlab/styles/rubocop/cop/polymorphic_associations.rb +27 -0
  37. data/lib/gitlab/styles/rubocop/cop/project_path_helper.rb +55 -0
  38. data/lib/gitlab/styles/rubocop/cop/redirect_with_status.rb +48 -0
  39. data/lib/gitlab/styles/rubocop/cop/rspec/single_line_hook.rb +42 -0
  40. data/lib/gitlab/styles/rubocop/migration_helpers.rb +15 -0
  41. data/lib/gitlab/styles/rubocop/model_helpers.rb +15 -0
  42. data/lib/gitlab/styles/version.rb +5 -0
  43. 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,6 @@
1
+ require 'gitlab/styles/version'
2
+
3
+ module Gitlab
4
+ module Styles
5
+ end
6
+ 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