rubocop-rails 2.4.1 → 2.6.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +6 -2
  4. data/config/default.yml +71 -6
  5. data/lib/rubocop-rails.rb +3 -0
  6. data/lib/rubocop/cop/mixin/active_record_helper.rb +77 -0
  7. data/lib/rubocop/cop/mixin/index_method.rb +161 -0
  8. data/lib/rubocop/cop/rails/content_tag.rb +82 -0
  9. data/lib/rubocop/cop/rails/create_table_with_timestamps.rb +1 -3
  10. data/lib/rubocop/cop/rails/delegate.rb +1 -3
  11. data/lib/rubocop/cop/rails/dynamic_find_by.rb +40 -15
  12. data/lib/rubocop/cop/rails/environment_comparison.rb +60 -14
  13. data/lib/rubocop/cop/rails/exit.rb +2 -2
  14. data/lib/rubocop/cop/rails/file_path.rb +1 -0
  15. data/lib/rubocop/cop/rails/http_positional_arguments.rb +2 -2
  16. data/lib/rubocop/cop/rails/http_status.rb +2 -0
  17. data/lib/rubocop/cop/rails/index_by.rb +56 -0
  18. data/lib/rubocop/cop/rails/index_with.rb +59 -0
  19. data/lib/rubocop/cop/rails/inverse_of.rb +0 -4
  20. data/lib/rubocop/cop/rails/link_to_blank.rb +1 -3
  21. data/lib/rubocop/cop/rails/pick.rb +51 -0
  22. data/lib/rubocop/cop/rails/presence.rb +2 -6
  23. data/lib/rubocop/cop/rails/rake_environment.rb +24 -6
  24. data/lib/rubocop/cop/rails/redundant_foreign_key.rb +80 -0
  25. data/lib/rubocop/cop/rails/redundant_receiver_in_with_options.rb +0 -3
  26. data/lib/rubocop/cop/rails/refute_methods.rb +52 -26
  27. data/lib/rubocop/cop/rails/reversible_migration.rb +6 -1
  28. data/lib/rubocop/cop/rails/save_bang.rb +16 -9
  29. data/lib/rubocop/cop/rails/skips_model_validations.rb +4 -1
  30. data/lib/rubocop/cop/rails/time_zone.rb +1 -3
  31. data/lib/rubocop/cop/rails/uniq_before_pluck.rb +16 -16
  32. data/lib/rubocop/cop/rails/unique_validation_without_index.rb +155 -0
  33. data/lib/rubocop/cop/rails/unknown_env.rb +7 -6
  34. data/lib/rubocop/cop/rails_cops.rb +8 -0
  35. data/lib/rubocop/rails/inject.rb +1 -1
  36. data/lib/rubocop/rails/schema_loader.rb +61 -0
  37. data/lib/rubocop/rails/schema_loader/schema.rb +190 -0
  38. data/lib/rubocop/rails/version.rb +1 -1
  39. metadata +32 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c9d8cbc9fa9fc0752a334a92dd7af50f2a33331ac1b9604dc7c1f27efbc141e6
4
- data.tar.gz: 81833c9b0dbc71254f75f18eb59a13147025efdff95342c3ba1855e773301fc2
3
+ metadata.gz: b6b55ee8df864d6eaee4a491f4324343ee2274eafcb352952a81ff045832aa22
4
+ data.tar.gz: 344ea324a589dc25ce3e87366c4af33bde4e392acf6acaccf9b9296066680824
5
5
  SHA512:
6
- metadata.gz: a5ffc7a5e763076a13e5addebf2b4321ebfddbbb19e752bfe9697003eb4451e5d5eebc4ced8c2f3f55e1e10f24a81670e14b7ec48ee748d1c15996085618b5c8
7
- data.tar.gz: 0e8b3557bfa819b2fb1304f992a48845afbb52db0c8c7beb970bbcc09c4345c5f9d2f9f273efb2692374f3a03c28c6d615824af20ca669c40359299dca53f41a
6
+ metadata.gz: 5bdccc2a5e2a45533cb5bb33b2df26872f806dd590c13d640d7449315e56ec6250aebdb876fb37812bb3dd47cdaeda61c6049bc5cf98b01b473a616e9061491b
7
+ data.tar.gz: 6f8673f2e8d0068bcdbe1599936793461bab38f4b333a4ac6e22e03e7162851d6fc20543ec58e3f2fabdf404764b09f8ff47e6a5c23d492f0c8bb28acd012296
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-18 Bozhidar Batsov
1
+ Copyright (c) 2012-20 Bozhidar Batsov
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -18,7 +18,7 @@ gem install rubocop-rails
18
18
  or if you use bundler put this in your `Gemfile`
