rubocop-rails 2.0.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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +73 -0
  4. data/bin/setup +7 -0
  5. data/config/default.yml +466 -0
  6. data/lib/rubocop-rails.rb +12 -0
  7. data/lib/rubocop/cop/mixin/target_rails_version.rb +16 -0
  8. data/lib/rubocop/cop/rails/action_filter.rb +117 -0
  9. data/lib/rubocop/cop/rails/active_record_aliases.rb +48 -0
  10. data/lib/rubocop/cop/rails/active_record_override.rb +82 -0
  11. data/lib/rubocop/cop/rails/active_support_aliases.rb +69 -0
  12. data/lib/rubocop/cop/rails/application_job.rb +40 -0
  13. data/lib/rubocop/cop/rails/application_record.rb +40 -0
  14. data/lib/rubocop/cop/rails/assert_not.rb +44 -0
  15. data/lib/rubocop/cop/rails/belongs_to.rb +102 -0
  16. data/lib/rubocop/cop/rails/blank.rb +164 -0
  17. data/lib/rubocop/cop/rails/bulk_change_table.rb +289 -0
  18. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +91 -0
  19. data/lib/rubocop/cop/rails/date.rb +161 -0
  20. data/lib/rubocop/cop/rails/delegate.rb +132 -0
  21. data/lib/rubocop/cop/rails/delegate_allow_blank.rb +37 -0
  22. data/lib/rubocop/cop/rails/dynamic_find_by.rb +91 -0
  23. data/lib/rubocop/cop/rails/enum_uniqueness.rb +45 -0
  24. data/lib/rubocop/cop/rails/environment_comparison.rb +68 -0
  25. data/lib/rubocop/cop/rails/exit.rb +67 -0
  26. data/lib/rubocop/cop/rails/file_path.rb +108 -0
  27. data/lib/rubocop/cop/rails/find_by.rb +55 -0
  28. data/lib/rubocop/cop/rails/find_each.rb +51 -0
  29. data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +25 -0
  30. data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +106 -0
  31. data/lib/rubocop/cop/rails/helper_instance_variable.rb +39 -0
  32. data/lib/rubocop/cop/rails/http_positional_arguments.rb +117 -0
  33. data/lib/rubocop/cop/rails/http_status.rb +160 -0
  34. data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +94 -0
  35. data/lib/rubocop/cop/rails/inverse_of.rb +246 -0
  36. data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +175 -0
  37. data/lib/rubocop/cop/rails/link_to_blank.rb +98 -0
  38. data/lib/rubocop/cop/rails/not_null_column.rb +67 -0
  39. data/lib/rubocop/cop/rails/output.rb +49 -0
  40. data/lib/rubocop/cop/rails/output_safety.rb +99 -0
  41. data/lib/rubocop/cop/rails/pluralization_grammar.rb +107 -0
  42. data/lib/rubocop/cop/rails/presence.rb +124 -0
  43. data/lib/rubocop/cop/rails/present.rb +153 -0
  44. data/lib/rubocop/cop/rails/read_write_attribute.rb +74 -0
  45. data/lib/rubocop/cop/rails/redundant_allow_nil.rb +111 -0
  46. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +136 -0
  47. data/lib/rubocop/cop/rails/reflection_class_name.rb +37 -0
  48. data/lib/rubocop/cop/rails/refute_methods.rb +76 -0
  49. data/lib/rubocop/cop/rails/relative_date_constant.rb +93 -0
  50. data/lib/rubocop/cop/rails/request_referer.rb +56 -0
  51. data/lib/rubocop/cop/rails/reversible_migration.rb +286 -0
  52. data/lib/rubocop/cop/rails/safe_navigation.rb +87 -0
  53. data/lib/rubocop/cop/rails/save_bang.rb +316 -0
  54. data/lib/rubocop/cop/rails/scope_args.rb +29 -0
  55. data/lib/rubocop/cop/rails/skips_model_validations.rb +87 -0
  56. data/lib/rubocop/cop/rails/time_zone.rb +238 -0
  57. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +105 -0
  58. data/lib/rubocop/cop/rails/unknown_env.rb +63 -0
  59. data/lib/rubocop/cop/rails/validation.rb +109 -0
  60. data/lib/rubocop/cop/rails_cops.rb +64 -0
  61. data/lib/rubocop/rails.rb +12 -0
  62. data/lib/rubocop/rails/inject.rb +18 -0
  63. data/lib/rubocop/rails/version.rb +10 -0
  64. metadata +143 -0
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for duplicate values in enum declarations.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # enum status: { active: 0, archived: 0 }
11
+ #
12
+ # # good
13
+ # enum status: { active: 0, archived: 1 }
14
+ #
15
+ # # bad
16
+ # enum status: [:active, :archived, :active]
17
+ #
18
+ # # good
19
+ # enum status: [:active, :archived]
20
+ class EnumUniqueness < Cop
21
+ include Duplication
22
+
23
+ MSG = 'Duplicate value `%<value>s` found in `%<enum>s` ' \
24
+ 'enum declaration.'
25
+
26
+ def_node_matcher :enum_declaration, <<-PATTERN
27
+ (send nil? :enum (hash (pair (_ $_) ${array hash})))
28
+ PATTERN
29
+
30
+ def on_send(node)
31
+ enum_declaration(node) do |name, args|
32
+ items = args.values
33
+
34
+ return unless duplicates?(items)
35
+
36
+ consecutive_duplicates(items).each do |item|
37
+ add_offense(item, message: format(MSG, value: item.source,
38
+ enum: name))
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks that Rails.env is compared using `.production?`-like
7
+ # methods instead of equality against a string or symbol.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # Rails.env == 'production'
12
+ #
13
+ # # bad, always returns false
14
+ # Rails.env == :test
15
+ #
16
+ # # good
17
+ # Rails.env.production?
18
+ class EnvironmentComparison < Cop
19
+ MSG = "Favor `Rails.env.%<env>s?` over `Rails.env == '%<env>s'`."
20
+
21
+ SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always ' \
22
+ 'evaluate to `false`.'
23
+
24
+ def_node_matcher :environment_str_comparison?, <<-PATTERN
25
+ (send
26
+ (send (const {nil? cbase} :Rails) :env)
27
+ :==
28
+ $str
29
+ )
30
+ PATTERN
31
+
32
+ def_node_matcher :environment_sym_comparison?, <<-PATTERN
33
+ (send
34
+ (send (const {nil? cbase} :Rails) :env)
35
+ :==
36
+ $sym
37
+ )
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ environment_str_comparison?(node) do |env_node|
42
+ env, = *env_node
43
+ add_offense(node, message: format(MSG, env: env))
44
+ end
45
+ environment_sym_comparison?(node) do |_|
46
+ add_offense(node, message: SYM_MSG)
47
+ end
48
+ end
49
+
50
+ def autocorrect(node)
51
+ lambda do |corrector|
52
+ corrector.replace(node.source_range, replacement(node))
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def replacement(node)
59
+ "#{node.receiver.source}.#{content(node.first_argument)}?"
60
+ end
61
+
62
+ def_node_matcher :content, <<-PATTERN
63
+ ({str sym} $_)
64
+ PATTERN
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop enforces that `exit` calls are not used within a rails app.
7
+ # Valid options are instead to raise an error, break, return, or some
8
+ # other form of stopping execution of current request.
9
+ #
10
+ # There are two obvious cases where `exit` is particularly harmful:
11
+ #
12
+ # - Usage in library code for your application. Even though Rails will
13
+ # rescue from a `SystemExit` and continue on, unit testing that library
14
+ # code will result in specs exiting (potentially silently if `exit(0)`
15
+ # is used.)
16
+ # - Usage in application code outside of the web process could result in
17
+ # the program exiting, which could result in the code failing to run and
18
+ # do its job.
19
+ #
20
+ # @example
21
+ # # bad
22
+ # exit(0)
23
+ #
24
+ # # good
25
+ # raise 'a bad error has happened'
26
+ class Exit < Cop
27
+ include ConfigurableEnforcedStyle
28
+
29
+ MSG = 'Do not use `exit` in Rails applications.'
30
+ TARGET_METHODS = %i[exit exit!].freeze
31
+ EXPLICIT_RECEIVERS = %i[Kernel Process].freeze
32
+
33
+ def on_send(node)
34
+ add_offense(node, location: :selector) if offending_node?(node)
35
+ end
36
+
37
+ private
38
+
39
+ def offending_node?(node)
40
+ right_method_name?(node.method_name) &&
41
+ right_argument_count?(node.arguments) &&
42
+ right_receiver?(node.receiver)
43
+ end
44
+
45
+ def right_method_name?(method_name)
46
+ TARGET_METHODS.include?(method_name)
47
+ end
48
+
49
+ # More than 1 argument likely means it is a different
50
+ # `exit` implementation than the one we are preventing.
51
+ def right_argument_count?(arg_nodes)
52
+ arg_nodes.size <= 1
53
+ end
54
+
55
+ # Only register if exit is being called explicitly on `Kernel`,
56
+ # `Process`, or if receiver node is nil for plain `exit` calls.
57
+ def right_receiver?(receiver_node)
58
+ return true unless receiver_node
59
+
60
+ _a, receiver_node_class, _c = *receiver_node
61
+
62
+ EXPLICIT_RECEIVERS.include?(receiver_node_class)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop is used to identify usages of file path joining process
7
+ # to use `Rails.root.join` clause. It is used to add uniformity when
8
+ # joining paths.
9
+ #
10
+ # @example EnforcedStyle: arguments (default)
11
+ # # bad
12
+ # Rails.root.join('app/models/goober')
13
+ # File.join(Rails.root, 'app/models/goober')
14
+ # "#{Rails.root}/app/models/goober"
15
+ #
16
+ # # good
17
+ # Rails.root.join('app', 'models', 'goober')
18
+ #
19
+ # @example EnforcedStyle: slashes
20
+ # # bad
21
+ # Rails.root.join('app', 'models', 'goober')
22
+ # File.join(Rails.root, 'app/models/goober')
23
+ # "#{Rails.root}/app/models/goober"
24
+ #
25
+ # # good
26
+ # Rails.root.join('app/models/goober')
27
+ #
28
+ class FilePath < Cop
29
+ include ConfigurableEnforcedStyle
30
+ include RangeHelp
31
+
32
+ MSG_SLASHES = 'Please use `Rails.root.join(\'path/to\')` ' \
33
+ 'instead.'
34
+ MSG_ARGUMENTS = 'Please use `Rails.root.join(\'path\', \'to\')` ' \
35
+ 'instead.'
36
+
37
+ def_node_matcher :file_join_nodes?, <<-PATTERN
38
+ (send (const nil? :File) :join ...)
39
+ PATTERN
40
+
41
+ def_node_search :rails_root_nodes?, <<-PATTERN
42
+ (send (const nil? :Rails) :root)
43
+ PATTERN
44
+
45
+ def_node_matcher :rails_root_join_nodes?, <<-PATTERN
46
+ (send (send (const nil? :Rails) :root) :join ...)
47
+ PATTERN
48
+
49
+ def on_dstr(node)
50
+ return unless rails_root_nodes?(node)
51
+ return unless node.children.last.source.start_with?('.') ||
52
+ node.children.last.source.include?(File::SEPARATOR)
53
+
54
+ register_offense(node)
55
+ end
56
+
57
+ def on_send(node)
58
+ check_for_file_join_with_rails_root(node)
59
+ check_for_rails_root_join_with_slash_separated_path(node)
60
+ check_for_rails_root_join_with_string_arguments(node)
61
+ end
62
+
63
+ private
64
+
65
+ def check_for_file_join_with_rails_root(node)
66
+ return unless file_join_nodes?(node)
67
+ return unless node.arguments.any? { |e| rails_root_nodes?(e) }
68
+
69
+ register_offense(node)
70
+ end
71
+
72
+ def check_for_rails_root_join_with_string_arguments(node)
73
+ return unless style == :slashes
74
+ return unless rails_root_nodes?(node)
75
+ return unless rails_root_join_nodes?(node)
76
+ return unless node.arguments.size > 1
77
+ return unless node.arguments.all?(&:str_type?)
78
+
79
+ register_offense(node)
80
+ end
81
+
82
+ def check_for_rails_root_join_with_slash_separated_path(node)
83
+ return unless style == :arguments
84
+ return unless rails_root_nodes?(node)
85
+ return unless rails_root_join_nodes?(node)
86
+ return unless node.arguments.any? { |arg| string_with_slash?(arg) }
87
+
88
+ register_offense(node)
89
+ end
90
+
91
+ def string_with_slash?(node)
92
+ node.str_type? && node.source =~ %r{/}
93
+ end
94
+
95
+ def register_offense(node)
96
+ line_range = node.loc.column...node.loc.last_column
97
+ source_range = source_range(processed_source.buffer, node.first_line,
98
+ line_range)
99
+ add_offense(node, location: source_range)
100
+ end
101
+
102
+ def message(_node)
103
+ format(style == :arguments ? MSG_ARGUMENTS : MSG_SLASHES)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop is used to identify usages of `where.first` and
7
+ # change them to use `find_by` instead.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # User.where(name: 'Bruce').first
12
+ # User.where(name: 'Bruce').take
13
+ #
14
+ # # good
15
+ # User.find_by(name: 'Bruce')
16
+ class FindBy < Cop
17
+ include RangeHelp
18
+
19
+ MSG = 'Use `find_by` instead of `where.%<method>s`.'
20
+ TARGET_SELECTORS = %i[first take].freeze
21
+
22
+ def_node_matcher :where_first?, <<-PATTERN
23
+ (send ({send csend} _ :where ...) {:first :take})
24
+ PATTERN
25
+
26
+ def on_send(node)
27
+ return unless where_first?(node)
28
+
29
+ range = range_between(node.receiver.loc.selector.begin_pos,
30
+ node.loc.selector.end_pos)
31
+
32
+ add_offense(node, location: range,
33
+ message: format(MSG, method: node.method_name))
34
+ end
35
+ alias on_csend on_send
36
+
37
+ def autocorrect(node)
38
+ # Don't autocorrect where(...).first, because it can return different
39
+ # results from find_by. (They order records differently, so the
40
+ # 'first' record can be different.)
41
+ return if node.method?(:first)
42
+
43
+ where_loc = node.receiver.loc.selector
44
+ first_loc = range_between(node.loc.dot.begin_pos,
45
+ node.loc.selector.end_pos)
46
+
47
+ lambda do |corrector|
48
+ corrector.replace(where_loc, 'find_by')
49
+ corrector.replace(first_loc, '')
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop is used to identify usages of `all.each` and
7
+ # change them to use `all.find_each` instead.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # User.all.each
12
+ #
13
+ # # good
14
+ # User.all.find_each
15
+ class FindEach < Cop
16
+ MSG = 'Use `find_each` instead of `each`.'
17
+
18
+ SCOPE_METHODS = %i[
19
+ all eager_load includes joins left_joins left_outer_joins not preload
20
+ references unscoped where
21
+ ].freeze
22
+ IGNORED_METHODS = %i[order limit select].freeze
23
+
24
+ def on_send(node)
25
+ return unless node.receiver&.send_type? &&
26
+ node.method?(:each)
27
+
28
+ return unless SCOPE_METHODS.include?(node.receiver.method_name)
29
+ return if method_chain(node).any? { |m| ignored_by_find_each?(m) }
30
+
31
+ add_offense(node, location: :selector)
32
+ end
33
+
34
+ def autocorrect(node)
35
+ ->(corrector) { corrector.replace(node.loc.selector, 'find_each') }
36
+ end
37
+
38
+ private
39
+
40
+ def method_chain(node)
41
+ node.each_node(:send).map(&:method_name)
42
+ end
43
+
44
+ def ignored_by_find_each?(relation_method)
45
+ # Active Record's #find_each ignores various extra parameters
46
+ IGNORED_METHODS.include?(relation_method)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop checks for the use of the has_and_belongs_to_many macro.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # # has_and_belongs_to_many :ingredients
11
+ #
12
+ # # good
13
+ # # has_many :ingredients, through: :recipe_ingredients
14
+ class HasAndBelongsToMany < Cop
15
+ MSG = 'Prefer `has_many :through` to `has_and_belongs_to_many`.'
16
+
17
+ def on_send(node)
18
+ return unless node.command?(:has_and_belongs_to_many)
19
+
20
+ add_offense(node, location: :selector)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ # This cop looks for `has_many` or `has_one` associations that don't
7
+ # specify a `:dependent` option.
8
+ # It doesn't register an offense if `:through` option was specified.
9
+ #
10
+ # @example
11
+ # # bad
12
+ # class User < ActiveRecord::Base
13
+ # has_many :comments
14
+ # has_one :avatar
15
+ # end
16
+ #
17
+ # # good
18
+ # class User < ActiveRecord::Base
19
+ # has_many :comments, dependent: :restrict_with_exception
20
+ # has_one :avatar, dependent: :destroy
21
+ # has_many :patients, through: :appointments
22
+ # end
23
+ class HasManyOrHasOneDependent < Cop
24
+ MSG = 'Specify a `:dependent` option.'
25
+
26
+ def_node_search :active_resource_class?, <<-PATTERN
27
+ (const (const nil? :ActiveResource) :Base)
28
+ PATTERN
29
+
30
+ def_node_matcher :association_without_options?, <<-PATTERN
31
+ (send nil? {:has_many :has_one} _)
32
+ PATTERN
33
+
34
+ def_node_matcher :association_with_options?, <<-PATTERN
35
+ (send nil? {:has_many :has_one} _ (hash $...))
36
+ PATTERN
37
+
38
+ def_node_matcher :dependent_option?, <<-PATTERN
39
+ (pair (sym :dependent) !nil)
40
+ PATTERN
41
+
42
+ def_node_matcher :present_option?, <<-PATTERN
43
+ (pair (sym :through) !nil)
44
+ PATTERN
45
+
46
+ def_node_matcher :with_options_block, <<-PATTERN
47
+ (block
48
+ (send nil? :with_options
49
+ (hash $...))
50
+ (args) ...)
51
+ PATTERN
52
+
53
+ def on_send(node)
54
+ return if active_resource?(node.parent)
55
+
56
+ unless association_without_options?(node)
57
+ return if valid_options?(association_with_options?(node))
58
+ end
59
+
60
+ return if valid_options_in_with_options_block?(node)
61
+
62
+ add_offense(node, location: :selector)
63
+ end
64
+
65
+ private
66
+
67
+ def valid_options_in_with_options_block?(node)
68
+ return true unless node.parent
69
+
70
+ n = node.parent.begin_type? ? node.parent.parent : node.parent
71
+
72
+ contain_valid_options_in_with_options_block?(n)
73
+ end
74
+
75
+ def contain_valid_options_in_with_options_block?(node)
76
+ if with_options_block(node)
77
+ return true if valid_options?(with_options_block(node))
78
+
79
+ return false unless node.parent
80
+
81
+ return true if contain_valid_options_in_with_options_block?(
82
+ node.parent.parent
83
+ )
84
+ end
85
+
86
+ false
87
+ end
88
+
89
+ def valid_options?(options)
90
+ return true unless options
91
+ return true if options.any? do |o|
92
+ dependent_option?(o) || present_option?(o)
93
+ end
94
+
95
+ false
96
+ end
97
+
98
+ def active_resource?(node)
99
+ return false if node.nil?
100
+
101
+ active_resource_class?(node)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end