rubocop-rails 2.29.1 → 2.30.3
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/README.md +9 -7
- data/config/default.yml +18 -2
- 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/index_method.rb +65 -65
- data/lib/rubocop/cop/rails/belongs_to.rb +1 -1
- data/lib/rubocop/cop/rails/blank.rb +1 -1
- data/lib/rubocop/cop/rails/content_tag.rb +1 -1
- data/lib/rubocop/cop/rails/delegate.rb +53 -7
- data/lib/rubocop/cop/rails/file_path.rb +36 -7
- 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/pluck.rb +2 -2
- 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/reflection_class_name.rb +1 -1
- data/lib/rubocop/cop/rails/relative_date_constant.rb +1 -1
- data/lib/rubocop/cop/rails/reversible_migration.rb +1 -1
- data/lib/rubocop/cop/rails/save_bang.rb +4 -4
- data/lib/rubocop/cop/rails/skips_model_validations.rb +1 -1
- data/lib/rubocop/cop/rails/strip_heredoc.rb +1 -1
- data/lib/rubocop/cop/rails/transaction_exit_statement.rb +2 -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/rails/plugin.rb +48 -0
- data/lib/rubocop/rails/version.rb +1 -1
- data/lib/rubocop/rails.rb +1 -7
- data/lib/rubocop-rails.rb +1 -4
- metadata +23 -8
- data/lib/rubocop/rails/inject.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a45ebdeef3a184afbb85ff1e1685fa978198ee00cf3bbbc0f1cbfa04b56f9dc7
|
4
|
+
data.tar.gz: 2d06943201b3836891ed9f8c0bd9a5e2bebd90de7736351add71af9460e271b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed82ce027a4bee9e6dfa16dd763fa856d7b19b96356b918d469ef1274ef0c5a4e8ea5e949c6aaed84ee6d0f290ca838e34e352929640294a6bd4d579c0145cdd
|
7
|
+
data.tar.gz: 7e11061742bc02b64742e4219ccfea80d5befb27572cb043d7ebc57c7a616f2fd2c8b829ac08dfad6b644f23d8a0f096c8b571cc5b8e9ecf34e7769f1f551fb4
|
data/README.md
CHANGED
@@ -5,7 +5,8 @@
|
|
5
5
|
|
6
6
|
A [RuboCop](https://github.com/rubocop/rubocop) extension focused on enforcing Rails best practices and coding conventions.
|
7
7
|
|
8
|
-
|
8
|
+
> [!IMPORTANT]
|
9
|
+
> This repository manages rubocop-rails gem (>= 2.0.0). rubocop-rails gem (<= 1.5.0) has been renamed to [rubocop-rails_config](https://rubygems.org/gems/rubocop-rails_config) gem.
|
9
10
|
|
10
11
|
## Installation
|
11
12
|
|
@@ -31,13 +32,13 @@ ways to do this:
|
|
31
32
|
Put this into your `.rubocop.yml`.
|
32
33
|
|
33
34
|
```yaml
|
34
|
-
|
35
|
+
plugins: rubocop-rails
|
35
36
|
```
|
36
37
|
|
37
38
|
Alternatively, use the following array notation when specifying multiple extensions.
|
38
39
|
|
39
40
|
```yaml
|
40
|
-
|
41
|
+
plugins:
|
41
42
|
- rubocop-other-extension
|
42
43
|
- rubocop-rails
|
43
44
|
```
|
@@ -45,21 +46,22 @@ require:
|
|
45
46
|
Now you can run `rubocop` and it will automatically load the RuboCop Rails
|
46
47
|
cops together with the standard cops.
|
47
48
|
|
49
|
+
> [!NOTE]
|
50
|
+
> The plugin system is supported in RuboCop 1.72+. In earlier versions, use `require` instead of `plugins`.
|
51
|
+
|
48
52
|
### Command line
|
49
53
|
|
50
54
|
```sh
|
51
|
-
$ rubocop --
|
55
|
+
$ rubocop --plugin rubocop-rails
|
52
56
|
```
|
53
57
|
|
54
|
-
Note: `--rails` option is required while `rubocop` command supports `--rails` option.
|
55
|
-
|
56
58
|
### Rake task
|
57
59
|
|
58
60
|
```ruby
|
59
61
|
require 'rubocop/rake_task'
|
60
62
|
|
61
63
|
RuboCop::RakeTask.new do |task|
|
62
|
-
task.
|
64
|
+
task.plugins << 'rubocop-rails'
|
63
65
|
end
|
64
66
|
```
|
65
67
|
|
data/config/default.yml
CHANGED
@@ -28,7 +28,7 @@ AllCops:
|
|
28
28
|
# By specifying `MigratedSchemaVersion` option, migration files that have been migrated can be ignored.
|
29
29
|
# When `MigratedSchemaVersion: '20241231000000'` is set. Migration files lower than or equal to '20250101000000' will be ignored.
|
30
30
|
# For example, this is the timestamp in db/migrate/20250101000000_create_articles.rb.
|
31
|
-
MigratedSchemaVersion:
|
31
|
+
MigratedSchemaVersion: '19700101000000' # NOTE: Used as a sentinel value for the UNIX epoch time.
|
32
32
|
|
33
33
|
Lint/NumberConversion:
|
34
34
|
# Add Rails' duration methods to the ignore list for `Lint/NumberConversion`
|
@@ -77,6 +77,20 @@ Lint/SafeNavigationChain:
|
|
77
77
|
- try!
|
78
78
|
- in?
|
79
79
|
|
80
|
+
Lint/UselessAccessModifier:
|
81
|
+
# Add methods from `ActiveSupport::Concern` and `Module::Concerning`:
|
82
|
+
# https://api.rubyonrails.org/classes/ActiveSupport/Concern.html
|
83
|
+
# https://api.rubyonrails.org/classes/Module/Concerning
|
84
|
+
inherit_mode:
|
85
|
+
merge:
|
86
|
+
- ContextCreatingMethods
|
87
|
+
ContextCreatingMethods:
|
88
|
+
- class_methods
|
89
|
+
- included
|
90
|
+
- prepended
|
91
|
+
- concern
|
92
|
+
- concerning
|
93
|
+
|
80
94
|
Rails:
|
81
95
|
Enabled: true
|
82
96
|
DocumentationBaseURL: https://docs.rubocop.org/rubocop-rails
|
@@ -353,11 +367,13 @@ Rails/Delegate:
|
|
353
367
|
Description: 'Prefer delegate method for delegations.'
|
354
368
|
Enabled: true
|
355
369
|
VersionAdded: '0.21'
|
356
|
-
VersionChanged: '
|
370
|
+
VersionChanged: '2.30'
|
357
371
|
# When set to true, using the target object as a prefix of the
|
358
372
|
# method name without using the `delegate` method will be a
|
359
373
|
# violation. When set to false, this case is legal.
|
360
374
|
EnforceForPrefixed: true
|
375
|
+
Exclude:
|
376
|
+
- app/controllers/**/*.rb
|
361
377
|
|
362
378
|
Rails/DelegateAllowBlank:
|
363
379
|
Description: 'Do not use allow_blank as an option to delegate.'
|
@@ -87,7 +87,7 @@ module RuboCop
|
|
87
87
|
|
88
88
|
options.each_pair.find do |pair|
|
89
89
|
next unless pair.key.sym_type? && pair.key.value == :foreign_key
|
90
|
-
next unless pair.value.
|
90
|
+
next unless pair.value.type?(:sym, :str)
|
91
91
|
|
92
92
|
break pair.value.value.to_s
|
93
93
|
end
|
@@ -103,7 +103,7 @@ module RuboCop
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def in_where?(node)
|
106
|
-
send_node = node.each_ancestor(:
|
106
|
+
send_node = node.each_ancestor(:call).first
|
107
107
|
return false unless send_node
|
108
108
|
|
109
109
|
return true if WHERE_METHODS.include?(send_node.method_name)
|
@@ -6,6 +6,71 @@ 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
|
+
# 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 if block_node.numblock_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
|
+
|
9
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')
|
@@ -102,71 +167,6 @@ module RuboCop
|
|
102
167
|
correction.set_new_arg_name(captures.transformed_argname, corrector)
|
103
168
|
correction.set_new_body_expression(captures.transforming_body_expr, corrector)
|
104
169
|
end
|
105
|
-
|
106
|
-
# Internal helper class to hold match data
|
107
|
-
Captures = ::Struct.new(
|
108
|
-
:transformed_argname,
|
109
|
-
:transforming_body_expr
|
110
|
-
) do
|
111
|
-
def noop_transformation?
|
112
|
-
transforming_body_expr.lvar_type? && transforming_body_expr.children == [transformed_argname]
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
# Internal helper class to hold autocorrect data
|
117
|
-
Autocorrection = ::Struct.new(:match, :block_node, :leading, :trailing) do
|
118
|
-
def self.from_each_with_object(node, match)
|
119
|
-
new(match, node, 0, 0)
|
120
|
-
end
|
121
|
-
|
122
|
-
def self.from_to_h(node, match)
|
123
|
-
new(match, node, 0, 0)
|
124
|
-
end
|
125
|
-
|
126
|
-
def self.from_map_to_h(node, match)
|
127
|
-
if node.block_literal?
|
128
|
-
strip_trailing_chars = 0
|
129
|
-
else
|
130
|
-
map_range = node.children.first.source_range
|
131
|
-
node_range = node.source_range
|
132
|
-
strip_trailing_chars = node_range.end_pos - map_range.end_pos
|
133
|
-
end
|
134
|
-
|
135
|
-
new(match, node.children.first, 0, strip_trailing_chars)
|
136
|
-
end
|
137
|
-
|
138
|
-
def self.from_hash_brackets_map(node, match)
|
139
|
-
new(match, node.children.last, "#{node.receiver.source}[".length, ']'.length)
|
140
|
-
end
|
141
|
-
|
142
|
-
def strip_prefix_and_suffix(node, corrector)
|
143
|
-
expression = node.source_range
|
144
|
-
corrector.remove_leading(expression, leading)
|
145
|
-
corrector.remove_trailing(expression, trailing)
|
146
|
-
end
|
147
|
-
|
148
|
-
def set_new_method_name(new_method_name, corrector)
|
149
|
-
range = block_node.send_node.loc.selector
|
150
|
-
if (send_end = block_node.send_node.loc.end)
|
151
|
-
# If there are arguments (only true in the `each_with_object` case)
|
152
|
-
range = range.begin.join(send_end)
|
153
|
-
end
|
154
|
-
corrector.replace(range, new_method_name)
|
155
|
-
end
|
156
|
-
|
157
|
-
def set_new_arg_name(transformed_argname, corrector)
|
158
|
-
return if block_node.numblock_type?
|
159
|
-
|
160
|
-
corrector.replace(block_node.arguments, "|#{transformed_argname}|")
|
161
|
-
end
|
162
|
-
|
163
|
-
def set_new_body_expression(transforming_body_expr, corrector)
|
164
|
-
body_source = transforming_body_expr.source
|
165
|
-
body_source = "{ #{body_source} }" if transforming_body_expr.hash_type? && !transforming_body_expr.braces?
|
166
|
-
|
167
|
-
corrector.replace(block_node.body, body_source)
|
168
|
-
end
|
169
|
-
end
|
170
170
|
end
|
171
171
|
end
|
172
172
|
end
|
@@ -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
|
@@ -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')
|
@@ -97,7 +100,7 @@ module RuboCop
|
|
97
100
|
|
98
101
|
def check_for_file_join_with_rails_root(node)
|
99
102
|
return unless file_join_nodes?(node)
|
100
|
-
return unless node.arguments
|
103
|
+
return unless valid_arguments_for_file_join_with_rails_root?(node.arguments)
|
101
104
|
|
102
105
|
register_offense(node, require_to_s: true) do |corrector|
|
103
106
|
autocorrect_file_join(corrector, node) unless node.first_argument.array_type?
|
@@ -108,8 +111,7 @@ module RuboCop
|
|
108
111
|
return unless style == :slashes
|
109
112
|
return unless rails_root_nodes?(node)
|
110
113
|
return unless rails_root_join_nodes?(node)
|
111
|
-
return unless node.arguments
|
112
|
-
return unless node.arguments.all?(&:str_type?)
|
114
|
+
return unless valid_string_arguments_for_rails_root_join?(node.arguments)
|
113
115
|
|
114
116
|
register_offense(node, require_to_s: false) do |corrector|
|
115
117
|
autocorrect_rails_root_join_with_string_arguments(corrector, node)
|
@@ -120,15 +122,42 @@ module RuboCop
|
|
120
122
|
return unless style == :arguments
|
121
123
|
return unless rails_root_nodes?(node)
|
122
124
|
return unless rails_root_join_nodes?(node)
|
123
|
-
return unless node.arguments
|
125
|
+
return unless valid_slash_separated_path_for_rails_root_join?(node.arguments)
|
124
126
|
|
125
127
|
register_offense(node, require_to_s: false) do |corrector|
|
126
128
|
autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
|
127
129
|
end
|
128
130
|
end
|
129
131
|
|
130
|
-
def
|
131
|
-
|
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)
|
132
161
|
end
|
133
162
|
|
134
163
|
def register_offense(node, require_to_s:, &block)
|
@@ -226,7 +255,7 @@ module RuboCop
|
|
226
255
|
|
227
256
|
def autocorrect_rails_root_join_with_slash_separated_path(corrector, node)
|
228
257
|
node.arguments.each do |argument|
|
229
|
-
next unless
|
258
|
+
next unless string_contains_slash?(argument)
|
230
259
|
|
231
260
|
index = argument.source.index(File::SEPARATOR)
|
232
261
|
rest = inner_range_of(argument).adjust(begin_pos: index - 1)
|
@@ -29,7 +29,7 @@ module RuboCop
|
|
29
29
|
def on_send(node)
|
30
30
|
return unless node.arguments.empty?
|
31
31
|
return unless (receiver = node.receiver)
|
32
|
-
return
|
32
|
+
return unless receiver.type?(:str, :array)
|
33
33
|
|
34
34
|
add_offense(node.loc.selector)
|
35
35
|
end
|
@@ -115,6 +115,10 @@ module RuboCop
|
|
115
115
|
$_)))
|
116
116
|
PATTERN
|
117
117
|
|
118
|
+
def_node_matcher :delegated_methods, <<~PATTERN
|
119
|
+
(send nil? :delegate (sym $_)+ (hash <(pair (sym :to) _) ...>))
|
120
|
+
PATTERN
|
121
|
+
|
118
122
|
def on_send(node)
|
119
123
|
methods_node = only_or_except_filter_methods(node)
|
120
124
|
return unless methods_node
|
@@ -139,7 +143,13 @@ module RuboCop
|
|
139
143
|
return [] unless block
|
140
144
|
|
141
145
|
defined_methods = block.each_child_node(:def).map(&:method_name)
|
142
|
-
defined_methods + aliased_action_methods(block, defined_methods)
|
146
|
+
defined_methods + delegated_action_methods(block) + aliased_action_methods(block, defined_methods)
|
147
|
+
end
|
148
|
+
|
149
|
+
def delegated_action_methods(node)
|
150
|
+
node.each_child_node(:send).flat_map do |child_node|
|
151
|
+
delegated_methods(child_node) || []
|
152
|
+
end
|
143
153
|
end
|
144
154
|
|
145
155
|
def aliased_action_methods(node, defined_methods)
|
@@ -56,11 +56,11 @@ module RuboCop
|
|
56
56
|
minimum_target_rails_version 5.0
|
57
57
|
|
58
58
|
def_node_matcher :pluck_candidate?, <<~PATTERN
|
59
|
-
(
|
59
|
+
(any_block (call _ {:map :collect}) $_argument (send lvar :[] $_key))
|
60
60
|
PATTERN
|
61
61
|
|
62
62
|
def on_block(node)
|
63
|
-
return if node.each_ancestor(:
|
63
|
+
return if node.each_ancestor(:any_block).any?
|
64
64
|
|
65
65
|
pluck_candidate?(node) do |argument, key|
|
66
66
|
next if key.regexp_type? || !use_one_block_argument?(argument)
|
@@ -110,7 +110,7 @@ module RuboCop
|
|
110
110
|
def on_if(node)
|
111
111
|
return unless cop_config['UnlessBlank']
|
112
112
|
return unless node.unless?
|
113
|
-
return if node.else? && config.
|
113
|
+
return if node.else? && config.cop_enabled?('Style/UnlessElse')
|
114
114
|
|
115
115
|
unless_blank?(node) do |method_call, receiver|
|
116
116
|
range = unless_condition(node, method_call)
|
@@ -40,7 +40,7 @@ module RuboCop
|
|
40
40
|
|
41
41
|
def on_send(node)
|
42
42
|
association_with_reflection(node) do |reflection_class_name|
|
43
|
-
return if reflection_class_name.value.send_type? && reflection_class_name.value.receiver
|
43
|
+
return if reflection_class_name.value.send_type? && !reflection_class_name.value.receiver&.const_type?
|
44
44
|
return if reflection_class_name.value.lvar_type? && str_assigned?(reflection_class_name)
|
45
45
|
|
46
46
|
add_offense(reflection_class_name) do |corrector|
|
@@ -218,7 +218,7 @@ module RuboCop
|
|
218
218
|
return unless (last_argument = node.last_argument)
|
219
219
|
|
220
220
|
drop_table_call(node) do
|
221
|
-
unless node.parent.
|
221
|
+
unless node.parent.any_block_type? || last_argument.block_pass_type?
|
222
222
|
add_offense(node, message: format(MSG, action: 'drop_table(without block)'))
|
223
223
|
end
|
224
224
|
end
|
@@ -182,7 +182,7 @@ module RuboCop
|
|
182
182
|
def right_assignment_node(assignment)
|
183
183
|
node = assignment.node.child_nodes.first
|
184
184
|
|
185
|
-
return node unless node&.
|
185
|
+
return node unless node&.any_block_type?
|
186
186
|
|
187
187
|
node.send_node
|
188
188
|
end
|
@@ -248,7 +248,7 @@ module RuboCop
|
|
248
248
|
end
|
249
249
|
|
250
250
|
def conditional?(parent)
|
251
|
-
parent.
|
251
|
+
parent.type?(:if, :case)
|
252
252
|
end
|
253
253
|
|
254
254
|
def deparenthesize(node)
|
@@ -305,7 +305,7 @@ module RuboCop
|
|
305
305
|
|
306
306
|
node = assignable_node(node)
|
307
307
|
method, sibling_index = find_method_with_sibling_index(node.parent)
|
308
|
-
return false unless method
|
308
|
+
return false unless method&.type?(:def, :any_block)
|
309
309
|
|
310
310
|
method.children.size == node.sibling_index + sibling_index
|
311
311
|
end
|
@@ -324,7 +324,7 @@ module RuboCop
|
|
324
324
|
|
325
325
|
def explicit_return?(node)
|
326
326
|
ret = assignable_node(node).parent
|
327
|
-
ret
|
327
|
+
ret&.type?(:return, :next)
|
328
328
|
end
|
329
329
|
|
330
330
|
def return_value_assigned?(node)
|
@@ -33,7 +33,7 @@ module RuboCop
|
|
33
33
|
|
34
34
|
def on_send(node)
|
35
35
|
return unless (receiver = node.receiver)
|
36
|
-
return unless receiver.
|
36
|
+
return unless receiver.type?(:str, :dstr)
|
37
37
|
return unless receiver.respond_to?(:heredoc?) && receiver.heredoc?
|
38
38
|
|
39
39
|
register_offense(node, receiver)
|
@@ -99,7 +99,7 @@ module RuboCop
|
|
99
99
|
return false unless transaction_method_name?(node.method_name)
|
100
100
|
return false unless (parent = node.parent)
|
101
101
|
|
102
|
-
parent.
|
102
|
+
parent.any_block_type? && parent.body
|
103
103
|
end
|
104
104
|
|
105
105
|
def statement(statement_node)
|
@@ -113,7 +113,7 @@ module RuboCop
|
|
113
113
|
end
|
114
114
|
|
115
115
|
def nested_block?(statement_node)
|
116
|
-
name = statement_node.ancestors.find(&:
|
116
|
+
name = statement_node.ancestors.find(&:any_block_type?).children.first.method_name
|
117
117
|
!transaction_method_name?(name)
|
118
118
|
end
|
119
119
|
|
@@ -51,51 +51,28 @@ module RuboCop
|
|
51
51
|
|
52
52
|
MSG = 'Use `distinct` before `pluck`.'
|
53
53
|
RESTRICT_ON_SEND = %i[uniq].freeze
|
54
|
-
NEWLINE = "\n"
|
55
|
-
PATTERN = '[!^block (send (send %<type>s :pluck ...) :uniq ...)]'
|
56
54
|
|
57
|
-
def_node_matcher :
|
58
|
-
|
59
|
-
def_node_matcher :aggressive_node_match, format(PATTERN, type: '_')
|
55
|
+
def_node_matcher :uniq_before_pluck, '[!^any_block $(send $(send _ :pluck ...) :uniq ...)]'
|
60
56
|
|
61
57
|
def on_send(node)
|
62
|
-
|
63
|
-
|
64
|
-
else
|
65
|
-
aggressive_node_match(node)
|
66
|
-
end
|
67
|
-
|
68
|
-
return unless uniq
|
58
|
+
uniq_before_pluck(node) do |uniq_node, pluck_node|
|
59
|
+
next if style == :conservative && !pluck_node.receiver&.const_type?
|
69
60
|
|
70
|
-
|
71
|
-
|
61
|
+
add_offense(uniq_node.loc.selector) do |corrector|
|
62
|
+
autocorrect(corrector, uniq_node, pluck_node)
|
63
|
+
end
|
72
64
|
end
|
73
65
|
end
|
74
66
|
|
75
67
|
private
|
76
68
|
|
77
|
-
def autocorrect(corrector,
|
78
|
-
|
69
|
+
def autocorrect(corrector, uniq_node, pluck_node)
|
70
|
+
corrector.remove(range_between(pluck_node.loc.end.end_pos, uniq_node.loc.selector.end_pos))
|
79
71
|
|
80
|
-
|
81
|
-
if (dot = node.receiver.loc.dot)
|
72
|
+
if (dot = pluck_node.loc.dot)
|
82
73
|
corrector.insert_before(dot.begin, '.distinct')
|
83
74
|
else
|
84
|
-
corrector.insert_before(
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def dot_method_with_whitespace(method, node)
|
89
|
-
range_between(dot_method_begin_pos(method, node), node.loc.selector.end_pos)
|
90
|
-
end
|
91
|
-
|
92
|
-
def dot_method_begin_pos(method, node)
|
93
|
-
lines = node.source.split(NEWLINE)
|
94
|
-
|
95
|
-
if lines.last.strip == ".#{method}"
|
96
|
-
node.source.rindex(NEWLINE)
|
97
|
-
else
|
98
|
-
node.loc.dot.begin_pos
|
75
|
+
corrector.insert_before(pluck_node, 'distinct.')
|
99
76
|
end
|
100
77
|
end
|
101
78
|
end
|
@@ -73,7 +73,7 @@ module RuboCop
|
|
73
73
|
|
74
74
|
def column_names(node, uniqueness_part)
|
75
75
|
arg = node.first_argument
|
76
|
-
return unless arg.
|
76
|
+
return unless arg.type?(:str, :sym)
|
77
77
|
|
78
78
|
ret = [arg.value]
|
79
79
|
names_from_scope = column_names_from_scope(uniqueness_part)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'lint_roller'
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Rails
|
7
|
+
# A plugin that integrates RuboCop Rails with RuboCop's plugin system.
|
8
|
+
class Plugin < LintRoller::Plugin
|
9
|
+
def about
|
10
|
+
LintRoller::About.new(
|
11
|
+
name: 'rubocop-rails',
|
12
|
+
version: Version::STRING,
|
13
|
+
homepage: 'https://github.com/rubocop/rubocop-rails',
|
14
|
+
description: 'A RuboCop extension focused on enforcing Rails best practices and coding conventions.'
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def supported?(context)
|
19
|
+
context.engine == :rubocop
|
20
|
+
end
|
21
|
+
|
22
|
+
def rules(_context)
|
23
|
+
project_root = Pathname.new(__dir__).join('../../..')
|
24
|
+
|
25
|
+
ConfigObsoletion.files << project_root.join('config', 'obsoletion.yml')
|
26
|
+
|
27
|
+
# FIXME: This is a dirty hack relying on a private constant to prevent
|
28
|
+
# "Warning: AllCops does not support TargetRailsVersion parameter".
|
29
|
+
# It should be updated to a better design in the future.
|
30
|
+
without_warnings do
|
31
|
+
ConfigValidator.const_set(:COMMON_PARAMS, ConfigValidator::COMMON_PARAMS.dup << 'TargetRailsVersion')
|
32
|
+
end
|
33
|
+
|
34
|
+
LintRoller::Rules.new(type: :path, config_format: :rubocop, value: project_root.join('config', 'default.yml'))
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def without_warnings
|
40
|
+
original_verbose = $VERBOSE
|
41
|
+
$VERBOSE = nil
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
$VERBOSE = original_verbose
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/rubocop/rails.rb
CHANGED
@@ -1,13 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RuboCop
|
4
|
-
# RuboCop Rails project namespace
|
4
|
+
# RuboCop Rails project namespace.
|
5
5
|
module Rails
|
6
|
-
PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
|
7
|
-
CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
|
8
|
-
|
9
|
-
private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
|
10
|
-
|
11
|
-
::RuboCop::ConfigObsoletion.files << PROJECT_ROOT.join('config', 'obsoletion.yml')
|
12
6
|
end
|
13
7
|
end
|
data/lib/rubocop-rails.rb
CHANGED
@@ -7,12 +7,9 @@ require 'active_support/core_ext/object/blank'
|
|
7
7
|
|
8
8
|
require_relative 'rubocop/rails'
|
9
9
|
require_relative 'rubocop/rails/version'
|
10
|
-
require_relative 'rubocop/rails/inject'
|
11
10
|
require_relative 'rubocop/rails/schema_loader'
|
12
11
|
require_relative 'rubocop/rails/schema_loader/schema'
|
13
|
-
|
14
|
-
RuboCop::Rails::Inject.defaults!
|
15
|
-
|
12
|
+
require_relative 'rubocop/rails/plugin'
|
16
13
|
require_relative 'rubocop/cop/rails_cops'
|
17
14
|
|
18
15
|
require_relative 'rubocop/rails/migration_file_skippable'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.30.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bozhidar Batsov
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
- Yuji Nakayama
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2025-
|
12
|
+
date: 2025-03-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -25,6 +25,20 @@ dependencies:
|
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: 4.2.0
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: lint_roller
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1.1'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1.1'
|
28
42
|
- !ruby/object:Gem::Dependency
|
29
43
|
name: rack
|
30
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -45,7 +59,7 @@ dependencies:
|
|
45
59
|
requirements:
|
46
60
|
- - ">="
|
47
61
|
- !ruby/object:Gem::Version
|
48
|
-
version: 1.
|
62
|
+
version: 1.72.1
|
49
63
|
- - "<"
|
50
64
|
- !ruby/object:Gem::Version
|
51
65
|
version: '2.0'
|
@@ -55,7 +69,7 @@ dependencies:
|
|
55
69
|
requirements:
|
56
70
|
- - ">="
|
57
71
|
- !ruby/object:Gem::Version
|
58
|
-
version: 1.
|
72
|
+
version: 1.72.1
|
59
73
|
- - "<"
|
60
74
|
- !ruby/object:Gem::Version
|
61
75
|
version: '2.0'
|
@@ -65,7 +79,7 @@ dependencies:
|
|
65
79
|
requirements:
|
66
80
|
- - ">="
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.
|
82
|
+
version: 1.38.0
|
69
83
|
- - "<"
|
70
84
|
- !ruby/object:Gem::Version
|
71
85
|
version: '2.0'
|
@@ -75,7 +89,7 @@ dependencies:
|
|
75
89
|
requirements:
|
76
90
|
- - ">="
|
77
91
|
- !ruby/object:Gem::Version
|
78
|
-
version: 1.
|
92
|
+
version: 1.38.0
|
79
93
|
- - "<"
|
80
94
|
- !ruby/object:Gem::Version
|
81
95
|
version: '2.0'
|
@@ -238,8 +252,8 @@ files:
|
|
238
252
|
- lib/rubocop/cop/rails/where_range.rb
|
239
253
|
- lib/rubocop/cop/rails_cops.rb
|
240
254
|
- lib/rubocop/rails.rb
|
241
|
-
- lib/rubocop/rails/inject.rb
|
242
255
|
- lib/rubocop/rails/migration_file_skippable.rb
|
256
|
+
- lib/rubocop/rails/plugin.rb
|
243
257
|
- lib/rubocop/rails/schema_loader.rb
|
244
258
|
- lib/rubocop/rails/schema_loader/schema.rb
|
245
259
|
- lib/rubocop/rails/version.rb
|
@@ -250,9 +264,10 @@ metadata:
|
|
250
264
|
homepage_uri: https://docs.rubocop.org/rubocop-rails/
|
251
265
|
changelog_uri: https://github.com/rubocop/rubocop-rails/blob/master/CHANGELOG.md
|
252
266
|
source_code_uri: https://github.com/rubocop/rubocop-rails/
|
253
|
-
documentation_uri: https://docs.rubocop.org/rubocop-rails/2.
|
267
|
+
documentation_uri: https://docs.rubocop.org/rubocop-rails/2.30/
|
254
268
|
bug_tracker_uri: https://github.com/rubocop/rubocop-rails/issues
|
255
269
|
rubygems_mfa_required: 'true'
|
270
|
+
default_lint_roller_plugin: RuboCop::Rails::Plugin
|
256
271
|
rdoc_options: []
|
257
272
|
require_paths:
|
258
273
|
- lib
|
data/lib/rubocop/rails/inject.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RuboCop
|
4
|
-
module Rails
|
5
|
-
# Because RuboCop doesn't yet support plugins, we have to monkey patch in a
|
6
|
-
# bit of our configuration.
|
7
|
-
module Inject
|
8
|
-
def self.defaults!
|
9
|
-
path = CONFIG_DEFAULT.to_s
|
10
|
-
hash = ConfigLoader.send(:load_yaml_configuration, path)
|
11
|
-
config = Config.new(hash, path).tap(&:make_excludes_absolute)
|
12
|
-
puts "configuration from #{path}" if ConfigLoader.debug?
|
13
|
-
config = ConfigLoader.merge_with_default(config, path, unset_nil: false)
|
14
|
-
ConfigLoader.instance_variable_set(:@default_configuration, config)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|