rubocop-rails 2.26.2 → 2.32.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 +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +45 -7
- data/config/default.yml +87 -49
- data/lib/rubocop/cop/mixin/active_record_helper.rb +2 -2
- data/lib/rubocop/cop/mixin/active_record_migrations_helper.rb +2 -2
- data/lib/rubocop/cop/mixin/database_type_resolvable.rb +2 -2
- data/lib/rubocop/cop/mixin/enforce_superclass.rb +6 -1
- data/lib/rubocop/cop/mixin/index_method.rb +69 -61
- data/lib/rubocop/cop/mixin/routes_helper.rb +20 -0
- data/lib/rubocop/cop/mixin/target_rails_version.rb +3 -5
- data/lib/rubocop/cop/rails/add_column_index.rb +1 -0
- data/lib/rubocop/cop/rails/arel_star.rb +5 -5
- data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
- data/lib/rubocop/cop/rails/blank.rb +1 -1
- data/lib/rubocop/cop/rails/bulk_change_table.rb +1 -0
- data/lib/rubocop/cop/rails/content_tag.rb +1 -1
- data/lib/rubocop/cop/rails/dangerous_column_names.rb +2 -0
- data/lib/rubocop/cop/rails/delegate.rb +53 -7
- data/lib/rubocop/cop/rails/duplicate_association.rb +8 -4
- data/lib/rubocop/cop/rails/eager_evaluation_log_message.rb +1 -3
- data/lib/rubocop/cop/rails/enum_syntax.rb +2 -0
- data/lib/rubocop/cop/rails/env_local.rb +26 -3
- data/lib/rubocop/cop/rails/file_path.rb +61 -9
- data/lib/rubocop/cop/rails/http_positional_arguments.rb +7 -0
- data/lib/rubocop/cop/rails/index_by.rb +37 -12
- data/lib/rubocop/cop/rails/index_with.rb +37 -12
- data/lib/rubocop/cop/rails/inquiry.rb +1 -1
- data/lib/rubocop/cop/rails/lexically_scoped_action_filter.rb +11 -1
- data/lib/rubocop/cop/rails/match_route.rb +1 -9
- data/lib/rubocop/cop/rails/multiple_route_paths.rb +50 -0
- data/lib/rubocop/cop/rails/not_null_column.rb +6 -2
- data/lib/rubocop/cop/rails/output.rb +1 -2
- data/lib/rubocop/cop/rails/pluck.rb +30 -4
- data/lib/rubocop/cop/rails/pluralization_grammar.rb +1 -1
- data/lib/rubocop/cop/rails/presence.rb +1 -1
- data/lib/rubocop/cop/rails/present.rb +1 -1
- data/lib/rubocop/cop/rails/redundant_active_record_all_method.rb +1 -1
- data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +6 -1
- data/lib/rubocop/cop/rails/reflection_class_name.rb +2 -2
- data/lib/rubocop/cop/rails/relative_date_constant.rb +1 -1
- data/lib/rubocop/cop/rails/reversible_migration.rb +4 -1
- data/lib/rubocop/cop/rails/root_pathname_methods.rb +6 -1
- data/lib/rubocop/cop/rails/save_bang.rb +8 -7
- data/lib/rubocop/cop/rails/schema_comment.rb +2 -1
- data/lib/rubocop/cop/rails/select_map.rb +3 -2
- data/lib/rubocop/cop/rails/skips_model_validations.rb +1 -1
- data/lib/rubocop/cop/rails/squished_sql_heredocs.rb +1 -1
- data/lib/rubocop/cop/rails/strip_heredoc.rb +1 -1
- data/lib/rubocop/cop/rails/strong_parameters_expect.rb +104 -0
- data/lib/rubocop/cop/rails/three_state_boolean_column.rb +3 -2
- data/lib/rubocop/cop/rails/time_zone.rb +16 -7
- data/lib/rubocop/cop/rails/transaction_exit_statement.rb +7 -2
- data/lib/rubocop/cop/rails/uniq_before_pluck.rb +10 -33
- data/lib/rubocop/cop/rails/unique_validation_without_index.rb +1 -1
- data/lib/rubocop/cop/rails/where_range.rb +1 -1
- data/lib/rubocop/cop/rails_cops.rb +3 -0
- data/lib/rubocop/rails/migration_file_skippable.rb +54 -0
- data/lib/rubocop/rails/plugin.rb +48 -0
- data/lib/rubocop/rails/version.rb +1 -1
- data/lib/rubocop/rails.rb +1 -8
- data/lib/rubocop-rails.rb +4 -5
- metadata +28 -12
- data/lib/rubocop/rails/inject.rb +0 -18
@@ -6,7 +6,72 @@ module RuboCop
|
|
6
6
|
module IndexMethod # rubocop:disable Metrics/ModuleLength
|
7
7
|
RESTRICT_ON_SEND = %i[each_with_object to_h map collect []].freeze
|
8
8
|
|
9
|
-
|
9
|
+
# Internal helper class to hold match data
|
10
|
+
Captures = ::Struct.new(
|
11
|
+
:transformed_argname,
|
12
|
+
:transforming_body_expr
|
13
|
+
) do
|
14
|
+
def noop_transformation?
|
15
|
+
transforming_body_expr.lvar_type? && transforming_body_expr.children == [transformed_argname]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Internal helper class to hold autocorrect data
|
20
|
+
Autocorrection = ::Struct.new(:match, :block_node, :leading, :trailing) do
|
21
|
+
def self.from_each_with_object(node, match)
|
22
|
+
new(match, node, 0, 0)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_to_h(node, match)
|
26
|
+
new(match, node, 0, 0)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.from_map_to_h(node, match)
|
30
|
+
if node.block_literal?
|
31
|
+
strip_trailing_chars = 0
|
32
|
+
else
|
33
|
+
map_range = node.children.first.source_range
|
34
|
+
node_range = node.source_range
|
35
|
+
strip_trailing_chars = node_range.end_pos - map_range.end_pos
|
36
|
+
end
|
37
|
+
|
38
|
+
new(match, node.children.first, 0, strip_trailing_chars)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.from_hash_brackets_map(node, match)
|
42
|
+
new(match, node.children.last, "#{node.receiver.source}[".length, ']'.length)
|
43
|
+
end
|
44
|
+
|
45
|
+
def strip_prefix_and_suffix(node, corrector)
|
46
|
+
expression = node.source_range
|
47
|
+
corrector.remove_leading(expression, leading)
|
48
|
+
corrector.remove_trailing(expression, trailing)
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_new_method_name(new_method_name, corrector)
|
52
|
+
range = block_node.send_node.loc.selector
|
53
|
+
if (send_end = block_node.send_node.loc.end)
|
54
|
+
# If there are arguments (only true in the `each_with_object` case)
|
55
|
+
range = range.begin.join(send_end)
|
56
|
+
end
|
57
|
+
corrector.replace(range, new_method_name)
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_new_arg_name(transformed_argname, corrector)
|
61
|
+
return unless block_node.block_type?
|
62
|
+
|
63
|
+
corrector.replace(block_node.arguments, "|#{transformed_argname}|")
|
64
|
+
end
|
65
|
+
|
66
|
+
def set_new_body_expression(transforming_body_expr, corrector)
|
67
|
+
body_source = transforming_body_expr.source
|
68
|
+
body_source = "{ #{body_source} }" if transforming_body_expr.hash_type? && !transforming_body_expr.braces?
|
69
|
+
|
70
|
+
corrector.replace(block_node.body, body_source)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def on_block(node)
|
10
75
|
on_bad_each_with_object(node) do |*match|
|
11
76
|
handle_possible_offense(node, match, 'each_with_object')
|
12
77
|
end
|
@@ -18,6 +83,9 @@ module RuboCop
|
|
18
83
|
end
|
19
84
|
end
|
20
85
|
|
86
|
+
alias on_numblock on_block
|
87
|
+
alias on_itblock on_block
|
88
|
+
|
21
89
|
def on_send(node)
|
22
90
|
on_bad_map_to_h(node) do |*match|
|
23
91
|
handle_possible_offense(node, match, 'map { ... }.to_h')
|
@@ -100,66 +168,6 @@ module RuboCop
|
|
100
168
|
correction.set_new_arg_name(captures.transformed_argname, corrector)
|
101
169
|
correction.set_new_body_expression(captures.transforming_body_expr, corrector)
|
102
170
|
end
|
103
|
-
|
104
|
-
# Internal helper class to hold match data
|
105
|
-
Captures = ::Struct.new(
|
106
|
-
:transformed_argname,
|
107
|
-
:transforming_body_expr
|
108
|
-
) do
|
109
|
-
def noop_transformation?
|
110
|
-
transforming_body_expr.lvar_type? && transforming_body_expr.children == [transformed_argname]
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
# Internal helper class to hold autocorrect data
|
115
|
-
Autocorrection = ::Struct.new(:match, :block_node, :leading, :trailing) do
|
116
|
-
def self.from_each_with_object(node, match)
|
117
|
-
new(match, node, 0, 0)
|
118
|
-
end
|
119
|
-
|
120
|
-
def self.from_to_h(node, match)
|
121
|
-
new(match, node, 0, 0)
|
122
|
-
end
|
123
|
-
|
124
|
-
def self.from_map_to_h(node, match)
|
125
|
-
strip_trailing_chars = 0
|
126
|
-
|
127
|
-
unless node.parent&.block_type?
|
128
|
-
map_range = node.children.first.source_range
|
129
|
-
node_range = node.source_range
|
130
|
-
strip_trailing_chars = node_range.end_pos - map_range.end_pos
|
131
|
-
end
|
132
|
-
|
133
|
-
new(match, node.children.first, 0, strip_trailing_chars)
|
134
|
-
end
|
135
|
-
|
136
|
-
def self.from_hash_brackets_map(node, match)
|
137
|
-
new(match, node.children.last, "#{node.receiver.source}[".length, ']'.length)
|
138
|
-
end
|
139
|
-
|
140
|
-
def strip_prefix_and_suffix(node, corrector)
|
141
|
-
expression = node.source_range
|
142
|
-
corrector.remove_leading(expression, leading)
|
143
|
-
corrector.remove_trailing(expression, trailing)
|
144
|
-
end
|
145
|
-
|
146
|
-
def set_new_method_name(new_method_name, corrector)
|
147
|
-
range = block_node.send_node.loc.selector
|
148
|
-
if (send_end = block_node.send_node.loc.end)
|
149
|
-
# If there are arguments (only true in the `each_with_object` case)
|
150
|
-
range = range.begin.join(send_end)
|
151
|
-
end
|
152
|
-
corrector.replace(range, new_method_name)
|
153
|
-
end
|
154
|
-
|
155
|
-
def set_new_arg_name(transformed_argname, corrector)
|
156
|
-
corrector.replace(block_node.arguments, "|#{transformed_argname}|")
|
157
|
-
end
|
158
|
-
|
159
|
-
def set_new_body_expression(transforming_body_expr, corrector)
|
160
|
-
corrector.replace(block_node.body, transforming_body_expr.source)
|
161
|
-
end
|
162
|
-
end
|
163
171
|
end
|
164
172
|
end
|
165
173
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
# Common functionality for cops working with routes.
|
6
|
+
module RoutesHelper
|
7
|
+
extend NodePattern::Macros
|
8
|
+
|
9
|
+
HTTP_METHODS = %i[get post put patch delete].freeze
|
10
|
+
|
11
|
+
def_node_matcher :routes_draw?, <<~PATTERN
|
12
|
+
(send (send _ :routes) :draw)
|
13
|
+
PATTERN
|
14
|
+
|
15
|
+
def within_routes?(node)
|
16
|
+
node.each_ancestor(:block).any? { |block| routes_draw?(block.send_node) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -7,6 +7,9 @@ module RuboCop
|
|
7
7
|
# Informs the base RuboCop gem that it the Rails version is checked via `requires_gem` API,
|
8
8
|
# without needing to call this `#support_target_rails_version` method.
|
9
9
|
USES_REQUIRES_GEM_API = true
|
10
|
+
# Look for `railties` instead of `rails`, to support apps that only use a subset of `rails`
|
11
|
+
# See https://github.com/rubocop/rubocop/pull/11289
|
12
|
+
TARGET_GEM_NAME = 'railties' # :nodoc:
|
10
13
|
|
11
14
|
def minimum_target_rails_version(version)
|
12
15
|
if respond_to?(:requires_gem)
|
@@ -33,11 +36,6 @@ module RuboCop
|
|
33
36
|
@minimum_target_rails_version <= version
|
34
37
|
end
|
35
38
|
end
|
36
|
-
|
37
|
-
# Look for `railties` instead of `rails`, to support apps that only use a subset of `rails`
|
38
|
-
# See https://github.com/rubocop/rubocop/pull/11289
|
39
|
-
TARGET_GEM_NAME = 'railties'
|
40
|
-
private_constant :TARGET_GEM_NAME
|
41
39
|
end
|
42
40
|
end
|
43
41
|
end
|
@@ -5,14 +5,14 @@ module RuboCop
|
|
5
5
|
module Rails
|
6
6
|
# Prevents usage of `"*"` on an Arel::Table column reference.
|
7
7
|
#
|
8
|
-
# Using `arel_table["
|
9
|
-
# quoted asterisk (e.g.
|
10
|
-
# database to look for a column named
|
8
|
+
# Using `arel_table["\*"]` causes the outputted string to be a literal
|
9
|
+
# quoted asterisk (e.g. `my_model`.`*`). This causes the
|
10
|
+
# database to look for a column named `\*` (or `"*"`) as opposed
|
11
11
|
# to expanding the column list as one would likely expect.
|
12
12
|
#
|
13
13
|
# @safety
|
14
|
-
# This cop's autocorrection is unsafe because it turns a quoted
|
15
|
-
# an SQL `*`, unquoted.
|
14
|
+
# This cop's autocorrection is unsafe because it turns a quoted `\*` into
|
15
|
+
# an SQL `*`, unquoted. `\*` is a valid column name in certain databases
|
16
16
|
# supported by Rails, and even though it is usually a mistake,
|
17
17
|
# it might denote legitimate access to a column named `*`.
|
18
18
|
#
|
@@ -123,7 +123,7 @@ module RuboCop
|
|
123
123
|
def on_if(node)
|
124
124
|
return unless cop_config['UnlessPresent']
|
125
125
|
return unless node.unless?
|
126
|
-
return if node.else? && config.
|
126
|
+
return if node.else? && config.cop_enabled?('Style/UnlessElse')
|
127
127
|
|
128
128
|
unless_present?(node) do |method_call, receiver|
|
129
129
|
range = unless_condition(node, method_call)
|
@@ -15,6 +15,9 @@ module RuboCop
|
|
15
15
|
# without using the `delegate` method will be a violation.
|
16
16
|
# When set to `false`, this case is legal.
|
17
17
|
#
|
18
|
+
# It is disabled for controllers in order to keep controller actions
|
19
|
+
# explicitly defined.
|
20
|
+
#
|
18
21
|
# @example
|
19
22
|
# # bad
|
20
23
|
# def bar
|
@@ -68,12 +71,13 @@ module RuboCop
|
|
68
71
|
|
69
72
|
def_node_matcher :delegate?, <<~PATTERN
|
70
73
|
(def _method_name _args
|
71
|
-
(send {(send nil? _) (self)} _ ...))
|
74
|
+
(send {(send nil? _) (self) (send (self) :class) ({cvar gvar ivar} _) (const _ _)} _ ...))
|
72
75
|
PATTERN
|
73
76
|
|
74
77
|
def on_def(node)
|
75
78
|
return unless trivial_delegate?(node)
|
76
79
|
return if private_or_protected_delegation(node)
|
80
|
+
return if module_function_declared?(node)
|
77
81
|
|
78
82
|
register_offense(node)
|
79
83
|
end
|
@@ -82,15 +86,40 @@ module RuboCop
|
|
82
86
|
|
83
87
|
def register_offense(node)
|
84
88
|
add_offense(node.loc.keyword) do |corrector|
|
85
|
-
|
89
|
+
receiver = determine_register_offense_receiver(node.body.receiver)
|
90
|
+
delegation = build_delegation(node, receiver)
|
86
91
|
|
87
|
-
|
92
|
+
corrector.replace(node, delegation)
|
93
|
+
end
|
94
|
+
end
|
88
95
|
|
89
|
-
|
90
|
-
|
96
|
+
def determine_register_offense_receiver(receiver)
|
97
|
+
case receiver.type
|
98
|
+
when :self
|
99
|
+
'self'
|
100
|
+
when :const
|
101
|
+
full_name = full_const_name(receiver)
|
102
|
+
full_name.include?('::') ? ":'#{full_name}'" : ":#{full_name}"
|
103
|
+
when :cvar, :gvar, :ivar
|
104
|
+
":#{receiver.source}"
|
105
|
+
else
|
106
|
+
":#{receiver.method_name}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_delegation(node, receiver)
|
111
|
+
delegation = ["delegate :#{node.body.method_name}", "to: #{receiver}"]
|
112
|
+
delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
|
113
|
+
delegation.join(', ')
|
114
|
+
end
|
91
115
|
|
92
|
-
|
116
|
+
def full_const_name(node)
|
117
|
+
return unless node.const_type?
|
118
|
+
unless node.namespace
|
119
|
+
return node.absolute? ? "::#{node.source}" : node.source
|
93
120
|
end
|
121
|
+
|
122
|
+
"#{full_const_name(node.namespace)}::#{node.short_name}"
|
94
123
|
end
|
95
124
|
|
96
125
|
def trivial_delegate?(def_node)
|
@@ -120,13 +149,30 @@ module RuboCop
|
|
120
149
|
def prefixed_method_name(body)
|
121
150
|
return '' if body.receiver.self_type?
|
122
151
|
|
123
|
-
[body.receiver
|
152
|
+
[determine_prefixed_method_receiver_name(body.receiver), body.method_name].join('_').to_sym
|
153
|
+
end
|
154
|
+
|
155
|
+
def determine_prefixed_method_receiver_name(receiver)
|
156
|
+
case receiver.type
|
157
|
+
when :cvar, :gvar, :ivar
|
158
|
+
receiver.source
|
159
|
+
when :const
|
160
|
+
full_const_name(receiver)
|
161
|
+
else
|
162
|
+
receiver.method_name.to_s
|
163
|
+
end
|
124
164
|
end
|
125
165
|
|
126
166
|
def private_or_protected_delegation(node)
|
127
167
|
private_or_protected_inline(node) || node_visibility(node) != :public
|
128
168
|
end
|
129
169
|
|
170
|
+
def module_function_declared?(node)
|
171
|
+
node.each_ancestor(:module, :begin).any? do |ancestor|
|
172
|
+
ancestor.children.any? { |child| child.send_type? && child.method?(:module_function) }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
130
176
|
def private_or_protected_inline(node)
|
131
177
|
processed_source[node.first_line - 1].strip.match?(/\A(private )|(protected )/)
|
132
178
|
end
|
@@ -63,11 +63,15 @@ module RuboCop
|
|
63
63
|
private
|
64
64
|
|
65
65
|
def register_offense(name, nodes, message_template)
|
66
|
-
nodes.
|
67
|
-
add_offense(node, message: format(message_template, name: name)) do |corrector|
|
68
|
-
next if same_line?(nodes.last, node)
|
66
|
+
last_node = nodes.last
|
69
67
|
|
70
|
-
|
68
|
+
nodes.each_with_index do |node, index|
|
69
|
+
add_offense(node, message: format(message_template, name: name)) do |corrector|
|
70
|
+
if index.zero?
|
71
|
+
corrector.replace(node, last_node.source)
|
72
|
+
else
|
73
|
+
corrector.remove(range_by_whole_lines(node.source_range, include_final_newline: true))
|
74
|
+
end
|
71
75
|
end
|
72
76
|
end
|
73
77
|
end
|
@@ -45,12 +45,10 @@ module RuboCop
|
|
45
45
|
return if node.parent&.block_type?
|
46
46
|
|
47
47
|
interpolated_string_passed_to_debug(node) do |arguments|
|
48
|
-
message = format(MSG)
|
49
|
-
|
50
48
|
range = replacement_range(node)
|
51
49
|
replacement = replacement_source(node, arguments)
|
52
50
|
|
53
|
-
add_offense(range
|
51
|
+
add_offense(range) do |corrector|
|
54
52
|
corrector.replace(range, replacement)
|
55
53
|
end
|
56
54
|
end
|
@@ -19,20 +19,33 @@ module RuboCop
|
|
19
19
|
extend TargetRailsVersion
|
20
20
|
|
21
21
|
MSG = 'Use `Rails.env.local?` instead.'
|
22
|
+
MSG_NEGATED = 'Use `!Rails.env.local?` instead.'
|
22
23
|
LOCAL_ENVIRONMENTS = %i[development? test?].to_set.freeze
|
23
24
|
|
24
25
|
minimum_target_rails_version 7.1
|
25
26
|
|
26
|
-
# @!method
|
27
|
-
def_node_matcher :
|
27
|
+
# @!method rails_env_local_or?(node)
|
28
|
+
def_node_matcher :rails_env_local_or?, <<~PATTERN
|
28
29
|
(or
|
29
30
|
(send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
|
30
31
|
(send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
|
31
32
|
)
|
32
33
|
PATTERN
|
33
34
|
|
35
|
+
# @!method rails_env_local_and?(node)
|
36
|
+
def_node_matcher :rails_env_local_and?, <<~PATTERN
|
37
|
+
(and
|
38
|
+
(send
|
39
|
+
(send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
|
40
|
+
:!)
|
41
|
+
(send
|
42
|
+
(send (send (const {cbase nil? } :Rails) :env) $%LOCAL_ENVIRONMENTS)
|
43
|
+
:!)
|
44
|
+
)
|
45
|
+
PATTERN
|
46
|
+
|
34
47
|
def on_or(node)
|
35
|
-
|
48
|
+
rails_env_local_or?(node) do |*environments|
|
36
49
|
next unless environments.to_set == LOCAL_ENVIRONMENTS
|
37
50
|
|
38
51
|
add_offense(node) do |corrector|
|
@@ -40,6 +53,16 @@ module RuboCop
|
|
40
53
|
end
|
41
54
|
end
|
42
55
|
end
|
56
|
+
|
57
|
+
def on_and(node)
|
58
|
+
rails_env_local_and?(node) do |*environments|
|
59
|
+
next unless environments.to_set == LOCAL_ENVIRONMENTS
|
60
|
+
|
61
|
+
add_offense(node, message: MSG_NEGATED) do |corrector|
|
62
|
+
corrector.replace(node, '!Rails.env.local?')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
43
66
|
end
|
44
67
|
end
|
45
68
|
end
|
@@ -6,6 +6,9 @@ module RuboCop
|
|
6
6
|
# Identifies usages of file path joining process to use `Rails.root.join` clause.
|
7
7
|
# It is used to add uniformity when joining paths.
|
8
8
|
#
|
9
|
+
# NOTE: This cop ignores leading slashes in string literal arguments for `Rails.root.join`
|
10
|
+
# and multiple slashes in string literal arguments for `Rails.root.join` and `File.join`.
|
11
|
+
#
|
9
12
|
# @example EnforcedStyle: slashes (default)
|
10
13
|
# # bad
|
11
14
|
# Rails.root.join('app', 'models', 'goober')
|
@@ -66,6 +69,8 @@ module RuboCop
|
|
66
69
|
|
67
70
|
def on_send(node)
|
68
71
|
check_for_file_join_with_rails_root(node)
|
72
|
+
return unless node.receiver
|
73
|
+
|
69
74
|
check_for_rails_root_join_with_slash_separated_path(node)
|
70
75
|
check_for_rails_root_join_with_string_arguments(node)
|
71
76
|
end
|
@@ -76,6 +81,7 @@ module RuboCop
|
|
76
81
|
rails_root_index = find_rails_root_index(node)
|
77
82
|
slash_node = node.children[rails_root_index + 1]
|
78
83
|
return unless slash_node&.str_type? && slash_node.source.start_with?(File::SEPARATOR)
|
84
|
+
return unless node.children[rails_root_index].children.first.send_type?
|
79
85
|
|
80
86
|
register_offense(node, require_to_s: false) do |corrector|
|
81
87
|
autocorrect_slash_after_rails_root_in_dstr(corrector, node, rails_root_index)
|
@@ -94,7 +100,7 @@ module RuboCop
|
|
94
100
|
|
95
101
|
def check_for_file_join_with_rails_root(node)
|
96
102
|
return unless file_join_nodes?(node)
|
97
|
-
return unless node.arguments
|
103
|
+
return unless valid_arguments_for_file_join_with_rails_root?(node.arguments)
|
98
104
|
|
99
105
|
register_offense(node, require_to_s: true) do |corrector|
|
100
106
|
autocorrect_file_join(corrector, node) unless node.first_argument.array_type?
|
@@ -105,8 +111,7 @@ module RuboCop
|
|
105
111
|
return unless style == :slashes
|
106
112
|
return unless rails_root_nodes?(node)
|
107
113
|
return unless rails_root_join_nodes?(node)
|
108
|
-
return unless node.arguments
|
109
|
-
return unless node.arguments.all?(&:str_type?)
|
114
|
+
return unless valid_string_arguments_for_rails_root_join?(node.arguments)
|
110
115
|
|
111
116
|
register_offense(node, require_to_s: false) do |corrector|
|
112
117
|
autocorrect_rails_root_join_with_string_arguments(corrector, node)
|
@@ -117,15 +122,42 @@ module RuboCop
|
|
117
122
|
return unless style == :arguments
|
118
123
|
return unless rails_root_nodes?(node)
|
119
124
|
return unless rails_root_join_nodes?(node)
|
120
|
-
return unless node.arguments
|
125
|
+
return unless valid_slash_separated_path_for_rails_root_join?(node.arguments)
|
121
126
|
|
122
127
|
register_offense(node, require_to_s: false) do |corrector|
|
123
128
|
autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
|
124
129
|
end
|
125
130
|
end
|
126
131
|
|
127
|
-
def
|
128
|
-
|
132
|
+
def valid_arguments_for_file_join_with_rails_root?(arguments)
|
133
|
+
return false unless arguments.any? { |arg| rails_root_nodes?(arg) }
|
134
|
+
|
135
|
+
arguments.none? { |arg| arg.variable? || arg.const_type? || string_contains_multiple_slashes?(arg) }
|
136
|
+
end
|
137
|
+
|
138
|
+
def valid_string_arguments_for_rails_root_join?(arguments)
|
139
|
+
return false unless arguments.size > 1
|
140
|
+
return false unless arguments.all?(&:str_type?)
|
141
|
+
|
142
|
+
arguments.none? { |arg| string_with_leading_slash?(arg) || string_contains_multiple_slashes?(arg) }
|
143
|
+
end
|
144
|
+
|
145
|
+
def valid_slash_separated_path_for_rails_root_join?(arguments)
|
146
|
+
return false unless arguments.any? { |arg| string_contains_slash?(arg) }
|
147
|
+
|
148
|
+
arguments.none? { |arg| string_with_leading_slash?(arg) || string_contains_multiple_slashes?(arg) }
|
149
|
+
end
|
150
|
+
|
151
|
+
def string_contains_slash?(node)
|
152
|
+
node.str_type? && node.value.include?(File::SEPARATOR)
|
153
|
+
end
|
154
|
+
|
155
|
+
def string_contains_multiple_slashes?(node)
|
156
|
+
node.str_type? && node.value.include?('//')
|
157
|
+
end
|
158
|
+
|
159
|
+
def string_with_leading_slash?(node)
|
160
|
+
node.str_type? && node.value.start_with?(File::SEPARATOR)
|
129
161
|
end
|
130
162
|
|
131
163
|
def register_offense(node, require_to_s:, &block)
|
@@ -170,7 +202,17 @@ module RuboCop
|
|
170
202
|
end
|
171
203
|
|
172
204
|
def autocorrect_file_join(corrector, node)
|
205
|
+
replace_receiver_with_rails_root(corrector, node)
|
206
|
+
remove_first_argument_with_comma(corrector, node)
|
207
|
+
process_arguments(corrector, node.arguments)
|
208
|
+
append_to_string_conversion(corrector, node)
|
209
|
+
end
|
210
|
+
|
211
|
+
def replace_receiver_with_rails_root(corrector, node)
|
173
212
|
corrector.replace(node.receiver, 'Rails.root')
|
213
|
+
end
|
214
|
+
|
215
|
+
def remove_first_argument_with_comma(corrector, node)
|
174
216
|
corrector.remove(
|
175
217
|
range_with_surrounding_space(
|
176
218
|
range_with_surrounding_comma(
|
@@ -180,9 +222,19 @@ module RuboCop
|
|
180
222
|
side: :right
|
181
223
|
)
|
182
224
|
)
|
183
|
-
|
184
|
-
|
225
|
+
end
|
226
|
+
|
227
|
+
def process_arguments(corrector, arguments)
|
228
|
+
arguments.each do |argument|
|
229
|
+
if argument.str_type?
|
230
|
+
corrector.replace(argument, argument.value.delete_prefix('/').inspect)
|
231
|
+
elsif argument.array_type?
|
232
|
+
corrector.replace(argument, "*#{argument.source}")
|
233
|
+
end
|
185
234
|
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def append_to_string_conversion(corrector, node)
|
186
238
|
corrector.insert_after(node, '.to_s')
|
187
239
|
end
|
188
240
|
|
@@ -203,7 +255,7 @@ module RuboCop
|
|
203
255
|
|
204
256
|
def autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
|
205
257
|
node.arguments.each do |argument|
|
206
|
-
next unless
|
258
|
+
next unless string_contains_slash?(argument)
|
207
259
|
|
208
260
|
index = argument.source.index(File::SEPARATOR)
|
209
261
|
rest = inner_range_of(argument).adjust(begin_pos: index - 1)
|
@@ -40,6 +40,10 @@ module RuboCop
|
|
40
40
|
(hash (kwsplat _))
|
41
41
|
PATTERN
|
42
42
|
|
43
|
+
def_node_matcher :forwarded_kwrestarg?, <<~PATTERN
|
44
|
+
(hash (forwarded-kwrestarg))
|
45
|
+
PATTERN
|
46
|
+
|
43
47
|
def_node_matcher :include_rack_test_methods?, <<~PATTERN
|
44
48
|
(send nil? :include
|
45
49
|
(const
|
@@ -83,7 +87,9 @@ module RuboCop
|
|
83
87
|
end
|
84
88
|
end
|
85
89
|
|
90
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
86
91
|
def needs_conversion?(data)
|
92
|
+
return false if data.forwarded_args_type? || forwarded_kwrestarg?(data)
|
87
93
|
return true unless data.hash_type?
|
88
94
|
return false if kwsplat_hash?(data)
|
89
95
|
|
@@ -91,6 +97,7 @@ module RuboCop
|
|
91
97
|
special_keyword_arg?(pair.key) || (format_arg?(pair.key) && data.pairs.one?)
|
92
98
|
end
|
93
99
|
end
|
100
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
94
101
|
|
95
102
|
def special_keyword_arg?(node)
|
96
103
|
node.sym_type? && KEYWORD_ARGS.include?(node.value)
|