19
19
 
20
20
  ```ruby
21
- gem 'rubocop-rails'
21
+ gem 'rubocop-rails', require: false
22
22
  ```
23
23
 
24
24
  ## Usage
@@ -76,11 +76,15 @@ Rails/FindBy:
76
76
  - lib/example.rb
77
77
  ```
78
78
 
79
+ ## Documentation
80
+
81
+ You can read a lot more about RuboCop Rails in its [official docs](https://docs.rubocop.org/rubocop-rails/).
82
+
79
83
  ## Compatibility
80
84
 
81
85
  Rails cops support the following versions:
82
86
 
83
- - Rails 4.0+
87
+ - Rails 4.2+
84
88
 
85
89
  ## Contributing
86
90
 
@@ -1,5 +1,9 @@
1
1
  # Common configuration.
2
2
 
3
+ inherit_mode:
4
+ merge:
5
+ - Exclude
6
+
3
7
  AllCops:
4
8
  Exclude:
5
9
  - bin/*
@@ -53,22 +57,30 @@ Rails/ActiveSupportAliases:
53
57
  Rails/ApplicationController:
54
58
  Description: 'Check that controllers subclass ApplicationController.'
55
59
  Enabled: true
60
+ SafeAutoCorrect: false
56
61
  VersionAdded: '2.4'
62
+ VersionChanged: '2.5'
57
63
 
58
64
  Rails/ApplicationJob:
59
65
  Description: 'Check that jobs subclass ApplicationJob.'
60
66
  Enabled: true
67
+ SafeAutoCorrect: false
61
68
  VersionAdded: '0.49'
69
+ VersionChanged: '2.5'
62
70
 
63
71
  Rails/ApplicationMailer:
64
72
  Description: 'Check that mailers subclass ApplicationMailer.'
65
73
  Enabled: true
74
+ SafeAutoCorrect: false
66
75
  VersionAdded: '2.4'
76
+ VersionChanged: '2.5'
67
77
 
68
78
  Rails/ApplicationRecord:
69
79
  Description: 'Check that models subclass ApplicationRecord.'
70
80
  Enabled: true
81
+ SafeAutoCorrect: false
71
82
  VersionAdded: '0.49'
83
+ VersionChanged: '2.5'
72
84
 
73
85
  Rails/AssertNot:
74
86
  Description: 'Use `assert_not` instead of `assert !`.'
@@ -80,7 +92,7 @@ Rails/AssertNot:
80
92
  Rails/BelongsTo:
81
93
  Description: >-
82
94
  Use `optional: true` instead of `required: false` for
83
- `belongs_to` relations'
95
+ `belongs_to` relations.
84
96
  Enabled: true
85
97
  VersionAdded: '0.62'
86
98
 
@@ -107,6 +119,14 @@ Rails/BulkChangeTable:
107
119
  Include:
108
120
  - db/migrate/*.rb
109
121
 
122
+ Rails/ContentTag:
123
+ Description: 'Use `tag` instead of `content_tag`.'
124
+ Reference:
125
+ - 'https://github.com/rails/rails/issues/25195'
126
+ - 'https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag'
127
+ Enabled: true
128
+ VersionAdded: '2.6'
129
+
110
130
  Rails/CreateTableWithTimestamps:
111
131
  Description: >-
112
132
  Checks the migration for which timestamps are not included
@@ -153,8 +173,14 @@ Rails/DynamicFindBy:
153
173
  StyleGuide: 'https://rails.rubystyle.guide#find_by'
154
174
  Enabled: true
155
175
  VersionAdded: '0.44'
176
+ VersionChanged: '2.6'
177
+ # The `Whitelist` has been deprecated, Please use `AllowedMethods` instead.
156
178
  Whitelist:
157
179
  - find_by_sql
180
+ AllowedMethods:
181
+ - find_by_sql
182
+ AllowedReceivers:
183
+ - Gem::Specification
158
184
 
159
185
  Rails/EnumHash:
160
186
  Description: 'Prefer hash syntax over array syntax when defining enums.'
@@ -172,7 +198,7 @@ Rails/EnumUniqueness:
172
198
  - app/models/**/*.rb
173
199
 
174
200
  Rails/EnvironmentComparison:
175
- Description: "Favor `Rails.env.production?` over `Rails.env == 'production'`"
201
+ Description: "Favor `Rails.env.production?` over `Rails.env == 'production'`."
176
202
  Enabled: true
177
203
  VersionAdded: '0.52'
178
204
 
@@ -233,7 +259,7 @@ Rails/HasManyOrHasOneDependent:
233
259
  - app/models/**/*.rb
234
260
 
235
261
  Rails/HelperInstanceVariable:
236
- Description: 'Do not use instance variables in helpers'
262
+ Description: 'Do not use instance variables in helpers.'
237
263
  Enabled: true
238
264
  VersionAdded: '2.0'
239
265
  Include:
@@ -264,6 +290,16 @@ Rails/IgnoredSkipActionFilterOption:
264
290
  Include:
265
291
  - app/controllers/**/*.rb
266
292
 
293
+ Rails/IndexBy:
294
+ Description: 'Prefer `index_by` over `each_with_object` or `map`.'
295
+ Enabled: true
296
+ VersionAdded: '2.5'
297
+
298
+ Rails/IndexWith:
299
+ Description: 'Prefer `index_with` over `each_with_object` or `map`.'
300
+ Enabled: true
301
+ VersionAdded: '2.5'
302
+
267
303
  Rails/InverseOf:
268
304
  Description: 'Checks for associations where the inverse cannot be determined automatically.'
269
305
  Enabled: true
@@ -290,7 +326,7 @@ Rails/LinkToBlank:
290
326
  VersionAdded: '0.62'
291
327
 
292
328
  Rails/NotNullColumn:
293
- Description: 'Do not add a NOT NULL column without a default value'
329
+ Description: 'Do not add a NOT NULL column without a default value.'
294
330
  Enabled: true
295
331
  VersionAdded: '0.43'
296
332
  Include:
@@ -312,6 +348,12 @@ Rails/OutputSafety:
312
348
  Enabled: true
313
349
  VersionAdded: '0.41'
314
350
 
351
+ Rails/Pick:
352
+ Description: 'Prefer `pick` over `pluck(...).first`.'
353
+ Enabled: true
354
+ Safe: false
355
+ VersionAdded: '2.6'
356
+
315
357
  Rails/PluralizationGrammar:
316
358
  Description: 'Checks for incorrect grammar when using methods like `3.day.ago`.'
317
359
  Enabled: true
@@ -339,9 +381,12 @@ Rails/RakeEnvironment:
339
381
  Enabled: true
340
382
  Safe: false
341
383
  VersionAdded: '2.4'
384
+ VersionChanged: '2.6'
342
385
  Include:
343
386
  - '**/Rakefile'
344
387
  - '**/*.rake'
388
+ Exclude:
389
+ - 'lib/capistrano/tasks/**/*.rake'
345
390
 
346
391
  Rails/ReadWriteAttribute:
347
392
  Description: >-
@@ -363,6 +408,11 @@ Rails/RedundantAllowNil:
363
408
  Include:
364
409
  - app/models/**/*.rb
365
410
 
411
+ Rails/RedundantForeignKey:
412
+ Description: 'Checks for associations where the `:foreign_key` option is redundant.'
413
+ Enabled: true
414
+ VersionAdded: '2.6'
415
+
366
416
  Rails/RedundantReceiverInWithOptions:
367
417
  Description: 'Checks for redundant receiver in `with_options`.'
368
418
  Enabled: true
@@ -377,6 +427,10 @@ Rails/RefuteMethods:
377
427
  Description: 'Use `assert_not` methods instead of `refute` methods.'
378
428
  Enabled: true
379
429
  VersionAdded: '0.56'
430
+ EnforcedStyle: assert_not
431
+ SupportedStyles:
432
+ - assert_not
433
+ - refute
380
434
  Include:
381
435
  - '**/test/**/*'
382
436
 
@@ -406,7 +460,7 @@ Rails/ReversibleMigration:
406
460
  - db/migrate/*.rb
407
461
 
408
462
  Rails/SafeNavigation:
409
- Description: "Use Ruby's safe navigation operator (`&.`) instead of `try!`"
463
+ Description: "Use Ruby's safe navigation operator (`&.`) instead of `try!`."
410
464
  Enabled: true
411
465
  VersionAdded: '0.43'
412
466
  # This will convert usages of `try` to use safe navigation as well as `try!`.
@@ -485,13 +539,20 @@ Rails/UniqBeforePluck:
485
539
  Description: 'Prefer the use of uniq or distinct before pluck.'
486
540
  Enabled: true
487
541
  VersionAdded: '0.40'
488
- VersionChanged: '0.47'
542
+ VersionChanged: '2.6'
489
543
  EnforcedStyle: conservative
490
544
  SupportedStyles:
491
545
  - conservative
492
546
  - aggressive
493
547
  AutoCorrect: false
494
548
 
549
+ Rails/UniqueValidationWithoutIndex:
550
+ Description: 'Uniqueness validation should be with a unique index.'
551
+ Enabled: true
552
+ VersionAdded: '2.5'
553
+ Include:
554
+ - app/models/**/*.rb
555
+
495
556
  Rails/UnknownEnv:
496
557
  Description: 'Use correct environment name.'
497
558
  Enabled: true
@@ -508,3 +569,7 @@ Rails/Validation:
508
569
  VersionChanged: '0.41'
509
570
  Include:
510
571
  - app/models/**/*.rb
572
+
573
+ # Accept `redirect_to(...) and return` and similar cases.
574
+ Style/AndOr:
575
+ EnforcedStyle: conditionals
@@ -2,10 +2,13 @@
2
2
 
3
3
  require 'rubocop'
4
4
  require 'rack/utils'
5
+ require 'active_support/inflector'
5
6
 
6
7
  require_relative 'rubocop/rails'
7
8
  require_relative 'rubocop/rails/version'
8
9
  require_relative 'rubocop/rails/inject'
10
+ require_relative 'rubocop/rails/schema_loader'
11
+ require_relative 'rubocop/rails/schema_loader/schema'
9
12
 
10
13
  RuboCop::Rails::Inject.defaults!
11
14
 
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # A mixin to extend cops for Active Record features
6
+ module ActiveRecordHelper
7
+ extend NodePattern::Macros
8
+
9
+ def_node_search :find_set_table_name, <<~PATTERN
10
+ (send self :table_name= {str sym})
11
+ PATTERN
12
+
13
+ def_node_search :find_belongs_to, <<~PATTERN
14
+ (send nil? :belongs_to {str sym} ...)
15
+ PATTERN
16
+
17
+ def external_dependency_checksum
18
+ return @external_dependency_checksum if defined?(@external_dependency_checksum)
19
+
20
+ schema_path = RuboCop::Rails::SchemaLoader.db_schema_path
21
+ return nil if schema_path.nil?
22
+
23
+ schema_code = File.read(schema_path)
24
+
25
+ @external_dependency_checksum ||= Digest::SHA1.hexdigest(schema_code)
26
+ end
27
+
28
+ def schema
29
+ RuboCop::Rails::SchemaLoader.load(target_ruby_version)
30
+ end
31
+
32
+ def table_name(class_node)
33
+ table_name = find_set_table_name(class_node).to_a.last&.first_argument
34
+ return table_name.value.to_s if table_name
35
+
36
+ namespaces = class_node.each_ancestor(:class, :module)
37
+ [class_node, *namespaces]
38
+ .reverse
39
+ .map { |klass| klass.identifier.children[1] }.join('_')
40
+ .tableize
41
+ end
42
+
43
+ # Resolve relation into column name.
44
+ # It just returns column_name if the column exists.
45
+ # Or it tries to resolve column_name as a relation.
46
+ # It returns `nil` if it can't resolve.
47
+ #
48
+ # @param name [String]
49
+ # @param class_node [RuboCop::AST::Node]
50
+ # @param table [RuboCop::Rails::SchemaLoader::Table]
51
+ # @return [String, nil]
52
+ def resolve_relation_into_column(name:, class_node:, table:)
53
+ return name if table.with_column?(name: name)
54
+
55
+ find_belongs_to(class_node) do |belongs_to|
56
+ next unless belongs_to.first_argument.value.to_s == name
57
+
58
+ fk = foreign_key_of(belongs_to) || "#{name}_id"
59
+ return fk if table.with_column?(name: fk)
60
+ end
61
+ nil
62
+ end
63
+
64
+ def foreign_key_of(belongs_to)
65
+ options = belongs_to.last_argument
66
+ return unless options.hash_type?
67
+
68
+ options.each_pair.find do |pair|
69
+ next unless pair.key.sym_type? && pair.key.value == :foreign_key
70
+ next unless pair.value.sym_type? || pair.value.str_type?
71
+
72
+ break pair.value.value.to_s
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for Rails/IndexBy and Rails/IndexWith
6
+ module IndexMethod # rubocop:disable Metrics/ModuleLength
7
+ def on_block(node)
8
+ on_bad_each_with_object(node) do |*match|
9
+ handle_possible_offense(node, match, 'each_with_object')
10
+ end
11
+ end
12
+
13
+ def on_send(node)
14
+ on_bad_map_to_h(node) do |*match|
15
+ handle_possible_offense(node, match, 'map { ... }.to_h')
16
+ end
17
+
18
+ on_bad_hash_brackets_map(node) do |*match|
19
+ handle_possible_offense(node, match, 'Hash[map { ... }]')
20
+ end
21
+ end
22
+
23
+ def on_csend(node)
24
+ on_bad_map_to_h(node) do |*match|
25
+ handle_possible_offense(node, match, 'map { ... }.to_h')
26
+ end
27
+ end
28
+
29
+ def autocorrect(node)
30
+ lambda do |corrector|
31
+ correction = prepare_correction(node)
32
+ execute_correction(corrector, node, correction)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # @abstract Implemented with `def_node_matcher`
39
+ def on_bad_each_with_object(_node)
40
+ raise NotImplementedError
41
+ end
42
+
43
+ # @abstract Implemented with `def_node_matcher`
44
+ def on_bad_map_to_h(_node)
45
+ raise NotImplementedError
46
+ end
47
+
48
+ # @abstract Implemented with `def_node_matcher`
49
+ def on_bad_hash_brackets_map(_node)
50
+ raise NotImplementedError
51
+ end
52
+
53
+ def handle_possible_offense(node, match, match_desc)
54
+ captures = extract_captures(match)
55
+
56
+ return if captures.noop_transformation?
57
+
58
+ add_offense(
59
+ node,
60
+ message: "Prefer `#{new_method_name}` over `#{match_desc}`."
61
+ )
62
+ end
63
+
64
+ def extract_captures(match)
65
+ argname, body_expr = *match
66
+ Captures.new(argname, body_expr)
67
+ end
68
+
69
+ def new_method_name
70
+ raise NotImplementedError
71
+ end
72
+
73
+ def prepare_correction(node)
74
+ if (match = on_bad_each_with_object(node))
75
+ Autocorrection.from_each_with_object(node, match)
76
+ elsif (match = on_bad_map_to_h(node))
77
+ Autocorrection.from_map_to_h(node, match)
78
+ elsif (match = on_bad_hash_brackets_map(node))
79
+ Autocorrection.from_hash_brackets_map(node, match)
80
+ else
81
+ raise 'unreachable'
82
+ end
83
+ end
84
+
85
+ def execute_correction(corrector, node, correction)
86
+ correction.strip_prefix_and_suffix(node, corrector)
87
+ correction.set_new_method_name(new_method_name, corrector)
88
+
89
+ captures = extract_captures(correction.match)
90
+ correction.set_new_arg_name(captures.transformed_argname, corrector)
91
+ correction.set_new_body_expression(
92
+ captures.transforming_body_expr,
93
+ corrector
94
+ )
95
+ end
96
+
97
+ # Internal helper class to hold match data
98
+ Captures = Struct.new(
99
+ :transformed_argname,
100
+ :transforming_body_expr
101
+ ) do
102
+ def noop_transformation?
103
+ transforming_body_expr.lvar_type? &&
104
+ transforming_body_expr.children == [transformed_argname]
105
+ end
106
+ end
107
+
108
+ # Internal helper class to hold autocorrect data
109
+ Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do # rubocop:disable Metrics/BlockLength
110
+ def self.from_each_with_object(node, match)
111
+ new(match, node, 0, 0)
112
+ end
113
+
114
+ def self.from_map_to_h(node, match)
115
+ strip_trailing_chars = 0
116
+
117
+ unless node.parent&.block_type?
118
+ map_range = node.children.first.source_range
119
+ node_range = node.source_range
120
+ strip_trailing_chars = node_range.end_pos - map_range.end_pos
121
+ end
122
+
123
+ new(match, node.children.first, 0, strip_trailing_chars)
124
+ end
125
+
126
+ def self.from_hash_brackets_map(node, match)
127
+ new(match, node.children.last, 'Hash['.length, ']'.length)
128
+ end
129
+
130
+ def strip_prefix_and_suffix(node, corrector)
131
+ expression = node.loc.expression
132
+ corrector.remove_leading(expression, leading)
133
+ corrector.remove_trailing(expression, trailing)
134
+ end
135
+
136
+ def set_new_method_name(new_method_name, corrector)
137
+ range = block_node.send_node.loc.selector
138
+ if (send_end = block_node.send_node.loc.end)
139
+ # If there are arguments (only true in the `each_with_object` case)
140
+ range = range.begin.join(send_end)
141
+ end
142
+ corrector.replace(range, new_method_name)
143
+ end
144
+
145
+ def set_new_arg_name(transformed_argname, corrector)
146
+ corrector.replace(
147
+ block_node.arguments.loc.expression,
148
+ "|#{transformed_argname}|"
149
+ )
150
+ end
151
+
152
+ def set_new_body_expression(transforming_body_expr, corrector)
153
+ corrector.replace(
154
+ block_node.body.loc.expression,
155
+ transforming_body_expr.loc.expression.source
156
+ )
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end