rubocop-rails 2.0.1 → 2.19.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +52 -5
- data/config/default.yml +726 -32
- data/config/obsoletion.yml +17 -0
- data/lib/rubocop/cop/mixin/active_record_helper.rb +106 -0
- data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +32 -0
- data/lib/rubocop/cop/mixin/class_send_node_helper.rb +20 -0
- data/lib/rubocop/cop/mixin/enforce_superclass.rb +40 -0
- data/lib/rubocop/cop/mixin/index_method.rb +165 -0
- data/lib/rubocop/cop/mixin/migrations_helper.rb +26 -0
- data/lib/rubocop/cop/rails/action_controller_flash_before_render.rb +112 -0
- data/lib/rubocop/cop/rails/action_controller_test_case.rb +47 -0
- data/lib/rubocop/cop/rails/action_filter.rb +11 -21
- data/lib/rubocop/cop/rails/action_order.rb +116 -0
- data/lib/rubocop/cop/rails/active_record_aliases.rb +23 -24
- data/lib/rubocop/cop/rails/active_record_callbacks_order.rb +143 -0
- data/lib/rubocop/cop/rails/active_record_override.rb +3 -6
- data/lib/rubocop/cop/rails/active_support_aliases.rb +13 -22
- data/lib/rubocop/cop/rails/active_support_on_load.rb +70 -0
- data/lib/rubocop/cop/rails/add_column_index.rb +61 -0
- data/lib/rubocop/cop/rails/after_commit_override.rb +81 -0
- data/lib/rubocop/cop/rails/application_controller.rb +36 -0
- data/lib/rubocop/cop/rails/application_job.rb +9 -4
- data/lib/rubocop/cop/rails/application_mailer.rb +39 -0
- data/lib/rubocop/cop/rails/application_record.rb +9 -9
- data/lib/rubocop/cop/rails/arel_star.rb +47 -0
- data/lib/rubocop/cop/rails/assert_not.rb +8 -10
- data/lib/rubocop/cop/rails/attribute_default_block_value.rb +90 -0
- data/lib/rubocop/cop/rails/belongs_to.rb +12 -24
- data/lib/rubocop/cop/rails/blank.rb +40 -36
- data/lib/rubocop/cop/rails/bulk_change_table.rb +40 -35
- data/lib/rubocop/cop/rails/compact_blank.rb +111 -0
- data/lib/rubocop/cop/rails/content_tag.rb +93 -0
- data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +22 -15
- data/lib/rubocop/cop/rails/date.rb +41 -36
- data/lib/rubocop/cop/rails/default_scope.rb +61 -0
- data/lib/rubocop/cop/rails/delegate.rb +33 -29
- data/lib/rubocop/cop/rails/delegate_allow_blank.rb +9 -10
- data/lib/rubocop/cop/rails/deprecated_active_model_errors_methods.rb +168 -0
- data/lib/rubocop/cop/rails/dot_separated_keys.rb +71 -0
- data/lib/rubocop/cop/rails/duplicate_association.rb +56 -0
- data/lib/rubocop/cop/rails/duplicate_scope.rb +46 -0
- data/lib/rubocop/cop/rails/duration_arithmetic.rb +98 -0
- data/lib/rubocop/cop/rails/dynamic_find_by.rb +76 -31
- data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +82 -0
- data/lib/rubocop/cop/rails/enum_hash.rb +75 -0
- data/lib/rubocop/cop/rails/enum_uniqueness.rb +30 -12
- data/lib/rubocop/cop/rails/environment_comparison.rb +70 -22
- data/lib/rubocop/cop/rails/environment_variable_access.rb +67 -0
- data/lib/rubocop/cop/rails/exit.rb +7 -13
- data/lib/rubocop/cop/rails/expanded_date_range.rb +102 -0
- data/lib/rubocop/cop/rails/file_path.rb +48 -31
- data/lib/rubocop/cop/rails/find_by.rb +43 -24
- data/lib/rubocop/cop/rails/find_by_id.rb +94 -0
- data/lib/rubocop/cop/rails/find_each.rb +42 -18
- data/lib/rubocop/cop/rails/freeze_time.rb +79 -0
- data/lib/rubocop/cop/rails/has_and_belongs_to_many.rb +4 -3
- data/lib/rubocop/cop/rails/has_many_or_has_one_dependent.rb +62 -25
- data/lib/rubocop/cop/rails/helper_instance_variable.rb +32 -4
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +61 -32
- data/lib/rubocop/cop/rails/http_status.rb +27 -23
- data/lib/rubocop/cop/rails/i18n_lazy_lookup.rb +96 -0
- data/lib/rubocop/cop/rails/i18n_locale_assignment.rb +37 -0
- data/lib/rubocop/cop/rails/i18n_locale_texts.rb +110 -0
- data/lib/rubocop/cop/rails/ignored_columns_assignment.rb +50 -0
- data/lib/rubocop/cop/rails/ignored_skip_action_filter_option.rb +9 -16
- data/lib/rubocop/cop/rails/index_by.rb +65 -0
- data/lib/rubocop/cop/rails/index_with.rb +68 -0
- data/lib/rubocop/cop/rails/inquiry.rb +39 -0
- data/lib/rubocop/cop/rails/inverse_of.rb +33 -27
- data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +62 -32
- data/lib/rubocop/cop/rails/link_to_blank.rb +31 -32
- data/lib/rubocop/cop/rails/mailer_name.rb +90 -0
- data/lib/rubocop/cop/rails/match_route.rb +120 -0
- data/lib/rubocop/cop/rails/migration_class_name.rb +63 -0
- data/lib/rubocop/cop/rails/negate_include.rb +42 -0
- data/lib/rubocop/cop/rails/not_null_column.rb +16 -12
- data/lib/rubocop/cop/rails/order_by_id.rb +51 -0
- data/lib/rubocop/cop/rails/output.rb +29 -10
- data/lib/rubocop/cop/rails/output_safety.rb +9 -4
- data/lib/rubocop/cop/rails/pick.rb +64 -0
- data/lib/rubocop/cop/rails/pluck.rb +96 -0
- data/lib/rubocop/cop/rails/pluck_id.rb +59 -0
- data/lib/rubocop/cop/rails/pluck_in_where.rb +71 -0
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +14 -19
- data/lib/rubocop/cop/rails/presence.rb +54 -26
- data/lib/rubocop/cop/rails/present.rb +40 -37
- data/lib/rubocop/cop/rails/rake_environment.rb +112 -0
- data/lib/rubocop/cop/rails/read_write_attribute.rb +56 -18
- data/lib/rubocop/cop/rails/redundant_allow_nil.rb +33 -45
- data/lib/rubocop/cop/rails/redundant_foreign_key.rb +77 -0
- data/lib/rubocop/cop/rails/redundant_presence_validation_on_belongs_to.rb +257 -0
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +34 -32
- data/lib/rubocop/cop/rails/redundant_travel_back.rb +57 -0
- data/lib/rubocop/cop/rails/reflection_class_name.rb +56 -7
- data/lib/rubocop/cop/rails/refute_methods.rb +56 -35
- data/lib/rubocop/cop/rails/relative_date_constant.rb +52 -33
- data/lib/rubocop/cop/rails/render_inline.rb +41 -0
- data/lib/rubocop/cop/rails/render_plain_text.rb +71 -0
- data/lib/rubocop/cop/rails/request_referer.rb +10 -11
- data/lib/rubocop/cop/rails/require_dependency.rb +38 -0
- data/lib/rubocop/cop/rails/response_parsed_body.rb +57 -0
- data/lib/rubocop/cop/rails/reversible_migration.rb +122 -82
- data/lib/rubocop/cop/rails/reversible_migration_method_definition.rb +66 -0
- data/lib/rubocop/cop/rails/root_join_chain.rb +72 -0
- data/lib/rubocop/cop/rails/root_pathname_methods.rb +238 -0
- data/lib/rubocop/cop/rails/root_public_path.rb +59 -0
- data/lib/rubocop/cop/rails/safe_navigation.rb +55 -43
- data/lib/rubocop/cop/rails/safe_navigation_with_blank.rb +50 -0
- data/lib/rubocop/cop/rails/save_bang.rb +89 -63
- data/lib/rubocop/cop/rails/schema_comment.rb +104 -0
- data/lib/rubocop/cop/rails/scope_args.rb +8 -3
- data/lib/rubocop/cop/rails/short_i18n.rb +71 -0
- data/lib/rubocop/cop/rails/skips_model_validations.rb +53 -16
- data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +87 -0
- data/lib/rubocop/cop/rails/strip_heredoc.rb +56 -0
- data/lib/rubocop/cop/rails/table_name_assignment.rb +44 -0
- data/lib/rubocop/cop/rails/three_state_boolean_column.rb +73 -0
- data/lib/rubocop/cop/rails/time_zone.rb +83 -67
- data/lib/rubocop/cop/rails/time_zone_assignment.rb +37 -0
- data/lib/rubocop/cop/rails/to_formatted_s.rb +46 -0
- data/lib/rubocop/cop/rails/to_s_with_argument.rb +78 -0
- data/lib/rubocop/cop/rails/top_level_hash_with_indifferent_access.rb +49 -0
- data/lib/rubocop/cop/rails/transaction_exit_statement.rb +99 -0
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +40 -49
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +172 -0
- data/lib/rubocop/cop/rails/unknown_env.rb +52 -21
- data/lib/rubocop/cop/rails/unused_ignored_columns.rb +76 -0
- data/lib/rubocop/cop/rails/validation.rb +54 -23
- data/lib/rubocop/cop/rails/where_equals.rb +102 -0
- data/lib/rubocop/cop/rails/where_exists.rb +138 -0
- data/lib/rubocop/cop/rails/where_missing.rb +118 -0
- data/lib/rubocop/cop/rails/where_not.rb +101 -0
- data/lib/rubocop/cop/rails/where_not_with_multiple_conditions.rb +55 -0
- data/lib/rubocop/cop/rails_cops.rb +78 -8
- data/lib/rubocop/rails/inject.rb +1 -1
- data/lib/rubocop/rails/schema_loader/schema.rb +191 -0
- data/lib/rubocop/rails/schema_loader.rb +61 -0
- data/lib/rubocop/rails/version.rb +5 -1
- data/lib/rubocop/rails.rb +3 -1
- data/lib/rubocop-rails.rb +22 -0
- metadata +120 -19
- data/bin/setup +0 -7
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Enforces consistent ordering of the standard Rails RESTful controller actions.
|
7
|
+
#
|
8
|
+
# The cop is configurable and can enforce any ordering of the standard actions.
|
9
|
+
# All other methods are ignored. So, the actions specified in `ExpectedOrder` should be
|
10
|
+
# defined before actions not specified.
|
11
|
+
#
|
12
|
+
# [source,yaml]
|
13
|
+
# ----
|
14
|
+
# Rails/ActionOrder:
|
15
|
+
# ExpectedOrder:
|
16
|
+
# - index
|
17
|
+
# - show
|
18
|
+
# - new
|
19
|
+
# - edit
|
20
|
+
# - create
|
21
|
+
# - update
|
22
|
+
# - destroy
|
23
|
+
# ----
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# # bad
|
27
|
+
# def index; end
|
28
|
+
# def destroy; end
|
29
|
+
# def show; end
|
30
|
+
#
|
31
|
+
# # good
|
32
|
+
# def index; end
|
33
|
+
# def show; end
|
34
|
+
# def destroy; end
|
35
|
+
class ActionOrder < Base
|
36
|
+
extend AutoCorrector
|
37
|
+
include VisibilityHelp
|
38
|
+
include DefNode
|
39
|
+
include RangeHelp
|
40
|
+
|
41
|
+
MSG = 'Action `%<current>s` should appear before `%<previous>s`.'
|
42
|
+
|
43
|
+
def_node_search :action_declarations, '(def {%1} ...)'
|
44
|
+
|
45
|
+
def on_class(node)
|
46
|
+
action_declarations(node, actions).each_cons(2) do |previous, current|
|
47
|
+
next if node_visibility(current) != :public || non_public?(current)
|
48
|
+
next if find_index(current) >= find_index(previous)
|
49
|
+
|
50
|
+
register_offense(previous, current)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def expected_order
|
57
|
+
cop_config['ExpectedOrder'].map(&:to_sym)
|
58
|
+
end
|
59
|
+
|
60
|
+
def actions
|
61
|
+
@actions ||= Set.new(expected_order)
|
62
|
+
end
|
63
|
+
|
64
|
+
def find_index(node)
|
65
|
+
expected_order.find_index(node.method_name)
|
66
|
+
end
|
67
|
+
|
68
|
+
def register_offense(previous, current)
|
69
|
+
message = format(
|
70
|
+
MSG,
|
71
|
+
expected_order: expected_order.join(', '),
|
72
|
+
previous: previous.method_name,
|
73
|
+
current: current.method_name
|
74
|
+
)
|
75
|
+
add_offense(current, message: message) do |corrector|
|
76
|
+
current = correction_target(current)
|
77
|
+
previous = correction_target(previous)
|
78
|
+
|
79
|
+
swap_range(corrector, current, previous)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def correction_target(def_node)
|
84
|
+
range_with_comments_and_lines(def_node.each_ancestor(:if).first || def_node)
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_range(range1, range2)
|
88
|
+
range1.with(
|
89
|
+
begin_pos: [range1.begin_pos, range2.begin_pos].min,
|
90
|
+
end_pos: [range1.end_pos, range2.end_pos].max
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
def range_with_comments(node)
|
95
|
+
# rubocop:todo InternalAffairs/LocationExpression
|
96
|
+
# Using `RuboCop::Ext::Comment#source_range` requires RuboCop > 1.46,
|
97
|
+
# which introduces https://github.com/rubocop/rubocop/pull/11630.
|
98
|
+
ranges = [node, *processed_source.ast_with_comments[node]].map { |comment| comment.loc.expression }
|
99
|
+
# rubocop:enable InternalAffairs/LocationExpression
|
100
|
+
ranges.reduce do |result, range|
|
101
|
+
add_range(result, range)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def range_with_comments_and_lines(node)
|
106
|
+
range_by_whole_lines(range_with_comments(node), include_final_newline: true)
|
107
|
+
end
|
108
|
+
|
109
|
+
def swap_range(corrector, range1, range2)
|
110
|
+
corrector.insert_before(range2, range1.source)
|
111
|
+
corrector.remove(range1)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -6,42 +6,41 @@ module RuboCop
|
|
6
6
|
# Checks that ActiveRecord aliases are not used. The direct method names
|
7
7
|
# are more clear and easier to read.
|
8
8
|
#
|
9
|
+
# @safety
|
10
|
+
# This cop is unsafe because custom `update_attributes` method call was changed to
|
11
|
+
# `update` but the method name remained same in the method definition.
|
12
|
+
#
|
9
13
|
# @example
|
10
14
|
# #bad
|
11
|
-
#
|
15
|
+
# book.update_attributes!(author: 'Alice')
|
12
16
|
#
|
13
17
|
# #good
|
14
|
-
#
|
15
|
-
class ActiveRecordAliases <
|
18
|
+
# book.update!(author: 'Alice')
|
19
|
+
class ActiveRecordAliases < Base
|
20
|
+
extend AutoCorrector
|
21
|
+
|
16
22
|
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
17
23
|
|
18
|
-
ALIASES = {
|
19
|
-
|
20
|
-
|
21
|
-
}.freeze
|
24
|
+
ALIASES = { update_attributes: :update, update_attributes!: :update! }.freeze
|
25
|
+
|
26
|
+
RESTRICT_ON_SEND = ALIASES.keys.freeze
|
22
27
|
|
23
28
|
def on_send(node)
|
24
|
-
|
25
|
-
next unless node.method?(bad)
|
26
|
-
|
27
|
-
add_offense(node,
|
28
|
-
message: format(MSG, prefer: good, current: bad),
|
29
|
-
location: :selector,
|
30
|
-
severity: :warning)
|
31
|
-
break
|
32
|
-
end
|
33
|
-
end
|
29
|
+
return if node.arguments.empty?
|
34
30
|
|
35
|
-
|
31
|
+
method_name = node.method_name
|
32
|
+
alias_method = ALIASES[method_name]
|
36
33
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
)
|
34
|
+
add_offense(
|
35
|
+
node.loc.selector,
|
36
|
+
message: format(MSG, prefer: alias_method, current: method_name),
|
37
|
+
severity: :warning
|
38
|
+
) do |corrector|
|
39
|
+
corrector.replace(node.loc.selector, alias_method)
|
43
40
|
end
|
44
41
|
end
|
42
|
+
|
43
|
+
alias on_csend on_send
|
45
44
|
end
|
46
45
|
end
|
47
46
|
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Checks that Active Record callbacks are declared
|
7
|
+
# in the order in which they will be executed.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# class Person < ApplicationRecord
|
12
|
+
# after_commit :after_commit_callback
|
13
|
+
# before_validation :before_validation_callback
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# class Person < ApplicationRecord
|
18
|
+
# before_validation :before_validation_callback
|
19
|
+
# after_commit :after_commit_callback
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
class ActiveRecordCallbacksOrder < Base
|
23
|
+
extend AutoCorrector
|
24
|
+
|
25
|
+
MSG = '`%<current>s` is supposed to appear before `%<previous>s`.'
|
26
|
+
|
27
|
+
CALLBACKS_IN_ORDER = %i[
|
28
|
+
after_initialize
|
29
|
+
before_validation
|
30
|
+
after_validation
|
31
|
+
before_save
|
32
|
+
around_save
|
33
|
+
before_create
|
34
|
+
around_create
|
35
|
+
after_create
|
36
|
+
before_update
|
37
|
+
around_update
|
38
|
+
after_update
|
39
|
+
before_destroy
|
40
|
+
around_destroy
|
41
|
+
after_destroy
|
42
|
+
after_save
|
43
|
+
after_commit
|
44
|
+
after_rollback
|
45
|
+
after_find
|
46
|
+
after_touch
|
47
|
+
].freeze
|
48
|
+
|
49
|
+
CALLBACKS_ORDER_MAP = CALLBACKS_IN_ORDER.each_with_index.to_h.freeze
|
50
|
+
|
51
|
+
def on_class(class_node)
|
52
|
+
previous_index = -1
|
53
|
+
previous_callback = nil
|
54
|
+
|
55
|
+
defined_callbacks(class_node).each do |node|
|
56
|
+
callback = node.method_name
|
57
|
+
index = CALLBACKS_ORDER_MAP[callback]
|
58
|
+
|
59
|
+
if index < previous_index
|
60
|
+
message = format(MSG, current: callback, previous: previous_callback)
|
61
|
+
add_offense(node, message: message) do |corrector|
|
62
|
+
autocorrect(corrector, node)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
previous_index = index
|
66
|
+
previous_callback = callback
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Autocorrect by swapping between two nodes autocorrecting them
|
73
|
+
def autocorrect(corrector, node)
|
74
|
+
previous = node.left_siblings.reverse_each.find do |sibling|
|
75
|
+
callback?(sibling)
|
76
|
+
end
|
77
|
+
|
78
|
+
current_range = source_range_with_comment(node)
|
79
|
+
previous_range = source_range_with_comment(previous)
|
80
|
+
|
81
|
+
corrector.insert_before(previous_range, current_range.source)
|
82
|
+
corrector.remove(current_range)
|
83
|
+
end
|
84
|
+
|
85
|
+
def defined_callbacks(class_node)
|
86
|
+
class_def = class_node.body
|
87
|
+
|
88
|
+
if class_def
|
89
|
+
class_def.each_child_node.select { |c| callback?(c) }
|
90
|
+
else
|
91
|
+
[]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def callback?(node)
|
96
|
+
node.send_type? && CALLBACKS_ORDER_MAP.key?(node.method_name)
|
97
|
+
end
|
98
|
+
|
99
|
+
def source_range_with_comment(node)
|
100
|
+
begin_pos = begin_pos_with_comment(node)
|
101
|
+
end_pos = end_position_for(node)
|
102
|
+
|
103
|
+
Parser::Source::Range.new(buffer, begin_pos, end_pos)
|
104
|
+
end
|
105
|
+
|
106
|
+
def end_position_for(node)
|
107
|
+
end_line = buffer.line_for_position(node.source_range.end_pos)
|
108
|
+
buffer.line_range(end_line).end_pos
|
109
|
+
end
|
110
|
+
|
111
|
+
def begin_pos_with_comment(node)
|
112
|
+
annotation_line = node.first_line - 1
|
113
|
+
first_comment = nil
|
114
|
+
|
115
|
+
processed_source.each_comment_in_lines(0..annotation_line).reverse_each do |comment|
|
116
|
+
if comment.location.line == annotation_line && !inline_comment?(comment)
|
117
|
+
first_comment = comment
|
118
|
+
annotation_line -= 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
start_line_position(first_comment || node)
|
123
|
+
end
|
124
|
+
|
125
|
+
def inline_comment?(comment)
|
126
|
+
# rubocop:todo InternalAffairs/LocationExpression
|
127
|
+
# Using `RuboCop::Ext::Comment#source_range` requires RuboCop > 1.46,
|
128
|
+
# which introduces https://github.com/rubocop/rubocop/pull/11630.
|
129
|
+
!comment_line?(comment.loc.expression.source_line)
|
130
|
+
# rubocop:enable InternalAffairs/LocationExpression
|
131
|
+
end
|
132
|
+
|
133
|
+
def start_line_position(node)
|
134
|
+
buffer.line_range(node.loc.line).begin_pos - 1
|
135
|
+
end
|
136
|
+
|
137
|
+
def buffer
|
138
|
+
processed_source.buffer
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -24,13 +24,10 @@ module RuboCop
|
|
24
24
|
# end
|
25
25
|
# end
|
26
26
|
#
|
27
|
-
class ActiveRecordOverride <
|
28
|
-
MSG =
|
29
|
-
'Use %<prefer>s callbacks instead of overriding the Active Record ' \
|
30
|
-
'method `%<bad>s`.'
|
27
|
+
class ActiveRecordOverride < Base
|
28
|
+
MSG = 'Use %<prefer>s callbacks instead of overriding the Active Record method `%<bad>s`.'
|
31
29
|
BAD_METHODS = %i[create destroy save update].freeze
|
32
|
-
ACTIVE_RECORD_CLASSES = %w[ApplicationRecord ActiveModel::Base
|
33
|
-
ActiveRecord::Base].freeze
|
30
|
+
ACTIVE_RECORD_CLASSES = %w[ApplicationRecord ActiveModel::Base ActiveRecord::Base].freeze
|
34
31
|
|
35
32
|
def on_def(node)
|
36
33
|
return unless BAD_METHODS.include?(node.method_name)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
|
-
#
|
6
|
+
# Checks that ActiveSupport aliases to core ruby methods
|
7
7
|
# are not used.
|
8
8
|
#
|
9
9
|
# @example
|
@@ -19,8 +19,11 @@ module RuboCop
|
|
19
19
|
# [1, 2, 'a'].append('b')
|
20
20
|
# [1, 2, 'a'].prepend('b')
|
21
21
|
#
|
22
|
-
class ActiveSupportAliases <
|
22
|
+
class ActiveSupportAliases < Base
|
23
|
+
extend AutoCorrector
|
24
|
+
|
23
25
|
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
26
|
+
RESTRICT_ON_SEND = %i[starts_with? ends_with? append prepend].freeze
|
24
27
|
|
25
28
|
ALIASES = {
|
26
29
|
starts_with?: {
|
@@ -39,29 +42,17 @@ module RuboCop
|
|
39
42
|
|
40
43
|
def on_send(node)
|
41
44
|
ALIASES.each_key do |aliased_method|
|
42
|
-
|
43
|
-
public_send(aliased_method, node)
|
44
|
-
end
|
45
|
-
end
|
45
|
+
next unless public_send(aliased_method, node)
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
preferred_method = ALIASES[aliased_method][:original]
|
48
|
+
message = format(MSG, prefer: preferred_method, current: aliased_method)
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
replacement = ALIASES[method_name.to_sym][:original]
|
53
|
-
corrector.replace(node.loc.selector, replacement.to_s)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
50
|
+
add_offense(node, message: message) do |corrector|
|
51
|
+
next if append(node)
|
58
52
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
message: format(MSG, prefer: ALIASES[method_name][:original],
|
63
|
-
current: method_name)
|
64
|
-
)
|
53
|
+
corrector.replace(node.loc.selector, preferred_method)
|
54
|
+
end
|
55
|
+
end
|
65
56
|
end
|
66
57
|
end
|
67
58
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Checks for Rails framework classes that are patched directly instead of using Active Support load hooks. Direct
|
7
|
+
# patching forcibly loads the framework referenced, using hooks defers loading until it's actually needed.
|
8
|
+
#
|
9
|
+
# @safety
|
10
|
+
# While using lazy load hooks is recommended, it changes the order in which is code is loaded and may reveal
|
11
|
+
# load order dependency bugs.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
#
|
15
|
+
# # bad
|
16
|
+
# ActiveRecord::Base.include(MyClass)
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# ActiveSupport.on_load(:active_record) { include MyClass }
|
20
|
+
class ActiveSupportOnLoad < Base
|
21
|
+
extend AutoCorrector
|
22
|
+
|
23
|
+
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
24
|
+
RESTRICT_ON_SEND = %i[prepend include extend].freeze
|
25
|
+
LOAD_HOOKS = {
|
26
|
+
'ActionCable' => 'action_cable',
|
27
|
+
'ActionCable::Channel::Base' => 'action_cable_channel',
|
28
|
+
'ActionCable::Connection::Base' => 'action_cable_connection',
|
29
|
+
'ActionCable::Connection::TestCase' => 'action_cable_connection_test_case',
|
30
|
+
'ActionController::API' => 'action_controller',
|
31
|
+
'ActionController::Base' => 'action_controller',
|
32
|
+
'ActionController::TestCase' => 'action_controller_test_case',
|
33
|
+
'ActionDispatch::IntegrationTest' => 'action_dispatch_integration_test',
|
34
|
+
'ActionDispatch::Request' => 'action_dispatch_request',
|
35
|
+
'ActionDispatch::Response' => 'action_dispatch_response',
|
36
|
+
'ActionDispatch::SystemTestCase' => 'action_dispatch_system_test_case',
|
37
|
+
'ActionMailbox::Base' => 'action_mailbox',
|
38
|
+
'ActionMailbox::InboundEmail' => 'action_mailbox_inbound_email',
|
39
|
+
'ActionMailbox::Record' => 'action_mailbox_record',
|
40
|
+
'ActionMailbox::TestCase' => 'action_mailbox_test_case',
|
41
|
+
'ActionMailer::Base' => 'action_mailer',
|
42
|
+
'ActionMailer::TestCase' => 'action_mailer_test_case',
|
43
|
+
'ActionText::Content' => 'action_text_content',
|
44
|
+
'ActionText::Record' => 'action_text_record',
|
45
|
+
'ActionText::RichText' => 'action_text_rich_text',
|
46
|
+
'ActionView::Base' => 'action_view',
|
47
|
+
'ActionView::TestCase' => 'action_view_test_case',
|
48
|
+
'ActiveJob::Base' => 'active_job',
|
49
|
+
'ActiveJob::TestCase' => 'active_job_test_case',
|
50
|
+
'ActiveRecord::Base' => 'active_record',
|
51
|
+
'ActiveStorage::Attachment' => 'active_storage_attachment',
|
52
|
+
'ActiveStorage::Blob' => 'active_storage_blob',
|
53
|
+
'ActiveStorage::Record' => 'active_storage_record',
|
54
|
+
'ActiveStorage::VariantRecord' => 'active_storage_variant_record',
|
55
|
+
'ActiveSupport::TestCase' => 'active_support_test_case'
|
56
|
+
}.freeze
|
57
|
+
|
58
|
+
def on_send(node)
|
59
|
+
receiver, method, arguments = *node # rubocop:disable InternalAffairs/NodeDestructuring
|
60
|
+
return unless receiver && (hook = LOAD_HOOKS[receiver.const_name])
|
61
|
+
|
62
|
+
preferred = "ActiveSupport.on_load(:#{hook}) { #{method} #{arguments.source} }"
|
63
|
+
add_offense(node, message: format(MSG, prefer: preferred, current: node.source)) do |corrector|
|
64
|
+
corrector.replace(node, preferred)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Checks for migrations using `add_column` that have an `index`
|
7
|
+
# key. `add_column` does not accept `index`, but also does not raise an
|
8
|
+
# error for extra keys, so it is possible to mistakenly add the key without
|
9
|
+
# realizing it will not actually add an index.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad (will not add an index)
|
13
|
+
# add_column :table, :column, :integer, index: true
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# add_column :table, :column, :integer
|
17
|
+
# add_index :table, :column
|
18
|
+
#
|
19
|
+
class AddColumnIndex < Base
|
20
|
+
extend AutoCorrector
|
21
|
+
include RangeHelp
|
22
|
+
|
23
|
+
MSG = '`add_column` does not accept an `index` key, use `add_index` instead.'
|
24
|
+
RESTRICT_ON_SEND = %i[add_column].freeze
|
25
|
+
|
26
|
+
# @!method add_column_with_index(node)
|
27
|
+
def_node_matcher :add_column_with_index, <<~PATTERN
|
28
|
+
(
|
29
|
+
send nil? :add_column $_table $_column
|
30
|
+
<(hash <$(pair {(sym :index) (str "index")} $_) ...>) ...>
|
31
|
+
)
|
32
|
+
PATTERN
|
33
|
+
|
34
|
+
def on_send(node)
|
35
|
+
table, column, pair, value = add_column_with_index(node)
|
36
|
+
return unless pair
|
37
|
+
|
38
|
+
add_offense(pair) do |corrector|
|
39
|
+
corrector.remove(index_range(pair))
|
40
|
+
|
41
|
+
add_index = "add_index #{table.source}, #{column.source}"
|
42
|
+
add_index_opts = ''
|
43
|
+
|
44
|
+
if value.hash_type?
|
45
|
+
hash = value.source_range.adjust(begin_pos: 1, end_pos: -1).source.strip
|
46
|
+
add_index_opts = ", #{hash}"
|
47
|
+
end
|
48
|
+
|
49
|
+
corrector.insert_after(node, "\n#{add_index}#{add_index_opts}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def index_range(pair_node)
|
56
|
+
range_with_surrounding_comma(range_with_surrounding_space(pair_node.source_range, side: :left), :left)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Enforces that there is only one call to `after_commit`
|
7
|
+
# (and its aliases - `after_create_commit`, `after_update_commit`,
|
8
|
+
# and `after_destroy_commit`) with the same callback name per model.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# # This won't be triggered.
|
13
|
+
# after_create_commit :log_action
|
14
|
+
#
|
15
|
+
# # This will override the callback added by
|
16
|
+
# # after_create_commit.
|
17
|
+
# after_update_commit :log_action
|
18
|
+
#
|
19
|
+
# # bad
|
20
|
+
# # This won't be triggered.
|
21
|
+
# after_commit :log_action, on: :create
|
22
|
+
# # This won't be triggered.
|
23
|
+
# after_update_commit :log_action
|
24
|
+
# # This will override both previous callbacks.
|
25
|
+
# after_commit :log_action, on: :destroy
|
26
|
+
#
|
27
|
+
# # good
|
28
|
+
# after_save_commit :log_action
|
29
|
+
#
|
30
|
+
# # good
|
31
|
+
# after_create_commit :log_create_action
|
32
|
+
# after_update_commit :log_update_action
|
33
|
+
#
|
34
|
+
class AfterCommitOverride < Base
|
35
|
+
include ClassSendNodeHelper
|
36
|
+
|
37
|
+
MSG = 'There can only be one `after_*_commit :%<name>s` hook defined for a model.'
|
38
|
+
|
39
|
+
AFTER_COMMIT_CALLBACKS = %i[
|
40
|
+
after_commit
|
41
|
+
after_create_commit
|
42
|
+
after_update_commit
|
43
|
+
after_save_commit
|
44
|
+
after_destroy_commit
|
45
|
+
].freeze
|
46
|
+
|
47
|
+
def on_class(class_node)
|
48
|
+
seen_callback_names = {}
|
49
|
+
|
50
|
+
each_after_commit_callback(class_node) do |node|
|
51
|
+
callback_name = node.arguments[0].value
|
52
|
+
if seen_callback_names.key?(callback_name)
|
53
|
+
add_offense(node, message: format(MSG, name: callback_name))
|
54
|
+
else
|
55
|
+
seen_callback_names[callback_name] = true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def each_after_commit_callback(class_node)
|
63
|
+
class_send_nodes(class_node).each do |node|
|
64
|
+
yield node if after_commit_callback?(node) && named_callback?(node)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def after_commit_callback?(node)
|
69
|
+
AFTER_COMMIT_CALLBACKS.include?(node.method_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def named_callback?(node)
|
73
|
+
name = node.first_argument
|
74
|
+
return false unless name
|
75
|
+
|
76
|
+
name.sym_type?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Rails
|
6
|
+
# Checks that controllers subclass `ApplicationController`.
|
7
|
+
#
|
8
|
+
# @safety
|
9
|
+
# This cop's autocorrection is unsafe because it may let the logic from `ApplicationController`
|
10
|
+
# sneak into a controller that is not purposed to inherit logic common among other controllers.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# # good
|
15
|
+
# class MyController < ApplicationController
|
16
|
+
# # ...
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # bad
|
20
|
+
# class MyController < ActionController::Base
|
21
|
+
# # ...
|
22
|
+
# end
|
23
|
+
class ApplicationController < Base
|
24
|
+
extend AutoCorrector
|
25
|
+
|
26
|
+
MSG = 'Controllers should subclass `ApplicationController`.'
|
27
|
+
SUPERCLASS = 'ApplicationController'
|
28
|
+
BASE_PATTERN = '(const (const {nil? cbase} :ActionController) :Base)'
|
29
|
+
|
30
|
+
# rubocop:disable Layout/ClassStructure
|
31
|
+
include RuboCop::Cop::EnforceSuperclass
|
32
|
+
# rubocop:enable Layout/ClassStructure
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -3,7 +3,11 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Rails
|
6
|
-
#
|
6
|
+
# Checks that jobs subclass `ApplicationJob` with Rails 5.0.
|
7
|
+
#
|
8
|
+
# @safety
|
9
|
+
# This cop's autocorrection is unsafe because it may let the logic from `ApplicationJob`
|
10
|
+
# sneak into a job that is not purposed to inherit logic common among other jobs.
|
7
11
|
#
|
8
12
|
# @example
|
9
13
|
#
|
@@ -16,14 +20,15 @@ module RuboCop
|
|
16
20
|
# class Rails4Job < ActiveJob::Base
|
17
21
|
# # ...
|
18
22
|
# end
|
19
|
-
class ApplicationJob <
|
23
|
+
class ApplicationJob < Base
|
24
|
+
extend AutoCorrector
|
20
25
|
extend TargetRailsVersion
|
21
26
|
|
22
27
|
minimum_target_rails_version 5.0
|
23
28
|
|
24
29
|
MSG = 'Jobs should subclass `ApplicationJob`.'
|
25
30
|
SUPERCLASS = 'ApplicationJob'
|
26
|
-
BASE_PATTERN = '(const (const nil? :ActiveJob) :Base)'
|
31
|
+
BASE_PATTERN = '(const (const {nil? cbase} :ActiveJob) :Base)'
|
27
32
|
|
28
33
|
# rubocop:disable Layout/ClassStructure
|
29
34
|
include RuboCop::Cop::EnforceSuperclass
|
@@ -31,7 +36,7 @@ module RuboCop
|
|
31
36
|
|
32
37
|
def autocorrect(node)
|
33
38
|
lambda do |corrector|
|
34
|
-
corrector.replace(node
|
39
|
+
corrector.replace(node, self.class::SUPERCLASS)
|
35
40
|
end
|
36
41
|
end
|
37
42
|
end
|